You can actually combine the two approaches mentioned above. (The self: Box<Self>
method and the implementation for Box<dyn Trait>
.) I'd assume you want to keep it consume(self)
because if it's not a trait object, you don't want to enforce boxes. So the straightforward approach would be add a second method
trait Trait {
fn consume(self);
fn consume_boxed(self: Box<Self>);
}
struct Impl;
impl Trait for Impl {
fn consume(self) {}
fn consume_boxed(self: Box<Self>) {}
}
fn main() {
let obj = Box::new(Impl);
obj.consume();
let obj2: Box<dyn Trait> = Box::new(Impl);
obj2.consume_boxed();
}
But that means you'll now have to call this as .consume_boxed()
, which is annoying. But you can actually write the implementation for
impl Trait for Box<dyn Trait> {
fn consume(self) {
self.consume_boxed()
}
fn consume_boxed(self: Box<Self>) {
self.consume()
}
}
now. So the code looks like
trait Trait {
fn consume(self);
fn consume_boxed(self: Box<Self>);
}
struct Impl;
impl Trait for Impl {
fn consume(self) {}
fn consume_boxed(self: Box<Self>) {}
}
impl Trait for Box<dyn Trait> {
fn consume(self) {
self.consume_boxed()
}
fn consume_boxed(self: Box<Self>) {
self.consume()
}
}
fn main() {
let obj = Box::new(Impl);
obj.consume();
let obj2: Box<dyn Trait> = Box::new(Impl);
obj2.consume();
}
Still annoying that everybody needs to always manually implement consume_boxed
now, though. And you cannot just do
trait Trait {
fn consume(self);
fn consume_boxed(self: Box<Self>) {
self.consume();
}
}
error[E0161]: cannot move a value of type Self: the size of Self cannot be statically determined
--> src\main.rs:4:9
|
4 | self.consume();
| ^^^^^^^^^^^^^^
Solution: Add another trait! A supertrait works great, actually.
trait Trait: TraitBoxed {
fn consume(self);
}
trait TraitBoxed {
fn consume_boxed(self: Box<Self>);
}
How this helps? It allows for a generic implementation for sized types!
impl<T> TraitBoxed for T
where
T: Trait,
{
fn consume_boxed(self: Box<Self>) {
self.consume()
}
}
Full code right now...
trait Trait: TraitBoxed {
fn consume(self);
}
trait TraitBoxed {
fn consume_boxed(self: Box<Self>);
}
impl<T> TraitBoxed for T
where
T: Trait,
{
fn consume_boxed(self: Box<Self>) {
self.consume()
}
}
struct Impl;
impl Trait for Impl {
fn consume(self) {
println!("it works!")
}
}
impl Trait for Box<dyn Trait> {
fn consume(self) {
self.consume_boxed()
}
}
fn main() {
let obj = Box::new(Impl);
obj.consume();
let obj2: Box<dyn Trait> = Box::new(Impl);
obj2.consume();
}
Now, this implementation for Box<dyn Trait>
is still a bit restrictive, it only works with Box<dyn Trait + 'static>
right now. We can actually generalize further though anyways; we can make a generic implementation for any Box<T>
when T: ?Sized + Trait
....
impl<T> Trait for Box<T>
where
T: ?Sized + Trait,
{
fn consume(self) {
self.consume_boxed()
}
}
And we get a stack overflow... why? Because the
impl<T> TraitBoxed for T
where
T: Trait,
{
fn consume_boxed(self: Box<Self>) {
self.consume()
}
}
implementation now calls the implementation on Box
, quite circular! We need to make the previously implicit dereferencing explicit:
impl<T> TraitBoxed for T
where
T: Trait,
{
fn consume_boxed(self: Box<Self>) {
(*self).consume()
}
}
There we go, perfect solution:
trait Trait: TraitBoxed {
fn consume(self);
}
trait TraitBoxed {
fn consume_boxed(self: Box<Self>);
}
impl<T> TraitBoxed for T
where
T: Trait,
{
fn consume_boxed(self: Box<Self>) {
(*self).consume()
}
}
struct Impl;
impl Trait for Impl {
fn consume(self) {
println!("it works!")
}
}
impl<T> Trait for Box<T>
where
T: ?Sized + Trait,
{
fn consume(self) {
self.consume_boxed()
}
}
fn main() {
let obj = Box::new(Impl);
obj.consume();
let obj2: Box<dyn Trait> = Box::new(Impl);
obj2.consume();
}