[solved] Why does &dyn passed to templated method give a Sized error?

#1

I have the following code (playground link here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1a2cbcdf7eef6b4e3578044fb9f1282b )

trait DisplayImage {
    fn get(&self, x: i32, y: i32) -> bool;
}

struct TestScreen0 {
}

impl DisplayImage for TestScreen0 {
    fn get(&self, x: i32, y: i32) -> bool {
        (((x>>2) ^ (y>>2)) & 1) == 0
    }
}

/** Doestn't work */
fn set_screen1<X: DisplayImage>(contents: &X)
{
}

/** Works */
fn set_screen2(contents: &dyn DisplayImage)
{
}

fn main()
{
    let x: &dyn DisplayImage = &TestScreen0 {};
    set_screen1(x);   
    set_screen2(x);   
}

This gives the error:

error[E0277]: the size for values of type `dyn DisplayImage` cannot be known at compilation time
  --> src/main.rs:27:5
   |
27 |     set_screen1(x);   
   |     ^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `dyn DisplayImage`
   = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
note: required by `set_screen1`
  --> src/main.rs:15:1
   |
15 | fn set_screen1<X: DisplayImage>(contents: &X)
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

What I don’t understand is why the signature for set_screen1 requires the size of the object to be known; I would have expected that filling in the type would have resulted in something like that of set_screen2? (which does work)

0 Likes

#2

Hi, set_screen1 expects a reference to some concrete type that implement the DisplayImage trait whereas set_screen2 takes a trait object.
The issue when you make x is that you transform it into a trait object instantly, and we don’t know the concrete type of a trait object so when you call set_screen2, no problem you have a trait object and it expects a trait object. With set_screen1 you have a trait object but it expects a concrete type, and we don’t know what it is.

let x = &TestScreen0 {};

will work with both functions though.

2 Likes

#3

As @leudz said, trait objects use type erasure, so after let x: &dyn DisplayImage = &TestScreen0 {};, the original type is erased. You can not convert a trait object back into it’s original type, and neither can the compiler.

More information here: https://doc.rust-lang.org/stable/book/ch17-02-trait-objects.html ( especially the last two sections).

Also a minor terminology hint: Rust does not have templates, only generic functions or types. (welcome from C++ :wink: )

2 Likes

#4

One last thing to note is that all generic parameters have a default Sized bound. You can remove this with a ?Sized bound. This will allow the type parameter to be used with unsized types like trait objects.

Like this

fn set_screen1<X: ?Sized + DisplayImage>(contents: &X) { } 
4 Likes

#5

That’s definitely the part that I was missing. Thanks, I didn’t know of the ?trait syntax at all (I see it’s documented in https://doc.rust-lang.org/std/marker/trait.Sized.html, and specific to Sized). With that, it works perfectly.

Yea the reason for this awkward cast—it’s simplified from the original program—is that there is an array of screen images, with all different types and implementations but which implement that trait. So it ended up as [&dyn DisplayImage].
It’s definitely not optimal this way as it makes it do a dynamic call for every single pixel, I might go with something more static like an enum, but I was surprised that it didn’t work.

Whoops :blush:

1 Like

#6

Careful, trait object is a form of type erasure in that it unifies to its own type, which is actually concrete !
(There is no such thing as a not concrete type).

As @KrishnaSannasi very accurately pointed out, the problem comes from an implicit _ : Sized bound on all the type parameters of a function or struct.

In other words, set_screen1 without the X : Sized bound (i.e., with the X : ?Sized bound eraser) is a function factory that can monomorphise into set_screen2, thus making it strictly superior:

set_screen2 == set_screen1::<dyn DysplayImage>

since, by construction / definition of a trait object, we “implicitly have”

impl DisplayImage for dyn DisplayImage {
    fn get (&self, x: i32, y: i32) -> bool
    {
        <dyn DisplayImage>::get(self, x, y)
    }
}
3 Likes