The problem is not the return value but the function argument. Rust doesn’t allow you to call a fn f(self)
function on a dyn Trait
-type like dyn Trigger
.
There are unstable features that would allow this (these kind of features e.g. power the implementation of FnOnce()
for Box<dyn FnOnce()>
) and your code would compile using nightly and that feature – but for stable Rust, one needs to employ some workarounds:
The workaround that can save you here is that trait methods are allowed to declare not only self
and &self
/&mut self
as method receiver types, but also self: Box<Self>
. This way, a Box<Self>
version of your thread
method can be used for the use case of trait objects. The most straightforward approach would then look like this:
struct Thread {}
struct TriggerOne {
thread: Thread
}
trait Trigger {
fn thread(self: Box<Self>) -> Thread;
}
impl Trigger for TriggerOne {
fn thread(self: Box<Self>) -> Thread {
self.thread
}
}
fn main() {
let v: Vec<Box<dyn Trigger>> = vec![Box::new(TriggerOne { thread: Thread {} })];
let v2: Vec<Thread> = v.into_iter().map(|t| t.thread()).collect();
}
and this should be fine if you are okay with always calling the .thread()
method on boxed values only – so e.g. in particular if all use-cases shall employ the Box<dyn Trigger>
trait object anyways.
Otherwise it may be desirable to have a non-boxed version, too, resulting in two methods:
struct Thread {}
struct TriggerOne {
thread: Thread
}
trait Trigger {
fn thread(self) -> Thread;
fn thread_box(self: Box<Self>) -> Thread;
}
impl Trigger for TriggerOne {
fn thread(self) -> Thread {
self.thread
}
fn thread_box(self: Box<Self>) -> Thread {
self.thread()
}
}
fn main() {
let v: Vec<Box<dyn Trigger>> = vec![Box::new(TriggerOne { thread: Thread {} })];
let v2: Vec<Thread> = v.into_iter().map(|t| t.thread_box()).collect();
}
This approach has some downsides that can be improved upon:
Firstly, the implementation now features the boilerplate
fn thread_box(self: Box<Self>) -> Thread {
self.thread()
}
and a default implementation in Trigger
doesn’t work:
trait Trigger {
fn thread(self) -> Thread;
fn thread_box(self: Box<Self>) -> Thread {
self.thread()
}
}
error[E0161]: cannot move a value of type `Self`
--> src/main.rs:8:9
|
8 | self.thread()
| ^^^^ the size of `Self` cannot be statically determined
Secondly, the call site for the Box<dyn Trigger>
now needs to use the other thread_box
method.
Both problems can be addressed, let’s start with the second. To solve this, it suffices to provide an implementation for Box
so that .thread()
can be a fn …(self)
method on the Box
:
impl<T: Trigger + ?Sized> Trigger for Box<T> {
fn thread(self) -> Thread {
self.thread_box() // calls the implementation for `T`
}
fn thread_box(self: Box<Self>) -> Thread {
(*self).thread() // without dereferencing, this would indirectly call itself
}
}
Rust Playground
For the first problem, which now got even worse as the correct default implementation requires the somewhat ugly (*self).thread()
, the approach to solve the issue is via splitting the trait into two. If the thread_box
method is moved into a supertrait of Trigger
, there can be a default implementation only for Sized
types. This results in the final version of the code:
struct Thread {}
struct TriggerOne {
thread: Thread
}
trait TriggerBoxed {
fn thread_box(self: Box<Self>) -> Thread;
}
impl<T: Trigger> TriggerBoxed for T {
fn thread_box(self: Box<Self>) -> Thread {
(*self).thread()
}
}
trait Trigger: TriggerBoxed {
fn thread(self) -> Thread;
}
impl<T: Trigger + ?Sized> Trigger for Box<T> {
fn thread(self) -> Thread {
self.thread_box()
}
}
// implementation only needs to write `thread`
impl Trigger for TriggerOne {
fn thread(self) -> Thread {
self.thread
}
}
fn main() {
let v: Vec<Box<dyn Trigger>> = vec![Box::new(TriggerOne { thread: Thread {} })];
// call works fine with just calling `.thread()`
let v2: Vec<Thread> = v.into_iter().map(|t| t.thread()).collect();
}
Rust Playground