Skip to content

Identify which tuple item fails to implement a trait when using fake variadics #141258

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
alice-i-cecile opened this issue May 19, 2025 · 6 comments
Labels
A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@alice-i-cecile
Copy link

alice-i-cecile commented May 19, 2025

Code

trait WorksOnDefault {
    fn do_something() {}
}

impl<T: Default> WorksOnDefault for T {}

fn main() {
    let _success = <(i32, u32, String)>::do_something();
    let _failure = <(i32, &u32, String)>::do_something();
}

Current output

error[E0599]: the function or associated item `do_something` exists for tuple `(i32, &u32, String)`, but its trait bounds were not satisfied
  --> examples/hello_world.rs:11:28
   |
11 |     <(i32, &u32, String)>::do_something();
   |                            ^^^^^^^^^^^^ function or associated item cannot be called on `(i32, &u32, String)` due to unsatisfied trait bounds
   |
note: the following trait bounds were not satisfied:
      `&(i32, &u32, String): Default`
      `&mut (i32, &u32, String): Default`
      `(i32, &u32, String): Default`

Desired output

No method `works_on_default` found for <(i32, &u32, String)>`

The method `do_something` is available via the `WorksOnDefault` trait for any type T where T implements the `Default` trait.

If every member of the tuple (i32, &u32, String) implemented the `Default` trait, the `Default` trait would be implemented for the tuple.

However, `&u32` does not implement the `Default` trait.

note: did you mean `u32`, which implements the `Default` trait?

Rationale and extra context

This is an extremely common pattern for Bevy, primarily in our system and bundle code patterns. The poor error messages are one of our largest UX / teaching problems,. See #89681 for that specific issue, which could be fixed by more general improvements here.

This pattern occurs all over the place: the minimal example here uses Default, which relies on the same flavor of fake variadics for tuples where all tuple elements implement a given trait. Bevy's Bundle tuples matches this form exactly, while our Systems use the same sort of trick but with functions, where each parameter must implement SystemParam.

Other cases

fn main(){
    let _successful_default = <(i32, u32, String)>::default();
    let _failed_default = <(i32, &u32, String)>::default();
}

The error output when calling methods from the trait directly correctly identifies the problematic variant:

error[E0599]: the function or associated item `default` exists for tuple `(i32, &u32, String)`, but its trait bounds were not satisfied
 --> src/main.rs:3:50
  |
3 |     let _failed_default = <(i32, &u32, String)>::default();
  |                                                  ^^^^^^^ function or associated item cannot be called on `(i32, &u32, String)` due to unsatisfied trait bounds
  |
  = note: the following trait bounds were not satisfied:
          `&u32: Default`
          which is required by `(i32, &u32, String): Default`
``

Rust Version

rustc 1.87.0 (17067e9ac 2025-05-09)
binary: rustc
commit-hash: 17067e9ac6d7ecb70e50f92c1944e545188d2359
commit-date: 2025-05-09
host: x86_64-unknown-linux-gnu
release: 1.87.0
LLVM version: 20.1.1

Anything else?

Discussed with @estebank and @joshtriplett at RustWeek 2025, where I was encouraged to file an issue so we can improve diagnostics without waiting on full variadics!

@alice-i-cecile alice-i-cecile added A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels May 19, 2025
@joshtriplett
Copy link
Member

👍 for attempting to detect and diagnose this case, when looking for a trait impl for a tuple.

@estebank
Copy link
Contributor

estebank commented Jun 4, 2025

Any thoughts on this output?

Image
text version

error[E0599]: the function or associated item `do_something` exists for tuple `(i32, &u32, String)`, but its trait bounds were not satisfied
   --> traits-on-tuples.rs:24:43
    |
24  |     let _failure = <(i32, &u32, String)>::do_something(); //~ ERROR E0599
    |                                           ^^^^^^^^^^^^ function or associated item cannot be called on `(i32, &u32, String)` due to unsatisfied trait bounds
    |
note: the following trait bounds were not satisfied:
      `&(i32, &u32, String): Default`
      `&mut (i32, &u32, String): Default`
      `(i32, &u32, String): Default`
   --> traits-on-tuples.rs:5:9
    |
5   | impl<T: Default> WorksOnDefault for T {}
    |         ^^^^^^^  --------------     -
    |         |
    |         unsatisfied trait bound introduced here
note: `Default` is implemented for `(i32, u32, String)` but not for `(i32, &u32, String)`
   --> /home/gh-estebank/rust/library/core/src/tuple.rs:242:1
    |
242 | tuple_impls!(E D C B A Z Y X W V U T);
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `tuple_impls` (in Nightly builds, run with -Z macro-backtrace for more info)

As a bonus, it works on things other than (...): Default:

Image

The logic right now just retries with a tuple without any borrows, but the logic could be made more complex to try different combinations ("check every combination of removing borrows from every type and adding borrows"), but I'd like to leave that as a follow up.

@alice-i-cecile
Copy link
Author

I quite like the output! Agreed on leaving more complex retry logic for later: the key thing here is showcasing which element didn't meet the trait bounds.

@weiznich
Copy link
Contributor

weiznich commented Jun 5, 2025

This looks great.

Nevertheless I would like to raise a few points to prevent future problems. @estebank Mentions that the logic could in the future retry with different combinations. While that would be really useful, it is likely also very important to be carefully with which combinations should be retried. For example diesel has this 128-columns-table feature, which implements a number of traits for tuples with up to 128 elements. If you retry with all possible combinations for such a tuple you will certainly run into performance problems just due to the combinatorical complexity due to the number of elements. So maybe there should be a hard limit for how many variants the compiler tries before giving up.

The other thing I want to mention in this context is yet another idea that might help to improve error messages around such tuple impls significantly. At least in diesel (and I assume in bevy) as well there are often trait impls like impl Queryable<(Some, Tuple, Of, Types), Whatever> for (Other, Tuple, Of, Types) which are essentially only implemented if both tuples match in size (+ Each of the tuple elements implement an trait based on each other). It happens from time to time that a user manages to skew up the size of one tuple for whatever reason so that they have different sizes. For such cases it would be really helpful to just point out: This trait is only implemented if the tuple sizes match, but you got x elements there an y elements there. If that's helpful I can try to provide a minimal example for such an error case.

@estebank
Copy link
Contributor

estebank commented Jun 9, 2025

@weiznich very important points. Yes, we should have a limit on how many things we try. I think an example of the second case would be useful and should be handled separately.

@weiznich
Copy link
Contributor

I've put together a minimal example for the second case here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=6cc46789f0f7bd40298c7ca91f91575a

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jun 12, 2025
Detect method not being present that is present in other tuple types

When a method is not present because of a trait bound not being met, and that trait bound is on a tuple, we check if making the tuple have no borrowed types makes the method to be found and highlight it if it does. This is a common problem for Bevy in particular and ORMs in general.

<img width="1166" alt="Screenshot 2025-06-04 at 10 38 24 AM" src="https://github.com/user-attachments/assets/d257c9ea-c2d7-42e7-8473-8b93aa54b8e0" />

Address rust-lang#141258. I believe that more combination of cases in the tuple types should be handled (like adding borrows and checking when a specific type needs to not be a borrow while the rest stay the same), but for now this handles the most common case.
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jun 12, 2025
Detect method not being present that is present in other tuple types

When a method is not present because of a trait bound not being met, and that trait bound is on a tuple, we check if making the tuple have no borrowed types makes the method to be found and highlight it if it does. This is a common problem for Bevy in particular and ORMs in general.

<img width="1166" alt="Screenshot 2025-06-04 at 10 38 24 AM" src="https://github.com/user-attachments/assets/d257c9ea-c2d7-42e7-8473-8b93aa54b8e0" />

Address rust-lang#141258. I believe that more combination of cases in the tuple types should be handled (like adding borrows and checking when a specific type needs to not be a borrow while the rest stay the same), but for now this handles the most common case.
rust-timer added a commit that referenced this issue Jun 13, 2025
Rollup merge of #142034 - estebank:issue-141258, r=davidtwco

Detect method not being present that is present in other tuple types

When a method is not present because of a trait bound not being met, and that trait bound is on a tuple, we check if making the tuple have no borrowed types makes the method to be found and highlight it if it does. This is a common problem for Bevy in particular and ORMs in general.

<img width="1166" alt="Screenshot 2025-06-04 at 10 38 24 AM" src="https://github.com/user-attachments/assets/d257c9ea-c2d7-42e7-8473-8b93aa54b8e0" />

Address #141258. I believe that more combination of cases in the tuple types should be handled (like adding borrows and checking when a specific type needs to not be a borrow while the rest stay the same), but for now this handles the most common case.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

4 participants