Confusing error message: the trait std::convert::Into cannot be made into an object

6fc58c0
Opened by Edd Barrett at 2024-12-21 05:41:07

I've tried to make a function which will make a new thread, accepting either an &str or a String for the new thread's name:

/// Test helper to spawn a named thread.
///
/// The thread executes the work in the `f` closure.
#[cfg(test)]
fn named_thread<'s, F>(name: Into<String>, f: F) -> JoinHandle<()>
               where F: FnOnce(), F: Send + 'static {
    let thr = thread::Builder::new()
                              .name(String::from(name))
                              .spawn(f)
                              .unwrap();
    thr
}

This gives:

error[E0038]: the trait `std::convert::Into` cannot be made into an object
   --> src/lib.rs:377:1
    |
377 | / fn named_thread<'s, F>(name: Into<String>, f: F) -> JoinHandle<()>
378 | |                where F: FnOnce(), F: Send + 'static {
379 | |     let name = name.into();
380 | |     let thr = thread::Builder::new()
...   |
384 | |     thr
385 | | }
    | |_^ the trait `std::convert::Into` cannot be made into an object
    |
    = note: the trait cannot require that `Self : Sized`

Which, from a user POV, is confusing on a few levels:

  • I don't have any mention of Sized in my code (must be implicit).
  • Who is Self here?
  • Even if I knew where Sized came from, and who Self was, the error message doesn't tell me what I did wrong.

I'd be interested to know what I did wrong, even if the error message can't be improved.

Thanks

  1. This works for me:

    fn named_thread<F, T>(name: T, f: F) -> JoinHandle<()>
        where F: FnOnce(), F: Send + 'static, T: Into<String>
    {
        thread::Builder::new()
            .name(name.into())
            .spawn(f)
            .unwrap()
    }
    

    You cannot use std::convert::Into as if it was an object because it is just a trait. You have to use trait bounds for what you want to achieve. What the code above basically says is that T can be any type that implements the trait std::convert::Into<String> which guarantees that name.into() will return a String. All objects that you want to construct must have a known size at compile-time which the trait std::marker::Sized expresses. Since std::convert::Into is not an object, and thus has no size, it doesn't implement the trait std::marker::Sized. The error message expresses exactly what the problem is but I agree with you that it's confusing. It could be made clearer :)

    srccde at 2017-12-18 12:54:48

  2. Thanks. After asking on IRC, I eventually ended up at the same code as you have above, and it works.

    I think I understand after reading: https://doc.rust-lang.org/book/first-edition/trait-objects.html

    In my own words, passing a trait directly (i.e. without a trait object involved):

    • Uses static dispatch at the cost of code size.
    • But will only work if the argument implements Sized. Presumably, to make the static dispatch work, Rust needs to know the size of the incoming data for storing it, e.g., on the stack.

    And, I think, if you use a where clause with trait constraints:

    • You are using a trait object, with dynamic dispatch via an implicit reference (and since references all have the same size, they are trivially Sized).
    • There will be a runtime cost due to the use of a vtable.

    Do I understand correctly?

    Thank you for your comments.

    EDIT: Fix trait objects discussion.

    Edd Barrett at 2017-12-18 13:38:02

  3. @vext01 I realize this comment of yours is nearly two years old, so you probably have a good handle on this by now, but just for anybody else who comes by to read this later:

    No, you have it backwards. When passing a trait object directly (which in rust >= 1.27 is done using dyn Trait notation, making it more obvious), that's when you get dynamic dispatch. This is where the vtable and runtime cost comes in. You can only pass traits objects by reference, not by value though, so you'd need something like &dyn Trait or Box<dyn Trait>. But you cannot do this with a trait like Into, because Into has a Self: Sized constraint, and traits that are Self: Sized are not object-safe. Only object-safe traits can be used as trait objects, and therefore with dynamic dispatch.

    When you do the <T: Into> or <T> where T: Into approach is when you get static dispatch. What happens during compile is that Rust takes your T type and makes a copy of the function with that concrete type in place of T, in a process called monomorphization.

    Alexander Krivács Schrøder at 2019-09-03 04:59:10

  4. Triage: no change.

    Esteban Kuber at 2020-02-01 20:58:31

  5. Triage: Better span, should suggest using generics?

    error[E0038]: the trait `Into` cannot be made into an object
     --> src/lib.rs:3:30
      |
    3 | fn named_thread<'s, F>(name: Into<String>, f: F) -> JoinHandle<()>
      |                              ^^^^^^^^^^^^ `Into` cannot be made into an object
      |
      = note: the trait cannot be made into an object because it requires `Self: Sized`
      = note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
    

    Esteban Kuber at 2023-02-03 15:51:54

  6. Current output:

    error[E0038]: the trait `Into` cannot be made into an object
     --> src/lib.rs:3:30
      |
    3 | fn named_thread<'s, F>(name: dyn Into<String>, f: F) -> JoinHandle<()>
      |                              ^^^^^^^^^^^^^^^^ `Into` cannot be made into an object
      |
      = note: the trait cannot be made into an object because it requires `Self: Sized`
      = note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
    
    error[E0277]: the size for values of type `(dyn Into<String> + 'static)` cannot be known at compilation time
     --> src/lib.rs:3:24
      |
    3 | fn named_thread<'s, F>(name: dyn Into<String>, f: F) -> JoinHandle<()>
      |                        ^^^^ doesn't have a size known at compile-time
      |
      = help: the trait `Sized` is not implemented for `(dyn Into<String> + 'static)`
      = help: unsized fn params are gated as an unstable feature
    help: you can use `impl Trait` as the argument type
      |
    3 | fn named_thread<'s, F>(name: impl Into<String>, f: F) -> JoinHandle<()>
      |                              ~~~~
    help: function arguments must have a statically known size, borrowed types always have a known size
      |
    3 | fn named_thread<'s, F>(name: &dyn Into<String>, f: F) -> JoinHandle<()>
      |                              +
    
    error[E0277]: the trait bound `String: From<dyn Into<String>>` is not satisfied
     --> src/lib.rs:6:37
      |
    6 | ...                   .name(String::from(name))
      |                             ^^^^^^ the trait `From<dyn Into<String>>` is not implemented for `String`
      |
      = help: the following other types implement trait `From<T>`:
                <String as From<char>>
                <String as From<Box<str>>>
                <String as From<Cow<'a, str>>>
                <String as From<&str>>
                <String as From<&mut str>>
                <String as From<&String>>
    
    error[E0038]: the trait `Into` cannot be made into an object
     --> src/lib.rs:6:37
      |
    6 | ...                   .name(String::from(name))
      |                             ^^^^^^^^^^^^ `Into` cannot be made into an object
      |
      = note: the trait cannot be made into an object because it requires `Self: Sized`
      = note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
    
    error[E0277]: the size for values of type `dyn Into<String>` cannot be known at compilation time
       --> src/lib.rs:6:50
        |
    6   | ...                   .name(String::from(name))
        |                             ------------ ^^^^ doesn't have a size known at compile-time
        |                             |
        |                             required by a bound introduced by this call
        |
        = help: the trait `Sized` is not implemented for `dyn Into<String>`
    note: required by a bound in `from`
       --> /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/convert/mod.rs:579:16
        |
    579 | pub trait From<T>: Sized {
        |                ^ required by this bound in `From::from`
    ...
    584 |     fn from(value: T) -> Self;
        |        ---- required by a bound in this associated function
    

    The E0038 should suggest impl instead of dyn, like E0277 does. The additional object safety errors on String::from are redundant. The desired end code is https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=93a49bf8ddfb295dc579f4aed6bd5d2c

    Esteban Kuber at 2024-02-20 18:11:21