Why is Sizedness required to be explicitly specified here?


trait Foo {
    fn foo(&self, a: StructA<Self>) -> Self;
}

The above piece of code fails to compile with the following error:

|     fn foo(&self, a: StructA<Self>) -> Self;
|                      ^^^^^^^^^^^^^ doesn't have a size known at compile-time
|

So when I add Sized to Self, it compiles alright:


trait Foo where Self: Sized {
    fn foo(&self, a: StructA<Self>) -> Self;
}

But it seems the following code compiles without any complaints:

trait Foo {
    fn foo(&self, a: Self) -> Self;
}

I suppose in this case, Self is sized by default. My question is, why am I required to explicitly specify sizedness in the first case and not the second?

StructA<T> probably contains a T, and also requires it to be sized (this is default bound on struct parameters.) By putting a Sized bound on the trait, you guarantee that this is satisfied for that method.

In the last case, you have no StructA, so it is not constraining anything. You can't pass in a Self to Foo unless it is Sized, so that method would simply be unavailable if Self is not Sized, and you'd get an error trying to call it if so. (This also means that Foo is not object safe)

The allowed form is a situation where well-formedness is not enforced at the declaration:

// Fine to declare it this way
trait Foo {
    // But this isn't actually bound by `Sized`, even though you can't
    // implement it unless sized.  The bound is checked if you try to
    // implement it.
    fn foo(&self, a: Self) -> Self;
}

But the implicit type parameter bound for StructA<Self> is checked. If you change its declaration to StructA<T: ?Sized>, you can get it to compile-but-can't-implement status again.

Why is one lazy and the other not? I don't know.

Instead of putting the bound on the trait (or removing the bound from the struct), you can put it on individual methods:

trait Foo {
    fn foo(&self, a: StructA<Self>) -> Self where Self: Sized;
}

This enables dyn Foo to exist (without the foo method), i.e. you can make it object safe. And it gets rid of the error. But confusingly IMO, it does not enable actually implementing Foo for unsized types like str. It's a special case for dyn Trait, so far anyway.

2 Likes

Maybe this is why:

FWIW, this is intentional, as we still aim to allow DST moves at some point (or at least I do).

I.e. it's fine to declare and doesn't have the bound because maybe some day it will also be possible to implement. But today it is not, so you get the error when you try to implement. (StructA<T> on the other hand is bound, implicitly, unless you opt out.)

(And for your Rust trivia night needs, also from that thread, you can implement a trait with bound methods for arbitrary DSTs if you have a default impl of the method as well.)