Need explanation on how to avoid this move out of a Box<dyn ...>

Hi,

Here's a condensed version of a piece of code I'm trying to run:

struct Thread {}
struct TriggerOne {
    thread: Thread
}
trait Trigger {
    fn thread(self) -> 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 {} })];
    let v2: Vec<Thread> = v.into_iter().map(|t| t.thread()).collect();
}

Essentially I have various types of "triggers", which once initialised store references to thread handles. I'm happy with the thread method, which returns said handle, consuming self, as I no longer need the trigger value after that. However Rust complains that

error[E0161]: cannot move a value of type `dyn Trigger`
  --> src/main.rs:17:49
   |
17 |     let v2: Vec<Thread> = v.into_iter().map(|t| t.thread()).collect();
   |                                                 ^^^^^^^^^^ the size of `dyn Trigger` cannot be statically determined

I'm trying to wrap my head around what this means. I think it means that I'm trying to move the Trigger out of the Box, since I want to move the Thread and the Thread belongs to the Trigger. But... the Thread I'm trying to return is not a trait object, it's just a good old sized value. So why can't I make this move? And what would be the clean way to resolve this?

Thanks in advance! I've had quite a few days of good progress on my little project but now my brain has exploded again!

1 Like

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

5 Likes

Hi @steffahn !

Thank you so, so much!

For my immediate needs I've gone with the first solution. It's funny because I'm reading the "Programming Rust" book to retake what I've learned through the "Rust programming language" book some time ago, and I just found out about using self: Box<Self> a week ago; yet it did NOT click into my mind when I stumbled upon this today. It's the perfect application, so this was quite timely. I think that for my use case it is fine to assume that Triggers will always be boxed.

That said I've read the other options you've proposed with interest, and I must say my mind is blown. I can more or less follow the explanation (super clear! :clap: ) but that's very far from the kind of programming I could come up with myself at this point. I've got a long learning curve still ahead of me!

Thank you again for such a quick, yet complete and clear answer.

1 Like

There are some more dyn Trait related examples here, if you're interested.[1]


  1. impl Trait for Box<dyn Trait> includes the self: Box<Self> workaround ↩︎

1 Like

Thank you @quinedot! It looks quite useful! I'll bookmark this and revisit after I'm through reading the Programming Rust book.

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.