Description
I have here a collection of scenarios where the compiler fails to produce a helpful error message in response to a missing auto trait implementation. All examples are done on 1.58.0-nightly
.
Why do I care?
This issue cost me the entirety of an afternoon as I attempted to track down the cause of a very vague error. I've provided a minimal repro of that particular scenario in Example 4. Figuring out that it was due to a missing auto trait implementation was... not fun.
The root of the problem is that a type only a receives an auto trait implementation if the entire recursive closure of types that make it up implements the trait. If there is some unknown (to the user) negative impl somewhere deep inside that recursive closure of types, or (as was the case for me) there is some Box<T: !Sized>
1 hidden somewhere in there, then the compiler needs to point out the offending type to the user, or otherwise leave them baffled as to what's going on.
Example 1: Good
Below is an example where the compiler does give a diagnostic that I find satisfactory:
Code
#![feature(auto_traits)]
auto trait Something {}
fn destroy<T: Something>(_: T) {
println!("Destroyed something!");
}
struct Stringy(Box<dyn ToString>);
fn main() {
destroy("hello");
destroy(Stringy(Box::new("hello")));
}
The compiler correctly identifies the offending type, dyn ToString
:
Compile error
error[E0277]: the trait bound `(dyn ToString + 'static): Something` is not satisfied in `Stringy`
--> src/main.rs:16:13
|
16 | destroy(Stringy(Box::new("hello")));
| ------- ^^^^^^^^^^^^^^^^^^^^^^^^^^ within `Stringy`, the trait `Something` is not implemented for `(dyn ToString + 'static)`
| |
| required by a bound introduced by this call
|
= note: required because it appears within the type `*const (dyn ToString + 'static)`
= note: required because it appears within the type `Unique<(dyn ToString + 'static)>`
= note: required because it appears within the type `Box<(dyn ToString + 'static)>`
note: required because it appears within the type `Stringy`
--> src/main.rs:12:8
|
12 | struct Stringy(Box<dyn ToString>);
| ^^^^^^^
note: required by a bound in `destroy`
--> src/main.rs:5:15
|
5 | fn destroy<T: Something>(_: T) {
| ^^^^^^^^^ required by this bound in `destroy`
Example 2: What if it was a trait?
This example is like Example 1 but I've turned the destroy
function into a trait, Destroy
:
Code
#![feature(auto_traits)]
auto trait Something {}
trait Destroy {
fn destroy(self);
}
impl<T: Something> Destroy for T {
fn destroy(self) {
println!("Destroyed something!");
}
}
struct Stringy(Box<dyn ToString>);
fn main() {
"hello".destroy();
Stringy(Box::new("hello")).destroy();
}
With this little change, suddenly the compiler has lost all context regarding dyn ToString
:
Compile error
error[E0599]: the method `destroy` exists for struct `Stringy`, but its trait bounds were not satisfied
--> src/main.rs:25:32
|
21 | struct Stringy(Box<dyn ToString>);
| ----------------------------------
| |
| method `destroy` not found for this
| doesn't satisfy `Stringy: Destroy`
| doesn't satisfy `Stringy: Something`
...
25 | Stringy(Box::new("hello")).destroy();
| ^^^^^^^ method cannot be called on `Stringy` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`Stringy: Something`
which is required by `Stringy: Destroy`
`&Stringy: Something`
which is required by `&Stringy: Destroy`
`&mut Stringy: Something`
which is required by `&mut Stringy: Destroy`
note: the following trait must be implemented
--> src/main.rs:5:1
|
5 | auto trait Something {}
| ^^^^^^^^^^^^^^^^^^^^^^^
Example 3: With specialization
This expands on Example 2 by adding a specialized implementation of Destroy
using feature(min_specialization)
:
Code
#![feature(auto_traits)]
#![feature(min_specialization)]
#![feature(rustc_attrs)]
auto trait Something {}
trait Destroy {
fn destroy(self);
}
impl<T: Something> Destroy for T {
default fn destroy(self) {
println!("Destroyed something!");
}
}
#[rustc_specialization_trait]
trait Special {}
impl<T: Something + Special> Destroy for T {
fn destroy(self) {
println!("Destroyed something special!");
}
}
struct SpecialSomething(Box<dyn ToString>);
impl Special for SpecialSomething {}
fn main() {
"hello".destroy();
SpecialSomething(Box::new("hello")).destroy();
}
The error message is even less helpful!
Compile error
error[E0599]: no method named `destroy` found for struct `SpecialSomething` in the current scope
--> src/main.rs:36:41
|
31 | struct SpecialSomething(Box<dyn ToString>);
| ------------------------------------------- method `destroy` not found for this
...
36 | SpecialSomething(Box::new("hello")).destroy();
| ^^^^^^^ method not found in `SpecialSomething`
|
= help: items from traits can only be used if the trait is implemented and in scope
note: `Destroy` defines an item `destroy`, perhaps you need to implement it
--> src/main.rs:7:1
|
7 | trait Destroy {
| ^^^^^^^^^^^^^
Example 4: Specialization and Result
This is a representative example of what I was working on when I encountered these unhelpful errors:
My project involves modifying core::result::Result
to have specialized FromResidual
behavior for error types that implement a certain trait. I then have a wrapper type in "user space" that wraps some arbitrary error type and implements the specialization trait.
To enable ?
-conversion from one wrapper to another, I provide a blanket From
impl. I utilize auto traits and negative impls to ensure that it does not collide with the blanket From<T> for T
from the standard library.
If the wrapped error type does not receive the NotSame
auto trait, then the blanket From
impl fails to apply to it, which in turn makes the FromResidual
impl fail to apply, and I get the error below:
Compile error
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> src/main.rs:112:13
|
111 | / fn foo() -> Result<(), Special<ErrorA>> {
112 | | Ok(bar()?)
| | ^ cannot use the `?` operator in a function that returns `Result<(), Special<ErrorA>>`
113 | | }
| |_- this function should return `Result` or `Option` to accept `?`
|
= help: the trait `FromResidual<Result<Infallible, Special<ErrorB>>>` is not implemented for `Result<(), Special<ErrorA>>`
note: required by `from_residual`
This error message is even less helpful than the last, as it doesn't even mention any trait or type that is remotely related to my actual test code!
A few things seem clear here:
- The act of adding a specialized trait impl greatly degrades the compiler's ability to forward information about unimplemented auto traits.
- Even without specialization, the error messaging can be subpar.
I'd be happy to take a stab at improving things here, but I do not know what part of the compiler is responsible for doing this sort of inference (if that's even the correct term to use).
cc #13231 #31844 #68970
maybe also cc #84277
Footnotes
-
I will say that it's remarkably unintuitive that non-sized types do not receive auto traits by default. I only ended up learning this when I stumbled across this code. There is no mention of this peculiarity in the Unstable Book. ↩