luvguru69
luvguru69

Reputation: 55

How Can I Get Two Structs in Rust to Use and Share a reference to Another Struct

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?

Upvotes: 3

Views: 2474

Answers (2)

Jeremy Meadows
Jeremy Meadows

Reputation: 2561

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);
}

Upvotes: -1

Finomnis
Finomnis

Reputation: 22396

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 }]

Upvotes: 6

Related Questions