问题
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.)
回答1:
There are two sets of checks happening here, which is why the difference appears confusing.
Each type in the function signature is checked for validity.
Option
inherently requiresT: Sized
. A return type that doesn't requireSized
is fine:trait Works { fn foo() -> Box<Self>; }
The existing answer covers this well.
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 thatSelf
beSized
: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.
回答2:
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.
回答3:
The issue with Option
is just the tip of the iceberg and the others have already explained that bit; I'd like to elaborate on your question in the comment:
is there a way I can implement fn
foo() -> Self
withSelf
not beingSized
? Because if there is no way to do that, I don't see the point in allowing to returnSelf
without aSized
bound.
That method indeed makes it impossible (at least currently) to utilize the trait as a trait object due to 2 issues:
- Method has no receiver:
Methods that do not take a
self
parameter can't be called since there won't be a way to get a pointer to the method table for them.
- Method references the Self type in its arguments or return type:
This renders the trait not object-safe, i.e. it is impossible to create a trait object from it.
You are still perfectly able to use it for other things, though:
trait Works {
fn foo() -> Self;
}
#[derive(PartialEq, Debug)]
struct Foo;
impl Works for Foo {
fn foo() -> Self {
Foo
}
}
fn main() {
assert_eq!(Foo::foo(), Foo);
}
来源:https://stackoverflow.com/questions/54465400/why-does-returning-self-in-trait-work-but-returning-optionself-requires