Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass a boxed trait object by value in Rust?

I was writing some code and had a trait with a method that takes self by value. I want to call this method on a Box'd trait object (consuming the Box and its value). Is this possible? If so, how?

In terms of code, a minimal example looks like the following (incomplete) code:

trait Consumable {
    fn consume(self) -> u64;
}
fn consume_box(ptr: Box<dyn Consumable>) -> u64 {
    //what can I put here?
}

My question is how to fill in the function consume_box with the specified signature so that the value returned is whatever value would be gotten by calling consume on the Box'd value.

I had initially written

ptr.consume()

as the body of the function, though I realize this isn't quite the right idea, since it doesn't get across the fact that I want the Box to be consumed, not just its contents, but it's the only thing I could think of. This does not compile, giving an error:

cannot move a value of type dyn Consumable: the size of dyn Consumable cannot be statically determined

This was somewhat surprising to me, being new to Rust, I had thought that maybe the self argument was passed similarly to an rvalue reference in C++ (which is really what I want - in C++, I would probably implement this by a method with the signature virtual std::uint64_t consume() &&, letting a std::unique_ptr clean up the moved-from object via a virtual destructor), but I guess Rust is truly passing by value, moving the argument into place prior - so it's reasonable that it rejects the code.

Trouble is, I'm not sure how to get the behavior I want, where I can consume a Box'd trait object. I tried adding a method to the trait with a default implementation, thinking that might get me something useful in the vtable:

trait Consumable {
    fn consume(self) -> u64;
    fn consume_box(me: Box<Self>) -> u64 {
        me.consume()
    }
}

However, this then yields the error

the trait Consumable cannot be made into an object

when I mention the Box<dyn Consumable> type - which is not so surprising, since the compiler figuring out what to do with a function whose argument type varied with Self would have been miraculous.

Is it possible to implement the function consume_box with the provided signature - even modifying the trait if necessary?


If it's useful, more specifically, this is part of a sort of representation of some mathematical expressions - maybe a toy model would be that specific implementations that look roughly like:

impl Consumable for u64 {
    fn consume(self) -> u64 {
        self
    }
}
struct Sum<A, B>(A, B);
impl<A: Consumable, B: Consumable> Consumable for Sum<A, B> {
    fn consume(self) -> u64 {
        self.0.consume() + self.1.consume()
    }
}
struct Product<A, B>(A, B);
impl<A: Consumable, B: Consumable> Consumable for Product<A, B> {
    fn consume(self) -> u64 {
        self.0.consume() * self.1.consume()
    }
}
fn parse(&str) -> Option<Box<dyn Consumable> > {
    //do fancy stuff
}

where, for the most part, things are plain old data (but arbitrarily large blocks of it, potentially, due to the generics), but to also have this be compatible with passing around more opaque handles to these sorts of things - hence the desire to be able to work with Box<dyn Consumable>. At least at the language level, this is a good model of what sort of things I'm up to - the only resources owned by these objects are pieces of memory (nothing to do with multithreading and no self-referential shenanigans) - although this model doesn't capture that the use case I have is one where it's useful for the implementation to consume the object rather than to merely read it nor does it appropriately model that I want an "open" class of possible segments rather than a finite set of possiblities (making it hard to do something like an enum that represents a tree directly) - hence why I'm asking about passing by value rather than trying to rewrite it to pass by reference.

like image 419
Milo Brandt Avatar asked Mar 01 '23 20:03

Milo Brandt


1 Answers

You can consume from a Box<dyn Trait> if the parameter is self: Box<Self>:

trait Consumable {
    fn consume(self) -> u64;
    fn consume_box(self: Box<Self>) -> u64;
}

struct Foo;
impl Consumable for Foo {
    fn consume(self) -> u64 {
        42
    }
    fn consume_box(self: Box<Self>) -> u64 {
        self.consume()
    }
}

fn main() {
    let ptr: Box<dyn Consumable> = Box::new(Foo);
    println!("result is {}", ptr.consume_box());
}

However, this does have the annoying boilerplate of having to implement consume_box() for each implementation; trying to define a default implementation will run into a "cannot move value of type Self - the size of Self cannot be statically determined" error.


In general though this is not supported. A dyn Consumable represents an unsized type which are very limited except through indirection (via references or Box-like structs). It works for the above case because Box is a bit special (is the only dispatchable type you can take ownership from) and the consume_box method does not put self on the stack as a dynamic trait object (only in each implementation where its concrete).

However there is RFC 1909: Unsized RValues which hopes to loosen some of these limits. One being able to pass unsized function parameters, like self in this case. The current implementation of this RFC accepts your initial code when compiled on nightly with unsized_fn_params:

#![feature(unsized_fn_params)]

trait Consumable {
    fn consume(self) -> u64;
}

struct Foo;
impl Consumable for Foo {
    fn consume(self) -> u64 {
        42
    } 
}

fn main () {
    let ptr: Box<dyn Consumable> = Box::new(Foo);
    println!("result is {}", ptr.consume());
}

See on the playground.

like image 175
kmdreko Avatar answered Mar 05 '23 17:03

kmdreko