rust-lang / rust-lang/rust

`field_representing_type_raw` mishandles const generics

Open
#162,127 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-coherence A-const-generics A-trait-system C-bug F-field_projections I-unsound T-compiler T-types WG-trait-system-refactor
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

I am filing this issue on behalf of @carlini, who discovered this bug with an LLM (labelled as "bug 4" at https://rust-lang.zulipchat.com/#narrow/channel/364551-t-types.2Ftrait-system-refactor/topic/Reporting.20a.20collection.20of.20soundness.20bugs.20in.20next-solver/near/617737394)

The following code is unsound. It reads the local variable after the variable has already gone out of scope, when compiled with -Znext-solver. It is possible that a similar unsoundness can also happen on the old solver, but I haven't figured out how.

It appears that the compiler doesn't realize that the two Pick implementations overlap, using two different lifetimes for <W<0> as Pick<'a, 'b>>::Out. I don't quite understand why the compiler seems to think that FieldRepresentingType<Left, 0, F>: Field and FieldRepresentingType<Right, 0, F>: Field can't both hold simultaneously.

Note that this code (and comments in it) are generated by an LLM. I did not verify the correctness of the comments.

cc @BennoLossin, who worked on field_projections

// Old solver: `W<0>: Pick<'_, 'static>` is ambiguous at the call in `main` (both impls apply).
// -Znext-solver: its builtin `FieldRepresentingType<_, 0, F>: Field` candidate answers
// `NoSolution` while `F` is still an inference variable, so coherence believes the two
// impls never overlap and each is then selected alone -- turning `&'a u8` into `&'static u8`.
#![feature(field_projections, field_representing_type_raw)] // needed because: both gates required to name `FieldRepresentingType`
#![allow(incomplete_features, internal_features, dead_code)]
#![forbid(unsafe_code)]

use core::field::{Field, FieldRepresentingType};

struct Left { l: u8 }  // needed because: two distinct structs make the two impl bounds distinct
struct Right { r: u8 }

struct W<const F: u32>;

trait Pick<'a, 'b> {
    type Out;
}

// Both impls apply at `W<0>` (field 0 exists in `Left` and `Right`); coherence should reject.
impl<'a, 'b, const F: u32> Pick<'a, 'b> for W<F>
where
    FieldRepresentingType<Left, 0, F>: Field,
{
    type Out = &'a u8;
}

impl<'a, 'b, const F: u32> Pick<'a, 'b> for W<F>
where
    FieldRepresentingType<Right, 0, F>: Field,
{
    type Out = &'b u8; // needed because: a literal `&'static u8` here hits a mono recursion limit under -Znext-solver
}

// Only the `Left` bound in scope: `Out` normalizes to `&'a u8`.
fn into_out<'a, const F: u32>(x: &'a u8) -> <W<F> as Pick<'a, 'static>>::Out
where
    FieldRepresentingType<Left, 0, F>: Field,
{
    x
}

// Only the `Right` bound in scope: `Out` normalizes to `&'static u8`.
fn out_into_static<'a, const F: u32>(y: <W<F> as Pick<'a, 'static>>::Out) -> &'static u8
where
    FieldRepresentingType<Right, 0, F>: Field,
{
    y
}

fn launder<'a, const F: u32>(x: &'a u8) -> &'static u8
where
    W<F>: Pick<'a, 'static>, // needed because: keeps `Out` rigid here so the value crosses unnormalized
    FieldRepresentingType<Left, 0, F>: Field,
    FieldRepresentingType<Right, 0, F>: Field,
{
    out_into_static::<F>(into_out::<F>(x))
}

fn main() {
    let escaped: &'static u8 = {
        let local = 42u8;
        launder::<0>(&local) // `&local` escapes its block as `&'static u8`
    };
    println!("{escaped}");
}
Meta

Reproducible with 1.100.0-nightly (2026-08-31 0dfb098f3aeecbe38c25)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the supplied reproducer on the stated nightly using -Znext-solver, then trace how coherence handles the FieldRepresentingType<_, 0, F>: Field candidates and const generics. Done means the overlapping impls are not treated as disjoint and the reproducer no longer permits a non-static reference to escape as 'static, with a regression test covering the case.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.