I know that traits and slices are unsized, i.e. it's not possible to know their size at compile time, e.g. any type may implement a trait, but that type may not be sized.
Nevertheless, doesn't this example code mean that every type which implements trait Foo
needs to implement Sized
too?
trait Foo: Sized {}
struct Bar(i64);
impl Foo for Bar {}
If that's so, why doesn't this work?
impl From<Foo> for Bar {
fn from(foo: Foo) -> Bar {
Bar(64)
}
}
error[E0277]: the trait bound `Foo + 'static: std::marker::Sized` is not satisfied
--> src/main.rs:7:6
|
7 | impl From<Foo> for Bar {
| ^^^^^^^^^ `Foo + 'static` does not have a constant size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `Foo + 'static`
I want to provide to the consumer of the library a type (let's name it Bar
) and make it possible to convert to Bar
from any other type which implements a particular trait (let's name it Foo
).
I solved it by passing Foo
by the reference instead of the value, but I'm not sure why the compiler complains if it's required for implementors to be Sized
.
Why doesn't it work?
When you say that every Foo
is Sized
, you're kind of hiding the truth to yourself. Yes, every Foo
is Sized
but actually every type has a given size at some point. The real important information is that you're not saying how much this size is. Imagine if Bar(i64)
is Foo
, but Baz(i8)
is Foo
as well (they're both Sized
, right?) which size do you determine Foo
to be? Is it 8- or 1-byte long? This question is asked by the compiler when it tries to generate the code for your function from(foo: Foo)
. Usually, Sized
is rather used in a "maybe"-style with the syntax ?Sized
, indicating that the type size might be unknown at compile time.
How to solve it?
Typically you ditch the : Sized
part, and use the following syntax, which is actually kind of a C++ template; it gives the compiler a sketch to write the actual code when given a concrete type with a given size.
trait Foo {}
struct Bar(i64);
impl Foo for Bar {}
impl<F: Foo> From<F> for Bar {
fn from(foo: F) -> Bar {
Bar(64)
}
}
(This will still error based on the fact that you cannot reimplement From
because of the std
crate, but it's not related to your original question.)
You could also use the reference trait object &Foo
syntax in the argument to your function. This would transform your call from static dispatch to dynamic dispatch (read more here) but you can't do this here because the signature is imposed by the trait.
The Foo
trait requires implementors to be Sized
. It doesn't mean that Foo
itself will have a size. You're misunderstanding the syntax the second code example and I'm therefore not sure what you're actually trying to do. Are you looking for this?
impl From<i64> for Bar {
fn from(val: i64) -> Bar {
Bar(val)
}
}
Or do you want a way to construct a Bar
from any signed integer?
We've got a XY problem here.