Why are unsized types allowed in trait method declarations? For example, this code compiles:
trait Blah {
fn blah(&self, input: [u8]) -> dyn Display;
}
But implementing Blah
is impossible:
impl Blah for Foo {
fn blah(&self, input: [u8]) -> dyn Display {
"".to_string()
}
}
// error[E0277]: the size for values of type `(dyn std::fmt::Display + 'static)` cannot be known at compilation time
// error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
Giving blah
a default implementation is also impossible:
trait Blah {
fn blah(&self, input: dyn Display) -> dyn Display { "".to_string() }
}
// error[E0277]: the size for values of type `(dyn std::fmt::Display + 'static)` cannot be known at compilation time
Nested unsized types are also not allowed. This inconsistency makes me think this is a compiler bug:
trait Blah {
fn blah(&self, input: [str]) -> dyn Display;
}
// error[E0277]: the size for values of type `str` cannot be known at compilation time
I found a couple old GitHub issues that claim this behavior is intentional, but I could not find a reason for it. Why is this intentional behavior? If implementing a trait of this nature is impossible, why doesn't the compiler catch it in the trait declaration?
In Rust a type is sized if its size in bytes can be determined at compile-time. Determining a type's size is important for being able to allocate enough space for instances of that type on the stack. Sized types can be passed around by value or by reference.
A trait object is an opaque value of another type that implements a set of traits. The set of traits is made up of an object safe base trait plus any number of auto traits. Trait objects implement the base trait, its auto traits, and any supertraits of the base trait.
I believe there are two distinct concerns here: the unsized function parameter and the unsized return type.
Unsized function parameters are actually implemented and available in nightly Rust under feature(unsized_fn_params)
. It's likely this will be stabilized at some point, and then it will be possible to implement a trait like this one:
trait Crab {
fn pinch(&self, data: str);
}
Which currently is un-implementable in stable Rust.
In fact, the standard library uses this feature to implement FnOnce()
for Box<dyn FnOnce()>
, which requires moving *self
into the call_once
method. Since FnOnce
is (kind of) externally visible¹, and std
is always allowed to use unstable features, there might not have been any way to implement unsized_fn_params
(in nightly) without permitting trait functions to have them (in stable). However, that's only speculation.
See also How to pass a boxed trait object by value in Rust?
As the name hints, unsized_fn_params
only affects parameters; unsized return values are still not allowed even under unsized_locals
(the broader, more permissive, less complete feature). So the argument for allowing -> dyn Display
is far less convincing, and there's a good case to be made that this is an actual bug, or at least deserving of a lint.
I believe the "intentional" part of this, in fact, only applies to parameter types, and including return types was an accident.
¹ The Fn
traits are special because in stable they can only be used with Fn(...)
syntax, not Fn<Args>
, but are still valid in constraints and trait objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With