Not yet, it requires two-step partial implementations of a trait, which in turn requires the specialization
experimental feature:
#![feature(specialization)]
trait Test {
fn foo (self)
;
fn works (self: Box<Self>)
;
}
default impl<T> Test for T {
fn works (self: Box<Self>)
{
(*self) // move `mem::size_of::<Self>()` bytes from the heap into a local parameter
.foo()
}
}
struct Foo;
impl Test for Foo {
fn foo (self)
{
println!("<Foo as Test>::foo()");
}
}
The reason a Box<Self>
-receiver-based method is a valid method for a trait object is that the dyn Trait
type is !Sized
, meaning that there can be values of different sizes all having the same type dyn Trait
, so unlike most Sized
types, Rust cannot know statically / at compile time how big a value obj
of type dyn Trait
may be
- As a more illustrative example, Rust knows that a value
x
of type [u8; 4]
is always 4 bytes long, a value y
of type [u8; N]
is always N
-bytes long, but a value z
of type [u8]
could have any kind of length.
And it so happens that CPU architectures require1 static / compile-time knowledge of the size of a value for it to be used as a function parameter (or return value, or even a function local). That's why to see a x: T
, be it as a function parameter, a variable or a return value, then necessarily T : Sized
.
So having self: dyn Trait
is just not possible yet.
There is, however, a way to magically ensure a ?Sized
(i.e., a type that may or may not be Sized
) type T
becomes Sized
: (pointer) indirection.
By having a PtrTo<T>
pointer, with some additional (runtime) "metadata" attached when T : !Sized
(such pair makes what is called a fat pointer), such as the length of a slice, or a pointer to the static table of function pointers to the methods of a trait (a vtable), then the size is indeed fixed: size of the pointer + size of the metadata.
Example

-
Playground
-
As you can see, even if x
and y
point to slices of different lengths, they are both equal-sized "fat" pointers: an address + a length each.
-
A similar thing happens with dyn Trait
, which may be a type-erased Foo : Trait
of size 3
, or a type-erased Bar : Trait
of size 4096
. In the current implementation of trait objects, the "fat" pointer's metadata is a pointer to a struct containing the size, alignment, destructor, and the (object-safe) methods of Trait
.
So, we have seen that a a method using self: Self
as a receiver is not usable when Self : ?Sized
("Self
may not have a size known at compile-time"), so that's a situation where a Self : Sized
bound may be added. But since dyn Trait : !Sized
, adding such a bound automatically makes that method unusable for a trait object. Hence the idea of using indirection.
With &self
(i.e., self: &Self
) and &mut self
(i.e., self: &mut Self
) the borrowing contains the necessary indirection. But for ownership we need an owning pointer, such as Box<Self>
, Arc<Self>
, etc. preferably with exclusive ownership since that's what our original self: Self
expressed; this leads to using mainly Box
as the pointer type to wrap Self
receivers with.
In your case, you did get all this right with the .works()
method, except that you did not have the obvious generic derivation of a Box<Self>
-receiver method from a Self
-receiver method. So you've tried writing the default implementation within the trait definition, except that:
-
to be able to (*self)...
(move out of the Box
), you need Self : Sized
, so you needed a Self : Sized
bound for the generic implementation;
-
the method signature within the API of the trait must be trait-object compatible, so you cannot have a Self : Sized
bound there.
This contradiction shows that a trait's definition cannot always be used to define some restricted default implementations (in this case restricted to Self : Sized
), and that's what default impl
will be used for.
In the meantime you will need to write that "obvious" implementation for each impl
of the trait, although you can use macros to do that for you:
struct Foo;
impl_Test! {
impl Test for Foo {
fn foo (self)
{
println!("<Foo as Test>::foo()");
}
}
}
1 There is an RFC for unsized locals, but such implementations rely on runtime stack-allocations which in practice come with a bunch of drawbacks that are rarely worth it...