Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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.)

like image 691
Lukas Kalbertodt Avatar asked Jan 31 '19 16:01

Lukas Kalbertodt


People also ask

What is sized trait rust?

The Sized trait in Rust is an auto trait and a marker trait. Auto traits are traits that get automatically implemented for a type if it passes certain conditions. Marker traits are traits that mark a type as having a certain property.

How do you return yourself in Rust?

impl T for S { fn f(&self) -> "I want to return S" { // Do Something S { ... } } } struct R { ... } impl T for R { fn f(&self) -> "I want to return R" { // Do Something R { ... } } }

What is Self in Rust?

Keyword selfThe receiver of a method, or the current module. self is used in two situations: referencing the current module and marking the receiver of a method. In paths, self can be used to refer to the current module, either in a use statement or in a path to access an element: use std::io::{self, Read};

What is dyn rust?

dyn is a prefix of a trait object's type. The dyn keyword is used to highlight that calls to methods on the associated Trait are dynamically dispatched. To use the trait this way, it must be 'object safe'. Unlike generic parameters or impl Trait , the compiler does not know the concrete type that is being passed.


2 Answers

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? Allowing unsized types to be used in trait methods is a key part of allowing by-value trait objects, a very useful 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;
    }
    
    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.

like image 162
Shepmaster Avatar answered Sep 28 '22 03:09

Shepmaster


It's in the error message:

= note: required by `std::option::Option`

Option requires the type to be Sized because it allocates on the stack. All type parameters to a concrete type definition are bound to Sized by default. Some types choose to opt out with a ?Sized bound but Option does not.

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

I think this is a conscious design decision, due to history, future-proofing and ergonomics.

First of all, Self is not assumed to be Sized in a trait definition because people are going to forget to write where Self: ?Sized, and those traits would be less useful. Letting traits be as flexible as possible by default is a sensible design philosophy; push errors to impls or make developers explicitly add constraints where they are needed.

With that in mind, imagine that trait definitions did not permit unsized types to be returned by a method. Every trait method that returns Self would have to also specify where Self: Sized. Apart from being a lot of visual noise, this would be bad for future language development: if unsized types are allowed to be returned in the future (e.g. to be used with placement-new), then all of these existing traits would be overly constrained.

like image 35
Peter Hall Avatar answered Sep 28 '22 03:09

Peter Hall