3

(Following "cannot move out of borrowed content" when replacing a struct field)

I have a third-party API where one struct have a method that consumes the instance and returns a new instance; I want to wrap this API in my own wrapper and abstract away the detail that the struct is consumed in the operation.

This example explains what I'm trying to achieve:

// Third-party API
struct Item {
    x: u32,
}

impl Item {
    pub fn increment(self, amount: u32) -> Self {
        Item { x: self.x + amount }
    }
}

// My API
struct Container {
    item: Item,
}

impl Container {
    pub fn increment_item(&mut self, amount: u32) {
        // This line causes "cannot move out of borrowed content" but this is exactly what I want to do
        self.item = self.item.increment(amount);
    }
}

While now I understand the error I'm wondering how can I implement this without taking ownership of self inside Container::increment_item.

Proposed solutions:

  • Change Item::increment to take &mut self instead of self: I can't, Item::increment comes from a crate.
  • Use mem::replace (from here): Unfortunately constructing an Item instance it's not that easy, and to be honest I don't completely understand how the mem::replace solutions works.
  • Change Container::increment_item to take self instead of &mut self: I don't want to consume the Container instance in the process, I'm trying to design my wrapper to be as ergonomic as possible and abstract away completely the fact that Item must be consumed when changing it.

Some ideas on how to do it? Or am I trying an impossible design in Rust?

Terseus
  • 2,082
  • 15
  • 22
  • Why not changing the signature of `increment_item` to take `self` instead of `&mut self`? – hellow Jan 21 '19 at 15:35
  • @hellow Because I don't want to consume the `Container` instance in the process, I'm trying to design my wrapper to be as ergonomic as possible and abstract away *completely* the fact that `Item` must be consumed when changing it; I'll add it to the description. – Terseus Jan 21 '19 at 15:38
  • You can use an `Option` to `mem::replace` it with `None`, or you can `mem::replace` with a zeroed memory zone (unsafe). – Boiethios Jan 21 '19 at 15:42
  • 1
    @Boiethios: Why `mem::replace`, just `take`! :) – Matthieu M. Jan 21 '19 at 15:43
  • See the duplicate, and specifically [Shepmaster's answer](https://stackoverflow.com/a/41370552/147192), which illustrates how to use an option for your use case. – Matthieu M. Jan 21 '19 at 15:54
  • See also [Temporarily move out of borrowed content](https://stackoverflow.com/q/29570781/155423); [How do I move out of a struct field that is an Option?](https://stackoverflow.com/q/52031002/155423). – Shepmaster Jan 21 '19 at 15:55
  • @Caio how would that help? OP specifically states that they don't want to take `self` by value. – Shepmaster Jan 21 '19 at 15:56
  • @Shepmaster Sorry for the bad comment. It should be an additional `ContainerBuilder` struct instead. Deleted – Caio Jan 21 '19 at 16:16

1 Answers1

4

The simplest way is to use an Option:

  • use take to take ownership of the item,
  • then assign back Some(...).

If a panic occurs while the Option is empty, this is perfectly safe. No double-destruction occurs, and the container can even remain usable if you so desire.

Or in code:

// Third-party API
struct Item {
    x: u32,
}

impl Item {
    pub fn increment(self, amount: u32) -> Self {
        Item { x: self.x + amount }
    }
}

// My API
struct Container {
    item: Option<Item>,
}

impl Container {
    pub fn increment_item(&mut self, amount: u32) {
        let item = self.item.take().unwrap();
        self.item = Some(self.item.increment(amount));
    }
}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 1
    That's the way to do this, but I dislike to use `unwrap` each time I must use the field. – Boiethios Jan 21 '19 at 15:44
  • Ey this looks like a good solution, I'll try it and accept the answer when I have some time, thanks! – Terseus Jan 21 '19 at 16:00
  • Just asking to learn, are we going to give answer the questions even they are duplicate? Or do we just flag them as duplicate? – Akiner Alkan Jan 21 '19 at 17:02
  • 1
    @AkinerAlkan: It's better to flag as duplicate and not answer; but if a duplicate is only uncovered after an answer is made, then so be it. I should have waited for Shepmaster to check the question first, he's got a built-in duplicate detector ;) – Matthieu M. Jan 21 '19 at 17:31
  • 1
    @Matthieu M.: I am recently started actively contributing SO as habbit and I can say that he is pretty much idol to me in here rust community :) – Akiner Alkan Jan 21 '19 at 17:42