Why does returning `Self` in trait work, but returning `Option<Self>` requires `Sized`?

  • A+
Category:Languages

This trait definition compiles fine:

trait Works {     fn foo() -> Self; } 

This, however, does lead to an error:

trait Errors {     fn foo() -> Option<Self>; } 
error[E0277]: the size for values of type `Self` cannot be known at compilation time  --> src/lib.rs:6:5   | 6 |     fn foo() -> Option<Self>;   |     ^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time   |   = help: the trait `std::marker::Sized` is not implemented for `Self`   = note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>   = help: consider adding a `where Self: std::marker::Sized` bound   = note: required by `std::option::Option` 

With the : Sized supertrait bound, it works.

I know that the Self type in traits is not automatically bound to be Sized. And I understand that Option<Self> cannot be returned (via the stack) unless it is sized (which, in turn, requires Self to be sized). However, the same would go for Self as return type, right? It also cannot be stored on the stack unless it's sized.

Why doesn't the first trait definition already trigger that error?

(This question is related, but it doesn't answer my exact question – unless I didn't understand it.)

 


There are two sets of checks happening here, which is why the difference appears confusing.

  1. Each type in the function signature is checked for validity. Option inherently requires T: Sized. A return type that doesn't require Sized is fine:

    trait Works {     fn foo() -> Box<Self>; } 

    The existing answer covers this well.

  2. Any function with a body also checks that all of the parameters are Sized. Trait functions without a body do not have this check applied.

    Why is this useful? Well, it is currently impossible to make use of this in stable Rust, but allowing unsized types to be used in trait methods is a key step towards allowing by-value trait objects, a much-anticipated feature. For example, FnOnce does not require that Self be Sized:

    pub trait FnOnce<Args> {     type Output;     extern "rust-call" fn call_once(self, args: Args) -> Self::Output; } 
    #![feature(unsized_locals)]  fn call_it(f: Box<dyn FnOnce() -> i32>) -> i32 {     f() }  fn main() {     println!("{}", call_it(Box::new(|| 42))); } 

A big thanks to pnkfelix and nikomatsakis for answering my questions on this topic.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: