Skip to content

"Unused" associated type bounds in dyn no longer parameterize the type #140645

Closed

Description

@QuineDot

Code

I tried this code:

#![allow(unused_associated_type_bounds)]

pub trait Other {}
pub trait Trait {
    type Assoc where Self: Sized;
}

impl Other for dyn Trait {}
impl Other for dyn Trait<Assoc = ()> {}
impl<T> dyn Trait<Assoc = T> {}

I expected to see this happen: successful compilation.

Instead, this happened:

error[E0119]: conflicting implementations of trait `Other` for type `(dyn Trait + 'static)`
 --> src/lib.rs:9:1
  |
8 | impl Other for dyn Trait {}
  | ------------------------ first implementation here
9 | impl Other for dyn Trait<Assoc = ()> {}
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `(dyn Trait + 'static)`

error[E0207]: the type parameter `T` is not constrained by the impl trait, self type, or predicates
  --> src/lib.rs:10:6
   |
10 | impl<T> dyn Trait<Assoc = T> {}
   |      ^ unconstrained type parameter

Some errors have detailed explanations: E0119, E0207.
For more information about an error, try `rustc --explain E0119`.

Version it worked on

It most recently worked on: Rust 1.86.0

Version with regression

Playground: Beta version: 1.87.0-beta.8 (2025-05-03 973ec11)

More notes

CC #125560

Before Beta, dyn Trait and dyn Trait<Assoc = ()> were different types. Now they are the same type.

Here's a more complete example that makes practical (albeit contrived) use of distinct dyn Trait<Assoc = ...> types.

Considering the release notes, #136458 may be the cause (but there's no discussion about this change in particular). Assuming this is the case and the use case that broke is considered unimportant, this aspect of the change could at least be mentioned in the release notes.

Incidentally, this also means dyn Trait<Assoc = &'can_be_non_static str> meets a 'static bound now. Hopefully irrelevant, but it does seem like an exception to outlive bounds being syntactical.

@rustbot modify labels: +regression-from-stable-to-beta, -regression-untriaged

Activity

  1. added
    C-bugCategory: This is a bug.
    regression-untriagedUntriaged performance or correctness regression.
    on May 4, 2025
  2. added
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    and removed
    regression-untriagedUntriaged performance or correctness regression.
    on May 4, 2025
  3. added
    T-typesRelevant to the types team, which will review and decide on the PR/issue.
    A-dyn-traitArea: trait objects, vtable layout
    on May 4, 2025
  4. compiler-errors commented on May 4, 2025

    @compiler-errors
    Contributor

    I don't believe that this behavioral change is worth concern. I'll discuss this with @lcnr tomorrow since we worked together on these dyn issues, b/c we have several options here:

    1. Do nothing.
    2. Bump unused_associated_type_bounds up to a future-compat warning.
    3. Adjust the assoc-type-bound lowering algorithm in Do not deduplicate list of associated types provided by dyn principal #136458 to start lowering impossible associated type bounds again.
    4. Fully revert Do not deduplicate list of associated types provided by dyn principal #136458 -- this would be the worst option since Do not deduplicate list of associated types provided by dyn principal #136458 fixes a much more legitimate class of issues with dyn types than this IMO.

    I kinda prefer (0.) or (1.) here, personally. I don't see any problem with writing these bounds, but we could probably be clearer in explaining to users that we now don't consider dyn Trait<NeedsSized = T> and dyn Trait<NeedsSized = U> as distinct types if T != U. (2.) would be annoying but doable.


    Incidentally, this also means dyn Trait<Assoc = &'can_be_non_static str> meets a 'static bound now. [...] Hopefully irrelevant, but it does seem like an exception to outlive bounds being syntactical.

    Side-note, it's not exactly true to say that dyn Trait<Assoc = T> now implies that T satisfies the item bounds of Assoc due to this change; we're simply not enforcing the item bounds since we ignore the user-written associated type bound since dyn Trait is definitely unsized.

    This is IMO irrelevant because that bound is never relevant for a dyn type. It's as if we were saying "if dyn Sized: Sized, then require &'a str: 'static". It's got a false premise.

  5. compiler-errors commented on May 4, 2025

    @compiler-errors
    Contributor

    The fact that impossible associated type bounds used to constrain type parameters to me is interesting at best, but is technically an observable change. I think this warrants an FCP, and I'll open up one tomorrow when I can write up some coherent justification for why this behavioral change is at best desirable and at worst worth ignoring.

  6. QuineDot commented on May 5, 2025

    @QuineDot
    Author

    Let me call out one particular part of my more complete playground:

            // SAFETY:
            // - `BaseType: Use<Assoc = Assoc>` is guaranteed by unsizing coercion

    Or in other words: "Box<T> coerces to Box<dyn Use<Assoc = Assoc> + 't>" no longer implies T: Use<Assoc = Assoc> + 't, because now sometimes Box<dyn Use<Assoc = Assoc> + 't> is synonymous with Box<dyn Use + 't>.

    With that in mind, here's a version of the playground where code that used to be sound by relying on that implication is no longer sound, but still compiles on Beta. Even ignoring soundness, the code suddenly becomes very misleading IMO. I think assuming the implication holds is reasonable, but on Beta it's no longer always true.

    Even if (2) and (3) won't happen, perhaps this is relevant re: unused_associated_type_bounds becoming a FCW.

    If do-nothing bounds are eventually disallowed, the implication that "what you can (syntactically) coerce to reflects the capability of the coercee" will become true again.

    Side-note, it's not exactly true to say that dyn Trait<Assoc = T> now implies that T satisfies the item bounds of Assoc due to this change; we're simply not enforcing the item bounds since we ignore the user-written associated type bound since dyn Trait is definitely unsized.

    Under a mental model of "dyn Trait<..> + 'a means ErasedType: Trait<..> + 'a", the item bound is a restriction on the erased type, which was not unsized. That take correlates to the now-violated assumption above.

    Anyway, I don't care strongly enough to argue hard for (2) or (3), and I'm not trying to do that. The lost ability is neat but I also recognize the change is cleaner. The code I wrote using the lost ability were POCs, not load bearing. And I only pointed out the 'static thing in case there was some soundness consideration I didn't know about.

    But I do think "do-nothing item bounds" are quite misleading without the invariant, and should be loud about it. I think one can see how previously_sound_crate could be convincing in a code review, say.

  7. added and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on May 5, 2025
  8. compiler-errors commented on May 5, 2025

    @compiler-errors
    Contributor

    I changed my mind and implemented #140684.

  9. added 2 commits that reference this issue on May 8, 2025
    a054be7
    f52c4f9
  10. added a commit that references this issue on May 9, 2025
    9ceb477
  11. removed
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on May 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-dyn-traitArea: trait objects, vtable layoutC-bugCategory: This is a bug.L-unused_associated_type_boundsLint: unused_associated_type_boundsT-typesRelevant to the types team, which will review and decide on the PR/issue.regression-from-stable-to-betaPerformance or correctness regression from stable to beta.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions