Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Rust Return self if not changed or new Self if changed (Copy on Write)

I have a struct with function remove, and I want the remove function to return new instance if actually removed or the same reference

But I get Cannot move when trying to return *self

Is it possible?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

From the errors I don’t think so, but then how Cow is possible.

Code:

use std::collections::HashMap;

#[derive(Clone)]
struct Something {
    data: HashMap<char, u16>,
}

impl Something {
    fn new() -> Self {
        return Something {
            data: HashMap::new()
        }
    }

    fn get(&self, c: char) -> Option<&u16> {

        return self.data.get(&c);
    }

    fn add(&self, c: char, value: u16) -> Self {
        let mut n = self.clone();

        n.data.insert(c, value);

        return n;
    }

    fn remove(&self, c: char) -> Self {
        if !self.data.contains_key(&c) {
            return *self; // <-- error[E0507]: cannot move out of `*self` which is behind a shared reference
        }

        let mut n = self.clone();
        n.data.remove(&c);

        return n;
    }
}

fn main() {
    let s = Something::new();

    let s = s.add('a', 1);
    let s = s.add('b', 2);

    let s = s.remove('a');

    // Should return the same instance as c is missing
    let s = s.remove('c');
}

but If I change the remove function to return &Self then I can’t return a reference to a variable declared in the function:

    fn remove(&self, c: char) -> &Self {
        if !self.data.contains_key(&c) {
            return self;
        }

        let mut n = self.clone();
        n.data.remove(&c);

        return &n;  // <--- error[E0515]: cannot return reference to local variable `n`
    }

I know I can avoid borrowing something but I don’t want to move ownership:

    // <--- self and not &self
    fn remove(self, c: char) -> Self {
        if !self.data.contains_key(&c) {
            return self;  
        }

        let mut n = self.clone();
        n.data.remove(&c);

        return n;
    }

Because then the following will fail:

fn main() {
    let s = Something::new();

    let s = s.add('a', 1);
    let s = s.add('b', 2);

    let s1 = s.remove('a');
    let s2 = s.remove('a'); // <-- error[E0382]: use of moved value: `s`
}

>Solution :

A &self->Self method will always have to clone. That’s a fundamental thing about ownership.

There are two possible solutions to this, besides the obvious one of "just clone":

  • Make it &self->&self. Of course, then you won’t be able to modify it, but Cow can solve that:

    use std::borrow::Cow;
    
    fn remove(&self, c: char) -> Cow<'_, Self> {
        if !self.data.contains_key(&c) {
            return Cow::Borrowed(self);
        }
    
        let mut n = self.clone();
        n.data.remove(&c);
    
        Cow::Owned(n)
    }
    
  • Use a cheaply cloneable data structure that also allows Copy-On-Write, such as Rc or Arc:

    use std::rc::Rc;
    use std::collections::HashMap;
    
    #[derive(Clone)]
    struct Something {
        data: Rc<HashMap<char, u16>>,
    }
    
    impl Something {
        fn new() -> Self {
            Something {
                data: Rc::new(HashMap::new()),
            }
        }
    
        fn get(&self, c: char) -> Option<&u16> {
            self.data.get(&c)
        }
    
        fn add(&self, c: char, value: u16) -> Self {
            let mut this = self.clone();
            let n = Rc::make_mut(&mut this.data);
            n.insert(c, value);
            this
        }
    
        fn remove(&self, c: char) -> Self {
            let mut this = self.clone();
    
            if !this.data.contains_key(&c) {
                return this;
            }
    
            let n = Rc::make_mut(&mut this.data);
            n.remove(&c);
            this
        }
    }
    
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading