Why are unsized types allowed in trait method declarations?

Viewed 145

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?

1 Answers

I believe there are two distinct concerns here: the unsized function parameter and the unsized return type.

Unsized parameter types

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?

Unsized return types

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.

Related