Reputation: 35136
This used to work:
struct Foo<'a, T> {
parent:&'a (Array<T> + 'a)
}
impl<'a, T> Foo<'a, T> { //'
pub fn new<T>(parent:&Array<T>) -> Foo<T> {
return Foo {
parent: parent
};
}
}
trait Array<T> {
fn as_foo(&self) -> Foo<T> {
return Foo::new(self);
}
}
fn main() {
}
Now it errors:
:15:21: 15:25 error: the trait
core::kinds::Sized
is not implemented for the typeSelf
:15 return Foo::new(self);
I can kind of guess what's wrong; it's saying that my impl of Foo<'a, T> is for T, not Sized? T, but I'm not trying to store a Sized? element in it; I'm storing a reference to a Sized element in it. That should be a pointer, fixed size.
I don't see what's wrong with what I'm doing, or why it's wrong?
For example, I should (I think...) be able to store a &Array in my Foo, no problem. I can't see any reason this would force my Foo instance to be unsized.
playpen link: http://is.gd/eZSZYv
Upvotes: 11
Views: 2052
Reputation: 102076
There's two things going on here: trait objects coercions (the error), and object safety (fixing it).
As suggested by the error message, the difficult part of the code is the Foo::new(self)
, and this is because pub fn new<T>(parent: &Array<T>) -> ...
, that is, self
is being coerced to an &Array<T>
trait object. I'll simplify the code to:
trait Array {
fn as_foo(&self) {
let _ = self as &Array; // coerce to a trait object
}
}
fn main() {}
which gives the same thing:
<anon>:3:13: 3:27 error: the trait `core::kinds::Sized` is not implemented for the type `Self`
<anon>:3 let _ = self as &Array; // coerce to a trait object
^~~~~~~~~~~~~~
Self
is the stand-in name for the type that implements the trait. Unlike most generic parameters, Self
is possibly-unsized (?Sized
) by default, since RFC 546 and #20341 for the purposes of allowing e.g. impl Array<T> for Array<T>
to work by default more often (we'll come to this later).
The variable self
has type &Self
. If Self
is a sized type, then this is a normal reference: a single pointer. If Self
is an unsized type (like [T]
or a trait), then &Self
(&[T]
or &Trait
) is a slice/trait object: a fat pointer.
The error appears because the only references &T
that can be cast to a trait object are when T
is sized: Rust doesn't support making fat pointers fatter, only thin pointer → fat pointer is valid. Hence, since the compiler doesn't know that Self
will always be Sized
(remember, it's special and ?Sized
by default) it has to assume the worst: that the coercion is not legal, and so it's disallowed.
It seems logical that the fix we're looking for is to ensure that Self: Sized
when we want to do a coercion. The obvious way to do this would be to make Self
always Sized
, that is, override the default ?Sized
bound as follows:
trait Array: Sized {
fn as_foo(&self) {
let _ = self as &Array; // coerce to a trait object
}
}
fn main() {}
Looks good!
Except there's the small point that it doesn't work; but at least it's for a difference reason, we're making progress! Trait objects can only be made out of traits that are "object safe" (i.e. safe to be made into a trait object), and having Sized
Self
is one of the things that breaks object safety:
<anon>:3:13: 3:17 error: cannot convert to a trait object because trait `Array` is not object-safe [E0038]
<anon>:3 let _ = self as &Array; // coerce to a trait object
^~~~
<anon>:3:13: 3:17 note: the trait cannot require that `Self : Sized`
<anon>:3 let _ = self as &Array; // coerce to a trait object
^~~~
<anon>:3:13: 3:17 note: the trait cannot require that `Self : Sized`
<anon>:3 let _ = self as &Array; // coerce to a trait object
^~~~
(I filed the double printing of the note as #20692.)
Back to the drawing board. There's a few other "easy" possibilities for a solution:
trait ArrayExt: Sized + Array { fn as_foo(&self) { ... } }
and implement it for all Sized + Array
typesfn array_as_foo<A: Array>(x: &A) { ... }
However, these don't necessarily work for every use case, e.g. specific types can't customise the behaviour by overloading the default method. However, fortunately there is a fix!
(Named for Aaron Turon, who discovered it.)
Using generalised where
clauses we can be highly specific about when Self
should implement Sized
, restricting it to just the method(s) where it is required, without infecting the rest of the trait:
trait Array {
fn as_foo(&self) where Self: Sized {
let _ = self as &Array; // coerce to a trait object
}
}
fn main() {}
This compiles just fine! By using the where
clause like this, the compiler understands that (a) the coercion is legal because Self
is Sized
so self
is a thin pointer, and (b) that the method is illegal to call on a trait object anyway, and so doesn't break object safety. To see it being disallowed, changing the body of as_foo
to
let x = self as &Array; // coerce to a trait object
x.as_foo();
gives
<anon>:4:7: 4:15 error: the trait `core::kinds::Sized` is not implemented for the type `Array`
<anon>:4 x.as_foo();
^~~~~~~~
as expected.
Making this change to the original unsimplified code is as simple adding that where
clause to the as_foo
method:
struct Foo<'a, T> { //'
parent:&'a (Array<T> + 'a)
}
impl<'a, T> Foo<'a, T> {
pub fn new(parent:&Array<T>) -> Foo<T> {
return Foo {
parent: parent
};
}
}
trait Array<T> {
fn as_foo(&self) -> Foo<T> where Self: Sized {
return Foo::new(self);
}
}
fn main() {
}
which compiles without error. (NB. I had to remove the unnecessary <T>
in pub fn new<T>
because that was causing inference failures.)
(I have some in-progress blog posts that go into trait objects, object safety and the Turon trick, they will appear on /r/rust in the near future: first one.)
Upvotes: 17