Skip to content
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

Improve diagnostic for casting T: ?Sized + Trait to dyn Trait #88141

Open
Manishearth opened this issue Aug 18, 2021 · 3 comments
Open

Improve diagnostic for casting T: ?Sized + Trait to dyn Trait #88141

Manishearth opened this issue Aug 18, 2021 · 3 comments
Labels
A-diagnostics Area: Messages for errors, warnings, and lints C-enhancement Category: An issue proposing an enhancement or a PR with one. D-confusing Diagnostics: Confusing error or lint that should be reworked. D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@Manishearth
Copy link
Member

Given the following code: (playpen)

pub trait Trait {}

fn convert<T: Trait + ?Sized>(b: Box<T>) -> Box<dyn Trait> {
    b
}

The current output is:

error[E0277]: the size for values of type `T` cannot be known at compilation time
 --> src/main.rs:4:5
  |
3 | fn convert<T: Trait + ?Sized>(b: Box<T>) -> Box<dyn Trait> {
  |            - this type parameter needs to be `std::marker::Sized`
4 |     b
  |     ^ doesn't have a size known at compile-time
  |
  = note: required for the cast to the object type `dyn Trait`

This output is correct, however it is not quite clear in the standard mental model people have. In the standard mental model of ?Sized, in this context people will think T: Trait + ?Sized means T can be dyn Trait, which should obviously be castable to dyn Trait when behind a Box. So the error feels incorrect.

The reason the error is correct is that T could be a different unsized type that implements the trait, for example [u8] or dyn OtherTrait. In such a case the cast to dyn Trait would require some kind of double-DST, which doesn't exist.

I'm not quite sure how but it would be nice if this error could make this clearer.

In this specific case, if convert were a trait method, the solution would to have an impl for Box<T: Sized> and one for Box<dyn Trait> . Unsure if there's a good way to indicate that, but that's not as necessary as making the error clearer about why T: Trait + ?Sized can't be cast to dyn Trait

@Manishearth Manishearth 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 Aug 18, 2021
@inquisitivecrystal inquisitivecrystal added D-confusing Diagnostics: Confusing error or lint that should be reworked. D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. C-enhancement Category: An issue proposing an enhancement or a PR with one. labels Aug 18, 2021
@QuineDot
Copy link

QuineDot commented Sep 3, 2021

One possible update now:

error[E0277]: the size for values of type `T` cannot be known at compilation time
 --> src/main.rs:4:5
  |
3 | fn convert<T: Trait + ?Sized>(b: Box<T>) -> Box<dyn Trait> {
  |            - this type parameter needs to be `std::marker::Sized`
4 |     b
  |     ^ doesn't have a size known at compile-time
  |
  = note: only pointee types that do not have metadata can be cast to the object type `dyn Trait`
          as casting to a pointer to `dyn Trait` requires adding metadata in the form of a vtable
  = note: currently, `Sized` types are exactly the pointee types that do not have metadata;
          types which are not `Sized` have their own metadata such as slice lengths or vtables

This isn't completely satisfying though, as Box<dyn Trait> can be cast to Box<dyn Trait>. The situation will be exacerbated after RFC 2580 stabilizes, as any of T: Trait + Sized or T: Trait + Pointee<Metadata=()> or T: Trait + Pointee<Metadata=DynMetadata<Trait>> would presumably work.

If Unsized lands though, either before or after RFC 2580, it could be more like

error[E1337]: `T` does not satisfy `Unsize<dyn Trait>`
 --> src/main.rs:4:5
  |
3 | fn convert<T: Trait + ?Sized>(b: Box<T>) -> Box<dyn Trait> {
  |            - this type parameter needs to be `Unsize<dyn Trait>`
4 |     b
  |     ^ isn't known to satisfy `Unsize<dyn Trait>` at compile-time
  |
  = note: only types that satisfy `Unsize<dyn Trait>` can be cast to the object type `dyn Trait`
  = hint: add an `Unsize<dyn Trait>` bound:
...

@estebank
Copy link
Contributor

estebank commented Aug 4, 2023

Current output:

error[E0277]: the size for values of type `T` cannot be known at compilation time
 --> src/main.rs:4:5
  |
3 | fn convert<T: Trait + ?Sized>(b: Box<T>) -> Box<dyn Trait> {
  |            - this type parameter needs to be `std::marker::Sized`
4 |     b
  |     ^ doesn't have a size known at compile-time
  |
  = note: required for the cast from `Box<T>` to `Box<(dyn Trait + 'static)>`
help: consider removing the `?Sized` bound to make the type parameter `Sized`
  |
3 - fn convert<T: Trait + ?Sized>(b: Box<T>) -> Box<dyn Trait> {
3 + fn convert<T: Trait>(b: Box<T>) -> Box<dyn Trait> {
  |

@Zoybean
Copy link

Zoybean commented May 17, 2024

I hit this today when trying to cast Box<[T]> to Box<dyn Trait>. One thing I noticed was that the rustc help for the relevant error code, E0277, is technically desriptive of the problem but is possibly misleading in context. In my case, I only realised it was applicable while I was typing out this comment 😅).

rustc --explain E0277 output:

You tried to use a type which doesn't implement some trait in a place which
expected that trait.

Erroneous code example:

// here we declare the Foo trait with a bar method
trait Foo {
    fn bar(&self);
}

// we now declare a function which takes an object implementing the Foo trait
fn some_func<T: Foo>(foo: T) {
    foo.bar();
}

fn main() {
    // we now call the method with the i32 type, which doesn't implement
    // the Foo trait
    some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied
}

In order to fix this error, verify that the type you're using does implement
the trait. Example:
...

I suppose this is technically true, the relevant trait is Sized, but given the context of casting to a trait object, the error seems misleading, as the trait in my mind was my Trait, not Sized, and I instead went hunting for why my trait suddenly had a Sized bound.

Could/should there be a more specific error for this case, or some advanced tips added to the existing help?

Alternatively, updating the cargo check output to point to the specific requirement for unsized coercions, that T must implement both Trait + Sized, would probably have addressed my confusion sooner.

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 C-enhancement Category: An issue proposing an enhancement or a PR with one. D-confusing Diagnostics: Confusing error or lint that should be reworked. D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. 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

5 participants