4

I've been experimenting with using more complicated structs and I'm having trouble when trying to edit values using one struct when the value is contained in another struct. The goal of this is to be able write a simple abstractions from potential users point of view so that they just have one struct that they have to mutate and play with.

Example Code:

#[derive(Debug)]
pub struct Widget {
    counter: u16,
}

impl Widget{
    pub fn new() -> Widget {
        let nw = Widget {
            counter: 0
        };
        return nw;
    }
}

pub struct Market {
    widgets: Vec<Widget>
}

impl Market {
    pub fn new() -> Market {
        let market_vec = Vec::new();
        let market = Market {
            widgets: market_vec
        };
        return market;
    }

    pub fn new_user(&mut self) -> User {
        let user_widget = Widget::new();
        let user = User::new(user_widget);
        self.widgets.push(user_widget);
        return user;
    }
}

pub struct User {
    name: String,
    widget: Widget
}

impl User {
    pub fn new(user_widget: Widget) -> User {
        let user = User {
            name: "User1".to_string(),
            widget: user_widget
        };
        return user;
    }

    pub fn update_count(&mut self) {
        self.widget.counter +=1;
    }
}


pub fn main() {
    let mut market = Market::new();
    let mut user1 = market.new_user();
    println!("{:?}", market.widgets);
    user1.update_count();
    println!("{:?}", market.widgets);
}

Example Output:

  Compiling playground v0.0.1 (/playground)
error[E0382]: use of moved value: `user_widget`
  --> src/main.rs:31:27
   |
29 |         let user_widget = Widget::new();
   |             ----------- move occurs because `user_widget` has type `Widget`, which does not implement the `Copy` trait
30 |         let user = User::new(user_widget);
   |                              ----------- value moved here
31 |         self.widgets.push(user_widget);
   |                           ^^^^^^^^^^^ value used here after move

For more information about this error, try `rustc --explain E0382`.
error: could not compile `playground` due to previous error

In theory I would want the widget in user to be a reference to the widget but I would be unable to initialize user with a reference and then modify that reference. I've looked into trying to use Arc<T> or RC<T> but I'm unsure if I need to wrap both my vector that stores the widgets with these types and the user that references it. Can I just use it once in the User struct?

2
  • Rc is definitely the way to go (Arc is only necessary if you are using threads). You probably want to take a look at Mutex to enable mutability between your references. Commented Jun 9, 2022 at 19:18
  • Yes the reference needs to be mutable so that the counter can be update via the User and that change is reflected in the Markets vector as well Commented Jun 9, 2022 at 19:20

2 Answers 2

9

You are actually modifying the values through all these instances, which makes the problem a bit harder.


Background

The basic of ownership in Rust says three things:

  • Every object is owned by exactly one thing
  • Objects can be read by multiple immutable references
  • Object can be written to only by one mutable reference, and if a mutable reference exist, there cannot be any other references (including immutable ones).

This also applies to Rc and Arc, meaning, while they give access to multiple 'owners', they only do so immutably.

To actually then modify the values, you need to create interior mutability. This is usually done with RefCell in the single threaded case, or with Mutex in the multi threaded case.


Solution #1

Here is your code with Rc<RefCell>:

use std::{cell::RefCell, rc::Rc};

#[derive(Debug)]
pub struct Widget {
    counter: u16,
}

impl Widget {
    pub fn new() -> Widget {
        let nw = Widget { counter: 0 };
        return nw;
    }
}

pub struct Market {
    widgets: Vec<Rc<RefCell<Widget>>>,
}

impl Market {
    pub fn new() -> Market {
        let market_vec = Vec::new();
        let market = Market {
            widgets: market_vec,
        };
        return market;
    }

    pub fn new_user(&mut self) -> User {
        let user_widget = Rc::new(RefCell::new(Widget::new()));
        let user = User::new(user_widget.clone());
        self.widgets.push(user_widget);
        return user;
    }
}

pub struct User {
    name: String,
    widget: Rc<RefCell<Widget>>,
}

impl User {
    pub fn new(user_widget: Rc<RefCell<Widget>>) -> User {
        let user = User {
            name: "User1".to_string(),
            widget: user_widget,
        };
        return user;
    }

    pub fn update_count(&mut self) {
        self.widget.borrow_mut().counter += 1;
    }
}

pub fn main() {
    let mut market = Market::new();
    println!("{:?}", market.widgets);
    let mut user1 = market.new_user();
    user1.update_count();
    println!("{:?}", market.widgets);
}
[]
[RefCell { value: Widget { counter: 1 } }]

Solution #2

In your specific case, I noticed that the only thing you actually update is the counter.

Therefore, you wouldn't actually need to make the entire Widget mutable, but instead, you could make only the counter mutable. The counter is simpler then the Widget class, therefore we can optimize it a little.

In the single threaded case, we can use Cell. Cell is the same as RefCell, but cannot fail. But Cell only exists for copyable objects.

In the multi-threaded case, we can use AtomicU16. It is hugely more efficient than a Mutex; actually, it has zero overhead compared to a normal u16 in most cases.

Here is the solution with Cell<u16>:

use std::{cell::Cell, rc::Rc};

#[derive(Debug)]
pub struct Widget {
    counter: Cell<u16>,
}

impl Widget {
    pub fn new() -> Widget {
        let nw = Widget { counter: 0.into() };
        return nw;
    }
}

pub struct Market {
    widgets: Vec<Rc<Widget>>,
}

impl Market {
    pub fn new() -> Market {
        let market_vec = Vec::new();
        let market = Market {
            widgets: market_vec,
        };
        return market;
    }

    pub fn new_user(&mut self) -> User {
        let user_widget = Rc::new(Widget::new());
        let user = User::new(user_widget.clone());
        self.widgets.push(user_widget);
        return user;
    }
}

pub struct User {
    name: String,
    widget: Rc<Widget>,
}

impl User {
    pub fn new(user_widget: Rc<Widget>) -> User {
        let user = User {
            name: "User1".to_string(),
            widget: user_widget,
        };
        return user;
    }

    pub fn update_count(&mut self) {
        let prev = self.widget.counter.get();
        self.widget.counter.set(prev + 1);
    }
}

pub fn main() {
    let mut market = Market::new();
    println!("{:?}", market.widgets);
    let mut user1 = market.new_user();
    user1.update_count();
    println!("{:?}", market.widgets);
}
[]
[Widget { counter: Cell { value: 1 } }]

Thread safe versions

For completeness sake, here are the same solutions in a multi-threaded context.

With Arc<Mutex>:

use std::sync::{Arc, Mutex};

#[derive(Debug)]
pub struct Widget {
    counter: u16,
}

impl Widget {
    pub fn new() -> Widget {
        let nw = Widget { counter: 0 };
        return nw;
    }
}

pub struct Market {
    widgets: Vec<Arc<Mutex<Widget>>>,
}

impl Market {
    pub fn new() -> Market {
        let market_vec = Vec::new();
        let market = Market {
            widgets: market_vec,
        };
        return market;
    }

    pub fn new_user(&mut self) -> User {
        let user_widget = Arc::new(Mutex::new(Widget::new()));
        let user = User::new(user_widget.clone());
        self.widgets.push(user_widget);
        return user;
    }
}

pub struct User {
    name: String,
    widget: Arc<Mutex<Widget>>,
}

impl User {
    pub fn new(user_widget: Arc<Mutex<Widget>>) -> User {
        let user = User {
            name: "User1".to_string(),
            widget: user_widget,
        };
        return user;
    }

    pub fn update_count(&mut self) {
        self.widget.lock().unwrap().counter += 1;
    }
}

pub fn main() {
    let mut market = Market::new();
    println!("{:?}", market.widgets);
    let mut user1 = market.new_user();
    user1.update_count();
    println!("{:?}", market.widgets);
}
[]
[Mutex { data: Widget { counter: 1 }, poisoned: false, .. }]

With AtomicU16:

use std::{
    sync::atomic::{AtomicU16, Ordering},
    sync::Arc,
};

#[derive(Debug)]
pub struct Widget {
    counter: AtomicU16,
}

impl Widget {
    pub fn new() -> Widget {
        let nw = Widget { counter: 0.into() };
        return nw;
    }
}

pub struct Market {
    widgets: Vec<Arc<Widget>>,
}

impl Market {
    pub fn new() -> Market {
        let market_vec = Vec::new();
        let market = Market {
            widgets: market_vec,
        };
        return market;
    }

    pub fn new_user(&mut self) -> User {
        let user_widget = Arc::new(Widget::new());
        let user = User::new(user_widget.clone());
        self.widgets.push(user_widget);
        return user;
    }
}

pub struct User {
    name: String,
    widget: Arc<Widget>,
}

impl User {
    pub fn new(user_widget: Arc<Widget>) -> User {
        let user = User {
            name: "User1".to_string(),
            widget: user_widget,
        };
        return user;
    }

    pub fn update_count(&mut self) {
        self.widget.counter.fetch_add(1, Ordering::SeqCst);
    }
}

pub fn main() {
    let mut market = Market::new();
    println!("{:?}", market.widgets);
    let mut user1 = market.new_user();
    user1.update_count();
    println!("{:?}", market.widgets);
}
[]
[Widget { counter: 1 }]
Sign up to request clarification or add additional context in comments.

6 Comments

Solution #2 is what is what I believe I've been looking for. Thank you for including it.
Added multi-threaded versions now too. Glad I could help.
HUGELY smaller footprint. In x86, for example, all reads/writes to u32 are already atomic. AtomicU32 is mostly just an empty wrapper to inform the compiler about the mutability.
@JeremyMeadows just to illustrate: AtomicU32 vs Mutex<U32>. It's nuts how big the difference is. And that is compiled with -C opt-level=3
Wow that is a much bigger difference than what I would have expected, I guess it makes sense that mov would be atomic, though. Thanks for the examples :)
|
-1

Using Rc and Mutex, you can wrap each of your Widgets so that you pass around a mutable reference to that individual struct.

I modified your code a bit too so it was easier for me to work in, I hope is still plenty readable:

use std::rc::Rc;
use std::sync::Mutex;

#[derive(Debug)]
pub struct Widget {
    counter: u16,
}

impl Widget {
    pub fn new() -> Widget {
        Widget { counter: 0 }
    }
}

type MutWidget = Rc<Mutex<Widget>>;

pub struct Market {
    widgets: Vec<MutWidget>,
}

impl Market {
    pub fn new() -> Market {
        Market {
            widgets: Vec::new(),
        }
    }

    pub fn new_user(&mut self) -> User {
        let user_widget = Rc::new(Mutex::new(Widget::new()));
        
        let user = User::new(user_widget.clone());
        self.widgets.push(user_widget.clone());
        return user;
    }
}

pub struct User {
    name: String,
    widget: MutWidget,
}

impl User {
    pub fn new(user_widget: MutWidget) -> User {
        User {
            name: "User1".to_string(),
            widget: user_widget,
        }
    }

    pub fn update_count(&mut self) {
        self.widget.lock().unwrap().counter += 1;
    }
}

pub fn main() {
    let mut market = Market::new();
    println!("{:?}", market.widgets);
    
    let mut user1 = market.new_user();
    user1.update_count();
    
    println!("{:?}", market.widgets);
}

2 Comments

Hey :) I don't think the combination Rc<Mutex> makes much sense. Mutex is for thread-safety, for which you then need Arc instead of Rc. In the single-threaded case, RefCell is hugely more efficient than Mutex.
Definitely, I haven't hardly ever needed a Cell, and I didn't even remember they existed until reading your answer haha.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.