When would a method need `Self` to be `Sized`

I am learning Sized Trait and Trait Object, and there is a rule that if I want to create a trait object,
Sized must not be a supertrait. In other words, it must not require Self: Sized.

many resources have tell me that a trait method that returns Self needs the Trait to be Sized. What's more? Is there a rule that can determine a method or a trait should be `Sized?

here is a example that cannot be compiled:

trait SizedTrait {
    fn get_self(self) -> Self;
}

struct MyStruct {}

impl SizedTrait for MyStruct {
    fn get_self(self) -> Self {
        MyStruct {}
    }
}

struct OtherStruct {}

impl SizedTrait for OtherStruct {
    fn get_self(self) -> Self {
        OtherStruct {}
    }
}

fn main() {
    let x = MyStruct {};
    let y = OtherStruct {};
    let v = vec![&x as &dyn SizedTrait, &y as &dyn SizedTrait];
    for i in v {
        i.print_self();
    }
}

The general rule/principle more specifically is that in order to work with any value directly, by-value, the value’s type must be sized. Returning a value of some type Type from a method or function

fn f(…) -> Type {
    …
}

is one way of working with a value of type Type (specifically, moving that value) directly by-value, hence you’re only allowed to do it in contexts where the constraint Type: Sized is true.

In traits, if you declare a method

trait MyTrait {
    fn method() -> Self;
}

then there is nothing that ensure Self: Sized is true, so this is inherently somewhat problematic.

Rust doesn’t immediately reject it at this point because the function is not yet actually implemented for any specific type for which Self: Sized won’t be true…

…but if you tried to write an implementation involving a non-Sized type such as [u8], the problem emerges.

impl MyTrait for [u8] {
    // error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
    fn method() -> Self {
        todo!()
    }
}

If you now think about trait objects, they, too, are non-Sized types. So imagine the compiler would need to be able to write some automatic implementation sort-of like

impl MyTrait for dyn MyTrait {
    fn method() -> Self {
        …
    }
}

but that would run into the same problem. To avoid this problem, instead, the rules of “dyn compatibility” pose that in cases like this one, dyn MyTrait simply isn’t available.

The rules on the page you linked to are even more restrictive in disallowing any kind of use of Self outside of the first function argument, due to how the actual implementation of dispatching the method is ultimately supposed to work.


Now, you have the same kind of issue for generic implementations, like

trait MyTrait {
    fn method() -> Self;
}

impl<T: ?Sized> MyTrait for T {
    // error[E0277]: the size for values of type `T` cannot be known at compilation time
    fn method() -> Self {
        todo!()
    }
}

The problem can be avoided by making Self be used less directly, e.g.


trait MyTrait2 {
    fn method() -> Box<Self>;
}

impl<T: ?Sized> MyTrait2 for T {
    fn method() -> Box<Self> { // okay
        todo!()
    }
}

or, without changing the trait, you would have to limit the implementation to only T: Sized types

// equivalent to just writing `impl<T>`
// since `T: Sized` is always added implicitly

impl<T: Sized> MyTrait for T {
    fn method() -> Self { // okay
        todo!()
    }
}

Now, there’s also the matter of default implementation bodies for trait methods.

trait MyTrait {
    // error[E0277]: the size for values of type `Self` cannot be known at compilation time
    fn method() -> Self {
        todo!() // default implementation
    }
}

this creates a similar issue as the T: ?Sized blanked impl above; the default method body is supposed to be usable for any potential downstream implementation of the trait MyTrait, which could be for non-sized types, like

// no need to write `method` anymore
// since there’s the default
impl MyTrait for [u8] {}

and hence the trait definition above must already be the place the problem with non-Sized return values is caught.

The possible way of solving this is to restrict the trait so that it can only possibly ever be implemented for Sized types, which is the Sized supertrait approach:

trait MyTrait3: Sized {
    // okay!
    fn method() -> Self {
        todo!() // default implementation
    }
}

Now an impl of

impl MyTrait3 for [u8] {}

will error because the supertrait bound is not met:

error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
 --> src/lib.rs:8:19
  |
8 | impl MyTrait3 for [u8] {}
  |                   ^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `[u8]`
note: required by a bound in `MyTrait3`
 --> src/lib.rs:1:17
  |
1 | trait MyTrait3: Sized {
  |                 ^^^^^ required by this bound in `MyTrait3`

For more information about this error, try `rustc --explain E0277`.

and this fact that something like impl MyTrait3 for [u8] is disallowed is what allowed the default method definition to be acceptable in the first place.


But there’s a different way: look at this

// no `Sized` supertrait
trait MyTrait4 {
    // also okay!
    fn method() -> Self
    where
        Self: Sized
    {
        todo!() // default implementation
    }
}

The where Self: Sized bound on the method will mean that even if a non-Sized type implements the trait, the method method will only be callable for those types that are Sized.

Without default impls, e.g.

trait MyTrait5 {
    fn method() -> Self
    where
        Self: Sized;
}

you’re even allowed to leave off the implementeation entirely in cases where it’ll never be usable, like:

// okay
impl MyTrait5 for [u8] {}

but for a Sized type, you’ll have to define it:

// error[E0046]: not all trait items implemented, missing: `method`
impl MyTrait5 for u8 {}

The built-in automatic implementation of MyTrait5 for dyn MyTrait5, too – like [u8] above – can just leave out this method entirely, and this is why the definitions of “dyn compatibility” have an exceptional case for those methods that have where Self: Sized bounds – those aren’t further restricted in form, because they’re irrelevant to the trait object (dyn MyTrait5) type, anyway.


Now to answering your questions:

this statement is not true (nor false) because it’s unclear what you mean by “the Trait needs to be Sized”. If you mean it needs to have a : Sized supertrait bound, then the statement is false, and thus hopefully it’s not what many resources tell you, either. My explanations above are trying to paint the picture of the actual situation.

See above. But that’s not all there is to know, either. It’s a rather open-ended question…

Again, it’s unclear what you mean by “a method be Sized” or “a trait be Sized”, but if you’re referring to Sized supertrait bounds, and where Self: Sized constraints on methods, respectively, I’ve explained the general motivation of why & when they could come up in the context of dyn compatibility, in my explanations above :slight_smile:

The example doesn’t compile for the same reason as why you couldn’t write a manual implementation of SizedTrait for an non-Sized type (such as [u8] slices for example); the -> Self return value on the method.


The example also features self: Self parameter values; the situation for those is largely similar, however there’s a minor additional quirk in the current (stable) Rust language: if a method has a f(self) by-value parameter, it’s currently mostly the same as if you had added a where Self: Sized bound on that specific method explicitly. But there are differences in future compatibility, since some nightly features exist that can support passing non-Sized arguments to functions by-value directly; and in-fact the trait FnOnce in the standard library secretly makes use of this fact internally, in order to be able to provide the implementation of FnOnce(Args…) -> R for Box<dyn FnOnce(Args…) -> R by means of a generic impl FnOnce(Args…) -> R for Box<F> even for F: ?Sized implementations of FnOnce.

You wouldn’t need to worry about these details, but unfortunately they do result in the section in the reference you’ve linked to being a little bit more confusing around the precise handling of self: Self parameters, with the added parenthesized section in:

Have a where Self: Sized bound (receiver type of Self (i.e. self) implies this).

Arguably the reference is actually even wrong about this claim, as your example demonstrates. If you explicitly add a where Self: Sized bound to your get_self method, then the creation of &dyn SizedTrait no longer fails at all:

trait SizedTrait {
    fn get_self(self) -> Self
    where
        Self: Sized;
}

struct MyStruct {}

impl SizedTrait for MyStruct {
    fn get_self(self) -> Self {
        MyStruct {}
    }
}

struct OtherStruct {}

impl SizedTrait for OtherStruct {
    fn get_self(self) -> Self {
        OtherStruct {}
    }
}

fn main() {
    let x = MyStruct {};
    let y = OtherStruct {};
    let v = vec![&x as &dyn SizedTrait, &y as &dyn SizedTrait];
    for i in v {
        i.print_self();
    }
}

but the error moves down the line, since you still cannot call print_self on your &dyn SizedTrait value. (So in the end, not particularly much is gained.)

7 Likes