Problems with Attempts at Type Erasure using Boxes

Given a trait definition like the one below:

trait ExampleTrait<T> {
    // Return types are arbitrary for this simplified example
    fn borrowing_method(&mut self, val: T) -> bool;
    fn owning_method(self) -> T;
}

I am having issues with code like the following:

// obtain_vec() is stand-in for other code
// Returned vector is a collection of heterogenous trait objects
let collection: Vec<Box<dyn ExampleTrait<String>>> = obtain_vec();
for mut item in collection {
    // borrowing_method works fine as reference/pointer has constant size
    if item.borrowing_method("string".to_owned()) {
        // error[E0161]: cannot move a value of type dyn ExampleTrait<String>:
        // the size of dyn ExampleTrait<String> cannot be statically determined
        println!("{}", item.owning_method());
    }
}

Is there a way to call owning trait methods like owning_method on heterogeneous collections of trait objects?

Workarounds that I considered but that don't really work for me:

  • An enum over all possible implementations of ExampleTrait: this requires more code maintainence and would not handle external implementations of ExampleTrait on objects outside this code base
  • Downcasting the Box<dyn ExampleTrait>: runs into the same issues as the enum solution
  • Changing the type signature of owning_method to borrow the object: taking ownership (and then dropping the object upon completion of the method) is required for correctness in the code base where I extracted this example from

You can do:

-    fn owning_method(self) -> T;
+    fn owning_method(self: Box<Self>) -> T;

This is a weird hole in the language where you can end up with a dyn Trait that implements a method that is impossible to call (for some definition of "implements"), because you can't pass dynamically sized types like dyn Trait by value. I think it's accepted because some day it might be possible to do so? [Edit: yes, see below] Compare and contrast what happens when you put a Sized bound on the method.

(I say "hole in the language" because you can't necessarily work around it for DSTs other than dyn Trait using the Sized bound. You need to split up your trait or have a default implementation for the owning_method that all DSTs inherit.)

Edit: Found it:

2 Likes

FWIW, the usized_rvalues RFC may be reduced to "just" unsized_fn_params (which still covers this use case), and it seems it would act as mere sugar for the long-discussed &move references still lacking in the language:

fn owning_method(self: &move Self) -> T;

This way we do feature the necessary indirection for object safety, while avoiding the heap-allocation, which ought to be a soothing sight for #![no_std] / ]::alloc-less environments such as embedded :slightly_smiling_face:

1 Like

Perhaps a little off-topic, but could you perhaps provide some more info on that? The very concept makes little sense to me: if you need to move a value, just take ownership of it (either through some allocating type like a Box or Arc/Rc, or directly), borrows are simply the wrong tool for that. What value would move refs even provide?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.