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
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.
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
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?