Skip to content

Remove 'static requirement on try_as_dyn#150161

Open
oli-obk wants to merge 2 commits intorust-lang:mainfrom
oli-obk:try_as_dyn_non_static
Open

Remove 'static requirement on try_as_dyn#150161
oli-obk wants to merge 2 commits intorust-lang:mainfrom
oli-obk:try_as_dyn_non_static

Conversation

@oli-obk
Copy link
Contributor

@oli-obk oli-obk commented Dec 19, 2025

@rustbot
Copy link
Collaborator

rustbot commented Dec 19, 2025

Some changes occurred to the CTFE machinery

cc @RalfJung, @oli-obk, @lcnr

Some changes occurred to the core trait solver

cc @rust-lang/initiative-trait-system-refactor

Some changes occurred to the CTFE / Miri interpreter

cc @rust-lang/miri

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Dec 19, 2025
@rustbot rustbot added the WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver) label Dec 19, 2025
@rustbot
Copy link
Collaborator

rustbot commented Dec 19, 2025

r? @SparrowLii

rustbot has assigned @SparrowLii.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rust-log-analyzer

This comment has been minimized.

@oli-obk oli-obk force-pushed the try_as_dyn_non_static branch 2 times, most recently from e7ef1ee to 29f1dba Compare December 19, 2025 16:44
Copy link
Contributor

@danielhenrymantilla danielhenrymantilla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some drive-by comments; but I'm not rustc/HIR-savy, so take these with a grain of salt 🙇

View changes since this review

assert!(std::any::try_as_dyn::<_, dyn Trait>(&()).is_some());
assert!(std::any::try_as_dyn::<_, dyn Trait>(&&[()]).is_some());
assert!(std::any::try_as_dyn::<_, dyn Trait>(&&()).is_some());
assert!(std::any::try_as_dyn::<_, dyn Trait>(&&42_u32).is_none());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is a "regression", right? With the old design we could be involving a &'static u32 which is both : 'static, and implementing Trait in the classical sense. Perhaps we'll want a try_as_dyn_static() function as well, for those interested in this use case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add this to the tracking issue

Comment on lines +89 to +92
if let TypingMode::Reflection = ecx.typing_mode()
&& !cx.is_fully_generic_for_reflection(impl_def_id)
{
return Err(NoSolution);
Copy link
Contributor

@danielhenrymantilla danielhenrymantilla Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is way over my head, lol, but just to sanity-check: could this branch be hoisted to the beginning of the function, as in, any impl that is not fully_generic_for_reflection(), when checking in Reflection mode, ought to result in Err(NoSolution), right? As in, it's not specific to this double-Negative branch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it's only relevant if the polarities match. Otherwise it's not a match anyway. So while it could be lifted to the front, it's not really necessary either. No strong opinion, but also easily changeable without affecting behavior

@BoxyUwU
Copy link
Member

BoxyUwU commented Dec 19, 2025

r? BoxyUwU

@rustbot rustbot assigned BoxyUwU and unassigned SparrowLii Dec 19, 2025
@theemathas

This comment has been minimized.

@theemathas

This comment has been minimized.

@theemathas

This comment has been minimized.

@BoxyUwU BoxyUwU added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Dec 30, 2025
@oli-obk oli-obk force-pushed the try_as_dyn_non_static branch from 29f1dba to fe33b0c Compare January 7, 2026 12:41
@rustbot

This comment has been minimized.

@oli-obk oli-obk force-pushed the try_as_dyn_non_static branch 3 times, most recently from dfa5c33 to 30f5641 Compare January 7, 2026 12:44

fn extend(a: &Payload) -> &'static Payload {
// TODO: should panic at the `unwrap` here
let b: &(dyn Trait + 'static) = try_as_dyn::<&Payload, dyn Trait + 'static>(&a).unwrap();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a solution for this yet. The easy hammer is to reject traits that have such methods. Nicer would be to figure out how to make a bound that ensures the dyn Trait's lifetimes are shorter than the arguments'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we check during mir_typeck that the source type outlives the lifetime of the trait object type?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah its not the intrinsic 🤔 yeah thats tricky

@rust-log-analyzer

This comment has been minimized.

@oli-obk
Copy link
Contributor Author

oli-obk commented Jan 13, 2026

The hacky solution is obviously not a general fix. But I think it's progress. As a next step I will add the input type as a generic parameter on TryAsDynCompat, at which point we should be able to enforce (in borrowck) that the input type outlives any lifetimes on the dyn Trait or its generic parameters. So if a generic parameter T has a 'static bound, it could be used as an input type for a try_as_dyn irrespective of the bounds on the dyn Trait. In the other direction, we will likely end up rejecting many traits that have generic parameters as the bounds are not something that can be written in Rust.

@theemathas

This comment was marked as resolved.

@rustbot

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@oli-obk oli-obk force-pushed the try_as_dyn_non_static branch from 544129f to c25c968 Compare February 26, 2026 10:12

fn impl_is_fully_generic_for_reflection(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
tcx.impl_trait_header(def_id).is_fully_generic_for_reflection()
&& tcx.explicit_predicates_of(def_id).is_fully_generic_for_reflection()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this mean we dont handle elaborated supertraits? e.g.

trait Trait: Super<'static> {}
trait Other<T> {}
impl<T: Trait> Other<T> for Foo {}

is the impl of Other considered to be fully generic for reflection

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this is probably fine. It can't cause the answer to "does Foo implement Other" to depend on lifetimes, since satisfying the impl block's where bounds is a necessary and sufficient condition for implementing the subtrait. If the fully generic where bounds didn't imply implementing the supertrait already, then we would have already errored in the impl block.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There could plausibly be some problems involving inductive cycles that don't go through an impl block for the supertrait, but I don't think that could cause trouble, given that try_as_dyn isn't evaluated in a generic context.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before the last commit

trait Trait: 'static {}
trait Other {}
struct Foo<T>(T);

impl Trait for () {}

// This impl should not be visible, as it has a `T: 'static` bound
impl<T: Trait> Other for Foo<T> {}

const _: () = {
    let foo = Foo(());
    assert!(try_as_dyn::<Foo<()>, dyn Other>(&foo).is_none());
};

was returning Some, so we definitely need to elaborate.

Similar to how we need to expand type aliases (and not just reject them like I do rn).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some back and forth with @theemathas I have come to the conclusion that we do not need to elaborate bounds. The fact that we restrict all impls that are part of figuring out a Some output of try_as_dyn means that we allow this fully generic impl, but then reject

impl Trait for &'static () {}

And you can't cheat and do sth weird like

impl<'a> Trait for &'a () {}

because of the trait Trait: 'static {}.


/// Allow simple where bounds like `T: Debug`, but prevent any kind of
/// outlives bounds or uses of generic parameters on the right hand side.
pub fn is_fully_generic_for_reflection(self) -> bool {
Copy link
Member

@BoxyUwU BoxyUwU Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should see how min specialization handles repeated parameters and reuse that logic ideally

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

min spec almost always compares two impls, which we could probably somewhat re-use (the second impl can just be the fake one by the trait which defines its default methods)

Also all the spec logic just errors, while want to silently return None (or Err in the future if we want).

So yea, we can share logic, but it needs a larger refactoring of the spec logic.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@theemathas
Copy link
Contributor

trait_info_of_trait_type_id can be given a TypeId for a trait object type that doesn't implement TryAsDynCompatible. It should probably be at least noted that there could be false positives or something.

@rust-bors

This comment has been minimized.

@theemathas
Copy link
Contributor

theemathas commented Mar 1, 2026

I haven't given this enough thought to be sure, but I suspect that we can allow all where Type: Trait bounds in fully-generic impls, as long as each individual bound uses each generic type parameter and each lifetime parameter only once.

Edit: I've thought about it more. I think that this works, except that we still need to keep prohibiting generics inside ClauseKind::{RegionOutlives, TypeOutlives, Projection} bounds.

@theemathas
Copy link
Contributor

theemathas commented Mar 1, 2026

The invariant is that each Type: Trait obligation we generate does not mention 'static, and does not use the same generic type parameter or lifetime parameter twice.

Edit: A Type: Trait bound, however, may mention the same bound (HRTB) lifetime any number of times.

@theemathas
Copy link
Contributor

theemathas commented Mar 2, 2026

This code feels really close to an unsoundness. It panics at run time at the unwrap, but I don't understand why. Could you explain how the trait solver manages to correctly figure out that the type doesn't implement the trait?

#![feature(try_as_dyn)]

use core::any::try_as_dyn;

trait HasAssoc<'a> {
    type Assoc;
}
struct Dummy;
impl<'a> HasAssoc<'a> for Dummy {
    // Changing this to &'a i64 makes the code not panic
    type Assoc = &'static i64;
}

trait Trait {}
impl Trait for i32 where for<'a> Dummy: HasAssoc<'a, Assoc = &'a i64> {}

fn main() {
    let x = 1i32;
    let _: &dyn Trait = try_as_dyn(&x).unwrap();
}

Edit: I think it's because the trait solver probably can distinguish bound lifetimes from each other and from 'static

@theemathas theemathas added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 2, 2026
@oli-obk oli-obk force-pushed the try_as_dyn_non_static branch from d08dd2f to c6f08ed Compare March 2, 2026 14:51
@rustbot
Copy link
Collaborator

rustbot commented Mar 2, 2026

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@oli-obk oli-obk force-pushed the try_as_dyn_non_static branch from c6f08ed to 1bb0baa Compare March 2, 2026 14:51
@rust-log-analyzer

This comment has been minimized.

@oli-obk oli-obk force-pushed the try_as_dyn_non_static branch from 1bb0baa to d2cc847 Compare March 2, 2026 15:41
@rust-log-analyzer
Copy link
Collaborator

The job aarch64-gnu-llvm-20-2 failed! Check out the build log: (web) (plain enhanced) (plain)

Click to see the possible cause of the failure (guessed by this bot)
/dev/sda15       98M  6.4M   92M   7% /boot/efi
tmpfs           1.6G   16K  1.6G   1% /run/user/1001
================================================================================

Sufficient disk space available (120307224KB >= 52428800KB). Skipping cleanup.
##[group]Run echo "[CI_PR_NUMBER=$num]"
echo "[CI_PR_NUMBER=$num]"
shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
---
set -ex

# Run a subset of tests. Used to run tests in parallel in multiple jobs.

# When this job partition is run as part of PR CI, skip tidy to allow revealing more failures. The
# dedicated `tidy` job failing won't block other PR CI jobs from completing, and so tidy failures
# shouldn't inhibit revealing other failures in PR CI jobs.
if [ "$PR_CI_JOB" == "1" ]; then
  echo "PR_CI_JOB set; skipping tidy"
  SKIP_TIDY="--skip tidy"
fi

../x.py --stage 2 test \
  ${SKIP_TIDY:+$SKIP_TIDY} \
  --skip compiler \
  --skip src
#!/bin/bash

set -ex

# Run a subset of tests. Used to run tests in parallel in multiple jobs.

# When this job partition is run as part of PR CI, skip tidy to allow revealing more failures. The
# dedicated `tidy` job failing won't block other PR CI jobs from completing, and so tidy failures
# shouldn't inhibit revealing other failures in PR CI jobs.
if [ "$PR_CI_JOB" == "1" ]; then
  echo "PR_CI_JOB set; skipping tidy"
  SKIP_TIDY="--skip tidy"
fi

../x.py --stage 2 test \
  ${SKIP_TIDY:+$SKIP_TIDY} \
  --skip tests \
  --skip coverage-map \
  --skip coverage-run \
  --skip library \
  --skip tidyselftest
---
Executing "/scripts/stage_2_test_set2.sh"
+ /scripts/stage_2_test_set2.sh
PR_CI_JOB set; skipping tidy
+ '[' 1 == 1 ']'
+ echo 'PR_CI_JOB set; skipping tidy'
+ SKIP_TIDY='--skip tidy'
+ ../x.py --stage 2 test --skip tidy --skip tests --skip coverage-map --skip coverage-run --skip library --skip tidyselftest
##[group]Building bootstrap
    Finished `dev` profile [unoptimized] target(s) in 0.04s
##[endgroup]
downloading https://static.rust-lang.org/dist/2026-01-21/rustfmt-nightly-aarch64-unknown-linux-gnu.tar.xz
---
[RUSTC-TIMING] allocbenches test:true 7.824
[RUSTC-TIMING] corebenches test:true 9.247
[RUSTC-TIMING] alloctests test:true 15.197
[RUSTC-TIMING] alloctests test:true 16.880
error[E0080]: evaluation panicked: assertion failed: TypeId::of::<Garlic>().trait_info_of::<dyn Blah + Send>().is_some()
##[error]  --> coretests/tests/mem/trait_info_of.rs:18:9
   |
18 |         assert!(TypeId::of::<Garlic>().trait_info_of::<dyn Blah + Send>().is_some());
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |         |
   |         evaluation of `mem::trait_info_of::test_implements_trait::{constant#0}` failed here
   |         in this macro invocation
   |
  --> library/core/src/macros/mod.rs:1687:4
   |
   = note: in this expansion of `assert!`

note: erroneous constant encountered
  --> coretests/tests/mem/trait_info_of.rs:16:5
   |
16 | /     const {
17 | |         assert!(TypeId::of::<Garlic>().trait_info_of::<dyn Blah>().is_some());
18 | |         assert!(TypeId::of::<Garlic>().trait_info_of::<dyn Blah + Send>().is_some());
19 | |         assert!(TypeId::of::<*const Box<Garlic>>().trait_info_of::<dyn Sync>().is_none());
20 | |         assert!(TypeId::of::<u8>().trait_info_of_trait_type_id(TypeId::of::<dyn Blah>()).is_none());
21 | |     }
   | |_____^

For more information about this error, try `rustc --explain E0080`.
[RUSTC-TIMING] coretests test:true 54.954
error: could not compile `coretests` (test "coretests") due to 1 previous error
env -u RUSTC_WRAPPER CARGO_ENCODED_RUSTDOCFLAGS="-Csymbol-mangling-version=v0\u{1f}-Zannotate-moves\u{1f}-Zrandomize-layout\u{1f}-Zunstable-options\u{1f}--check-cfg=cfg(bootstrap)\u{1f}-Wrustdoc::invalid_codeblock_attributes\u{1f}--crate-version\u{1f}1.96.0-nightly\t(b7dadd512\t2026-03-02)" CARGO_ENCODED_RUSTFLAGS="-Csymbol-mangling-version=v0\u{1f}-Zannotate-moves\u{1f}-Zrandomize-layout\u{1f}-Zunstable-options\u{1f}--check-cfg=cfg(bootstrap)\u{1f}-Zmacro-backtrace\u{1f}-Csplit-debuginfo=off\u{1f}-Clink-arg=-L/usr/lib/llvm-20/lib\u{1f}-Cllvm-args=-import-instr-limit=10\u{1f}-Clink-args=-Wl,-z,origin\u{1f}-Clink-args=-Wl,-rpath,$ORIGIN/../lib\u{1f}--cap-lints=allow\u{1f}--cfg\u{1f}randomized_layouts" RUSTC="/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-codegen/cg_clif/dist/rustc-clif" RUSTDOC="/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-codegen/cg_clif/dist/rustdoc-clif" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage0/bin/cargo" "test" "--manifest-path" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-codegen/cg_clif/build/sysroot_tests/Cargo.toml" "--target-dir" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-codegen/cg_clif/build/sysroot_tests_target" "--locked" "--target" "aarch64-unknown-linux-gnu" "-p" "coretests" "-p" "alloctests" "--tests" "--" "-q" exited with status ExitStatus(unix_wait_status(25856))
Bootstrap failed while executing `--stage 2 test --skip tidy --skip tests --skip coverage-map --skip coverage-run --skip library --skip tidyselftest`
Command `/checkout/obj/build/aarch64-unknown-linux-gnu/stage0/bin/cargo run -Zwarnings --target aarch64-unknown-linux-gnu -Zbinary-dep-depinfo -j 4 -Zroot-dir=/checkout --locked --color=always --profile=release --manifest-path /checkout/compiler/rustc_codegen_cranelift/build_system/Cargo.toml -- test --download-dir /checkout/obj/build/cg_clif_download --out-dir /checkout/obj/build/aarch64-unknown-linux-gnu/stage2-codegen/cg_clif --no-unstable-features --use-backend cranelift --sysroot llvm --skip-test testsuite.extended_sysroot [workdir=/checkout/compiler/rustc_codegen_cranelift]` failed with exit code 1
Created at: src/bootstrap/src/core/build_steps/test.rs:3896:25
Executed at: src/bootstrap/src/core/build_steps/test.rs:3941:26

Command has failed. Rerun with -v to see more details.
Build completed unsuccessfully in 0:26:26
  local time: Mon Mar  2 16:11:54 UTC 2026
  network time: Mon, 02 Mar 2026 16:11:55 GMT
##[error]Process completed with exit code 1.
##[group]Run echo "disk usage:"

@theemathas
Copy link
Contributor

theemathas commented Mar 3, 2026

Here are the current rules for what counts as a "fully generic impl". I think they should be documented in the public API doc comments.


For the purposes of defining fully generic impls, we'll define the following:

  • A "lifetime-infected parameter" is a generic type parameter T or lifetime parameter 'a declared in the impl<....> angle brackets.
    • This does not include const generics, and does not include for<'a> lifetimes.
  • A "where bound" is an explicitly written trait or lifetime bound (Type: Trait, Type: 'a, or 'a: 'b) in the trait header, including ones written in the impl<....> angle brackets without the where keyword.
    • This does not include any additional implicit bounds that are implied by Type: Trait bounds, such as via supertraits, associated type bounds in the trait definition, or well-formedness of types.
    • A bound such as Type: Trait1 + Trait2 is considered to be two separate where bounds: Type: Trait1 and Type: Trait2.
    • A bound such as Type: Trait<Assoc: Trait2> is desugared into two separate where bounds: Type: Trait and <Type as Trait>::Assoc: Trait2. (Note that the latter where bound is prohibited in fully generic impls, due to having an associated type projection.)

An impl block is a fully generic impl iff all the following conditions apply:

  • The impl block is a handwritten impl, as opposed to a type implementing a trait automatically by the compiler.
    • Examples of types automatically implementing traits include: types automatically implementing auto traits, dyn Trait implementing Trait, and closures implementing Fn traits.
    • derives are considered handwritten impls.
  • 'static is not mentioned anywhere in the impl block header.
  • Associated type projections (<Type as Trait>::Assoc) are not mentioned anywhere in the impl block header.
  • Each outlives where bound (Type: 'a and 'a: 'b) does not mention lifetime-infected parameters.
  • Each trait where bound with an associated type equality (Type: Trait<Assoc = Type2>) does not mention lifetime-infected parameters.
  • Each lifetime-infected parameter is mentioned at most once in the Self type and the implemented trait's generic parameters, combined.
  • Each trait where bound (Type: Trait) does not mention lifetime-infected parameters on the right hand side.
  • Each trait where bound (Type: Trait) either has a generic type parameter on its own on the left hand side (T: Trait), or does not mention lifetime-infected parameters on the left hand side.

My proposed rule change replaces the last two bullet points with the following, which I think makes the rules easier to understand:

  • Each individual trait where bound (Type: Trait) mentions each lifetime-infected parameter at most once. (Mentioning a lifetime-infected parameter in multiple where bounds is allowed.)

Edit: As per @danielhenrymantilla's suggestion below, I think that restrictions involving lifetime-infected parameters in where Type: Trait bounds can be removed completely.

@theemathas
Copy link
Contributor

I would like to rename "fully generic impl" to "lifetime-independent impl".

@danielhenrymantilla
Copy link
Contributor

💯 I was gonna suggest just that, @theemathas: a lifetime-independent, or rather, lifetime-agnostic, impl. If using "fully", I think the term used historically is that of it being a blanket impl, so "fully blanket over lifetimes".

@danielhenrymantilla
Copy link
Contributor

danielhenrymantilla commented Mar 3, 2026

We could also try and expand on "exploit" scenarios for each of these rules, should it be missing, that is, some form of rationale as to why each of these rules is (plausibly) necessary (the whole other question being whether these rules are sufficient for soundness, which currently stems from "we haven't been able to fathom other ways to amount to lifetime discrimination").

Plausible reasons for the necessity of these rules

Important

The main problem of try_as_dyn and specialization is the compiler's inability, while trait-checking, to distinguish/discriminate between any two given lifetimes1.

  • 'static is not mentioned anywhere in the impl block header.

    The most obvious one: if you have impl IsStatic for &'static str, then determining whether &'? str : IsStatic does hold amounts to discriminating '? : 'static.

  • Each outlives where bound (Type: 'a and 'a: 'b) does not mention lifetime-infected parameters.

    We can create lifetime discrimination this way. For instance, given impl<'a, 'b> Outlives<'a> for &'b str where 'b : 'a {}, Outlives<'static> amounts to IsStatic from previous bullet.

  • Each lifetime-infected parameter is mentioned at most once in the Self type and the implemented trait's generic parameters, combined.

    Repetition of a parameter entails equality of those two use-sites; in lifetime-terms, this would be a double 'a : 'b / 'b : 'a clause, for instance.
    Follow-up from the previous example: impl<'a> Uses<'a> for &'a str {}, and check whether &'? str : Uses<'static>.

  • Each individual trait where bound (Type: Trait) mentions each lifetime-infected parameter at most once.
    (Mentioning a lifetime-infected parameter in multiple where bounds is allowed.)

    Looking at the previous rules, which focuses on Self : …, this is just observing that shifting the requirements to other parameters within where clauses [ought to] boil down to the same set of issues.

    TODO: add a proper counter-example (cc @theemathas, do you have an easy one in mind?)

    EDIT: I am starting to think this is unnecessarily restrictive: we should be able to loosen it up somehow. Repetition only in where clauses seems fine.

  • The impl block is a handwritten impl, as opposed to a type implementing a trait automatically by the compiler (such as auto-traits, dyn Bounds… : Bounds…, and closures)
    The reason for this is that some such auto-generated impls come with hidden bounds or whatnot, which run afoul of the previous rules, whilst also being extremely challenging for the current compiler logic to know of such bounds.
    IIUC, this restriction could be lifted in the future should the compiler logic be better at spotting these hidden bounds, when present.

    • One contrived such example being the case of dyn 'u + for<'a> Outlives<'a>, where the compiler-generated impl for it of Outlives is: impl<'b, 'u> Outlives<'b> for dyn 'u + for<'a> Outlives<'a> where 'b : 'u {} which violates the "'a: 'b not to mention lt-infected params" rule, whilst also being hard to detect in current compiler logic.
  • Associated type projections (<Type as Trait>::Assoc) are not mentioned anywhere in the impl block header.

    Checking whether &'? str: Trait discriminates '? against 'static in the following scenario, for instance:

    trait Assoc { type StaticStr; }
    impl Assoc for () { type StaticStr = &'static str; }
    impl Trait for <() as Assoc>::StaticStr {}
  • Each trait where bound with an associated type equality (Type: Trait<Assoc = Type2>) does not mention lifetime-infected parameters.

    Checking whether Option<&'? str>: IntoIterator<Item = &'static str> holds discriminates '? against 'static.

Footnotes

  1. the compiler cannot branch on whether "'a : 'b holds": for soundness, it can either choose not to know the answer, or assume that it holds and produce an obligation for the borrow-checker which shall "assert this" (making compilation fail in a fatal manner if not). Most usages of Rust lie in the latter category (typical where clauses anywhere), whilst specialization/try_as_dyn() wants to support fallibility of the operation (i.e., being queried on a type not fulfilling the predicate without causing a compilation error). This rules out the latter, resulting in the need for the former, i.e., for the try_as_dyn() attempt to unconditionally "fail" with None.

@theemathas
Copy link
Contributor

theemathas commented Mar 3, 2026

I agree with @danielhenrymantilla. The repetition restriction on where Type: Trait bounds can be removed.

The problem with repeating lifetime-infected parameters is that, when figuring out the parameter substitutions for any given impl, we need to unify the "user-specified types" with the Self type and the trait's generic parameters. If there are repetitions (within the Self type combined with the trait's generics), then this process can cause two different lifetimes to be unified into the same lifetime-infected parameter, which is a problem.

In contrast, repeating lifetime-infected parameters in a where Type: Trait bound can only cause a single lifetime to be unified into two different lifetime-infected parameters, which doesn't cause any issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants