Transforming (consume+set) struct property

How can you change a property of a struct with a value resulted from the mutable consumption (take ownership) of the property (on the same thread)?

The best I could come up with is

struct Foo {
    arr: Vec<u8>,
}

impl Foo {
    fn change(&mut self) {
        self.arr = self.arr.into_iter().collect();
    }
}

but this will create an additional default item :frowning_face:

The Default implementation for Vec<T> calls Vec::new, which is basically free (doesn't allocate any memory), so at least if your field is a Vec I wouldn't worry about this. Default implementations for other types are often comparably cheap.

Also note your change could be a method on Foo taking &mut self—it doesn't need to take ownership of its argument.

4 Likes

You're asking for something like this. The general problem is that if you take out of the reference, then panic while constructing a replacement, you need a default object in place in order to avoid a double-free during the stack unwinding. So you must either create a default value, or decide that you will abort on panic rather than unwind.

2 Likes

Yet another solution is to wrap the type inside an Option and use Option::take to steal the Some-valued field, leaving a None in its place. This is basically a one-stop shop for the "default object" pattern, even for non-Default types.

1 Like

In this case you can just move out of arr :slightly_smiling_face::

fn change(mut foo: Foo) {
    foo.arr = foo.arr.into_iter().map(...).collect();
}

The real case my be more complicated, of course.

1 Like

Unfortunately you cant move arr. I updated the example so it's more clear now.

It basically says:

error[E0507]: cannot move out of `self.arr` which is behind a mutable reference
  --> src/runtime/worker/worker.rs:70:20
   |
70 |         self.arr = self.arr.into_iter().collect();
   |                    ^^^^^^^^ move occurs because `self.arr` has type `Vec<u8>`, which does not implement the `Copy` trait

Apparently it's possible by using a third party module, but it comes with a cost, check @skysch's answer. As I am new to Rust I would rather just use standard features.

As pointed out by @cole-miller creating an empty vector with new is rather inexpensive, so I will go with this.

Another option would be to use Option (as pointed out by @H2CO3), but it would mean checking every time for None.

Thank you everyone for your help!

1 Like

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.