Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are unsized types allowed in trait method declarations?

Tags:

rust

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?

like image 576
Ibraheem Ahmed Avatar asked Feb 16 '21 22:02

Ibraheem Ahmed


People also ask

What is sized in Rust?

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.

What is a trait object?

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.


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.

like image 182
trent Avatar answered Nov 04 '22 23:11

trent