I want to have multiple data containers for the same object with the same reference (pointer), and can't figure out how to achieve that in Rust. Go can simply use reference to the object as below:
type container struct {
id int
status string
}
m1 := make(map[int]*container)
m2 := make(map[int]*container)
c := &container{id: 1, status: "test"}
m1[1] = c
m2[1] = c
c.status = "changed"
fmt.Println(m1[1]) // &{1 changed}
fmt.Println(m2[1]) // &{1 changed}
I tried to use Box to allocate it in heap but cannot compile...
use std::{cell::Cell, collections::HashMap};
fn main() {
let mut m1: HashMap<i32, Box<Container>> = HashMap::new();
let mut m2: HashMap<i32, Box<Container>> = HashMap::new();
let mut c = Box::new(Container {
id: 1,
status: Cell::new(String::from("test")),
});
m1.insert(1, c);
m2.insert(1, c);
c.status.set("changed".to_string());
}
struct Container {
id: i32,
status: Cell<String>,
}
I used Cell to allow interior mutability but not relevant to this question... I want to learn how to create this Container object once, and allocate it on heap, and share the object across different data containers.
Instead of Box, you need to use Rc or Arc depending on your sync needs:
use std::rc::Rc;
use std::{cell::Cell, collections::HashMap};
fn main() {
let mut m1: HashMap<i32, Rc<Container>> = HashMap::new();
let mut m2: HashMap<i32, Rc<Container>> = HashMap::new();
let c = Rc::new(Container {
id: 1,
status: Cell::new(String::from("test")),
});
m1.insert(1, Rc::clone(&c));
m2.insert(1, Rc::clone(&c));
c.status.set("changed".to_string());
}
struct Container {
id: i32,
status: Cell<String>,
}
Playground
Related
From Rust documentation, this count variable wouldn't work without dereferencing (*)
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map);
However, I have the following code which tries to update a u8 variable (i.e team_val.goals_scored ) in a Struct if the key is found in a hashmap. It works without dereferencing. My understanding from the above Rust documentation was I need to dereference the team_val.goals_scored in order to update the content of the struct which is also a value for the hash map. Whelp!
My code:
#[derive(Debug)]
struct Team {
name: String,
goals_scored: u8,
goals_conceded: u8,
}
fn build_scores_table(results: String) -> HashMap<String, Team> {
// The name of the team is the key and its associated struct is the value.
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
// TODO: Populate the scores table with details extracted from the
// current line. Keep in mind that goals scored by team_1
// will be number of goals conceded from team_2, and similarly
// goals scored by team_2 will be the number of goals conceded by
// team_1.
let mut team_1_struct= Team {
name: team_1_name.clone(),
goals_scored: team_1_score,
goals_conceded: team_2_score
};
let mut team_2_struct= Team {
name: team_2_name.clone(),
goals_scored: team_2_score,
goals_conceded: team_1_score
};
if scores.contains_key(&team_1_name) {
let team_val = scores.entry(team_1_name.clone()).or_insert(team_1_struct);
println!("Yooo {:#?}",team_val);
team_val.goals_scored +=team_1_score;
team_val.goals_conceded += team_2_score;
} else {
scores.insert(team_1_name,team_1_struct);
}
if scores.contains_key(&team_2_name) {
let team_val = scores.entry(team_2_name.clone()).or_insert(team_2_struct);
println!("Yooo {:#?}",team_val);
team_val.goals_scored +=team_2_score;
team_val.goals_conceded += team_1_score;
} else {
scores.insert(team_2_name,team_2_struct);
}
}
scores
}
Rust does some automatic dereferencing, described here. We can see the difference between the documentation code and what you wrote:
// This
*count += 1
// versus this
team_val.goals_scored += team_1_score
^--- Causes an automatic dereferencing
If you're coming from C I think this documentation may be even clearer.
Answering the follow-up question 'can you use entry() without using clone() on the key - you cannot. entry() consumes what you send it, and doesn't give it back, so the borrow checker will prevent you from doing this. It's currently a 'limitation' on the API - but if you're dealing with something as cheap as a short string to copy, then this shouldn't impact you much.
You can do a fair amount to slim down your code, though (with the caveat that I only did this for one team - it's easily extensible):
use std::collections::HashMap;
struct Team {
name: String,
goals: u8
}
type Scorecard = HashMap<String, Team>;
fn scoring(records: String) -> Scorecard {
let mut scores : Scorecard = HashMap::new();
for r in records.lines() {
let record: Vec<&str> = r.split(',').collect();
let team_name = record[0].to_string();
let team_score: u8 = record[1].parse().unwrap();
// Note that we do not need to create teams here on every iteration.
// There is a chance they already exist, and we can create them only if
// the `or_insert()` clause is activated.
// Note that the `entry()` clause when used with `or_insert()`
// gives you an implicit 'does this key exist?' check.
let team = scores.entry(team_name.clone()).or_insert(Team {
name: team_name,
goals: 0,
});
team.goals += team_score;
}
scores
}
fn main() {
let record = "Thunderers,1\nFlashians,1\nThunderers,2";
let scores = scoring(record.to_string());
for (key, value) in &scores {
println!("{}: The mighty {} scored {} points.", key, value.name, value.goals)
}
// Flattening Some Options!
// let o = Some(Some(5));
// let p = Some(5);
// println!("o: {:#?}", o);
// println!("p: {:#?}", p);
// println!("flattened o: {:#?}", o.flatten());
// println!("flattened p: {:#?}", p.flatten());
}
The following code is used as an example of my problem. A structure named State contains a number of Residents.
Now there is a function that needs to modify both the State property and one of the Resident's properties.
Since it is not possible to get mutable borrows of State and one of the Residents in this State at the same time. The code can not compile.
I can think of two ways to solve it.
One is that just give only one parameter to modify_state_and_resident(): a mutable reference of State is provided. But I have to call the code to find Resident in hash map again in modify_state_and_resident(), which is expensive.
Another way is to split the State structure, splitting its properties and residents into separate variables. But this would bring other logical problems. After all, this is a complete entity that has to be referenced everywhere at the same time.
I don't know if there is a more perfect way to solve it.
#[derive(Debug)]
struct Resident {
age: i32,
name: String,
viewcnt: i32,
}
use std::collections::HashMap;
#[derive(Debug)]
struct State {
version: i32,
residents: HashMap<String, Resident>,
}
// This function cann not be invoked from modify_state_and_resident
fn show_resident(resident: &Resident) {
println!("{:?}", resident);
}
fn modify_state_and_resident(class: &mut State, resident: &mut Resident) {
// I do not want to call hash get again.
class.version = class.version + 1;
resident.viewcnt = resident.viewcnt + 1;
}
#[test]
fn whole_part_mutable() {
let mut s = State {
version: 1,
residents: HashMap::from([
(
String::from("this is a man who named Aaron"),
Resident{age: 18, name: String::from("Aaron"), viewcnt: 0}
),
])};
// get is expensive, I just want to call it when neccessary
let r = s.residents.get_mut("this is a man who named Aaron").unwrap();
// can not call from other function
show_resident(r);
modify_state_and_resident(&mut s, r);
}
You can destructure the State struct &mut to get individual access to both parts:
fn modify_state_and_resident(version: &mut i32, resident: &mut Resident) {
// I do not want to call hash get again.
*version = *version + 1;
resident.viewcnt = resident.viewcnt + 1;
}
#[test]
fn whole_part_mutable() {
let mut s = State {
version: 1,
residents: HashMap::from([
(
String::from("this is a man who named Aaron"),
Resident{age: 18, name: String::from("Aaron"), viewcnt: 0}
),
])};
let State { version, residents } = &mut s;
// get is expensive, I just want to call it when neccessary
let r = residents.get_mut("this is a man who named Aaron").unwrap();
// can not call from other function
show_resident(r);
modify_state_and_resident(version, r);
println!("{:?}", s);
}
Playground
Is it possible to borrow a mutable reference to the contents of a HashMap and use it for an extended period of time without impeding read-only access?
This is for trying to maintain a window into the state of various components in a system that are running independently (via Tokio) and need to be monitored.
As an example:
use std::sync::Arc;
use std::collections::HashMap;
struct Container {
running : bool,
count : u8
}
impl Container {
fn run(&mut self) {
for i in 1..100 {
self.count = i;
}
self.running = false;
}
}
fn main() {
let mut map = HashMap::new();
let mut container = Arc::new(
Box::new(
Container {
running: true,
count: 0
}
)
);
map.insert(0, container.clone());
container.run();
map.remove(&0);
}
This is for a Tokio-driven program where multiple operations will be happening asynchronously and visibility into the overall state of them is required.
There's this question where a temporary mutable reference can be borrowed, but that won't work as the run() function needs time to complete.
Based on suggestions from Jmb and Stargateur reworked this to use a RwLock internally. These internals could be reworked by having methods that manipulate them, but the basics are here:
use std::sync::Arc;
use std::sync::RwLock;
use std::collections::HashMap;
#[derive(Debug)]
struct ContainerState {
running : bool,
count : u8
}
struct Container {
state : Arc<RwLock<ContainerState>>
}
impl Container {
fn run(&self) {
for i in 1..100 {
let mut state = self.state.write().unwrap();
state.count = i;
}
{
let mut state = self.state.write().unwrap();
state.running = false;
}
}
}
fn main() {
let mut map = HashMap::new();
let state = Arc::new(
RwLock::new(
ContainerState {
running: true,
count: 0
}
)
);
map.insert(0, state);
let container = Container {
state: map[&0].clone()
};
container.run();
println!("Final state: {:?}", map[&0]);
map.remove(&0);
}
Where the key thing I was missing is you can have a mutable reference or multiple immutable references, and they're mutually exclusive. My initial understanding was that these two limits were independent.
I'm working on a game that involves a bunch of Beetle objects stored in a HashMap. Each beetle has a position, and it can also have a target id which is the key for another beetle in the hash. If a beetle has a target, it needs to move toward the target each time the game loop executes.
I can't perform the lookup of the target's current position, because you can't have a mutable and immutable borrow at the same. I get that, but any ideas how to restructure for my specific case?
I think I'm just getting caught up in how easy this would be in pretty much any other language, I can't see the idiomatic Rust way to do it. Here's a pretty minimal but complete example:
use std::collections::HashMap;
type Beetles = HashMap<i32, Beetle>;
struct Beetle {
x: f32,
y: f32,
target_id: i32,
}
impl Beetle {
fn new() -> Beetle {
Beetle {
x: 0.0,
y: 0.0,
target_id: -1,
}
}
}
fn main() {
let mut beetles: Beetles = HashMap::new();
beetles.insert(0, Beetle::new());
beetles.insert(1, Beetle::new());
set_target(&mut beetles, 0, 1);
move_toward_target(&mut beetles, 0);
}
fn set_target(beetles: &mut Beetles, subject_id: i32, target_id: i32) {
if let Some(subject) = beetles.get_mut(&subject_id) {
subject.target_id = target_id;
}
}
fn move_toward_target(beetles: &mut Beetles, beetle_id: i32) {
if let Some(subject) = beetles.get_mut(&beetle_id) {
if let Some(target) = beetles.get(&subject.target_id) {
// update subject position to move closer to target...
}
}
}
You could solve your specific problem by performing a double lookup for the subject. First, borrow immutably from the hash map to collect the information necessary for updating the subject. Then finally update the subject using the collected information by borrowing mutably from the hash map:
fn move_toward_target(beetles: &mut Beetles, beetle_id: i32) {
if let Some(subject_target_id) = beetles.get(&beetle_id).map(|b| b.target_id) {
let mut target_xy = None; // example
if let Some(target) = beetles.get(&subject_target_id) {
// collect information about target relevant for updating subject
target_xy = Some((target.x, target.y)) // example
}
let subject = beetles.get_mut(&beetle_id).unwrap();
// update subject using collected information about target
if let Some((target_x, target_y)) = target_xy{ // example
subject.x = target_x;
subject.y = target_y;
}
}
}
However, it is likely that you will run in similar and more complex problems with your beetles in the future, because the beetles are your central game objects, which you will likely want to reference mutably and immutably at the same time at several places in your code.
Therefore, it makes sense to wrap your beetles in std::cell::RefCells, which check borrow rules dynamically at runtime. This gives you a lot flexibility when referencing beetles in your hash map:
fn main() {
let mut beetles: Beetles = HashMap::new();
beetles.insert(0, RefCell::new(Beetle::new()));
beetles.insert(1, RefCell::new(Beetle::new()));
set_target(&mut beetles, 0, 1);
move_toward_target(&mut beetles, 0);
}
fn set_target(beetles: &mut Beetles, subject_id: i32, target_id: i32) {
if let Some(mut subject) = beetles.get_mut(&subject_id).map(|b| b.borrow_mut()) {
subject.target_id = target_id;
}
}
fn move_toward_target(beetles: &mut Beetles, beetle_id: i32) {
if let Some(mut subject) = beetles.get(&beetle_id).map(|b| b.borrow_mut()) {
if let Some(target) = beetles.get(&subject.target_id).map(|b| b.borrow()) {
//example for updating subject based on target
subject.x = target.x;
subject.y = target.y;
}
}
}
updated Beetles type:
type Beetles = HashMap<i32, RefCell<Beetle>>;
I could make a copy of this vector, but that will take time and memory.
I could write another println, but this is just an example — instead of println there may be several loops — and it will take space and complicate the code. I could perform the conversion in main and write two versions of the function call, but changing the reference is much easier.
fn foo(mut a: &Vec<i32>) {
let mut c: Vec<i32>;
if a[0] == 0 {
c = vec![1; 3];
a = &c;
}
println!("{:?}", a);
}
fn main() {
let a: Vec<i32> = vec![0; 3];
foo(&a);
}
Error:
main.rs:9:14: 9:15 error: `c` does not live long enough
main.rs:9 a = &c;
^
The rules of lifetime in rust are quite (very) strict: if you have a reference to an object, this object must live longer than the reference, in both direction.
This means the reference must be created after the object.
In your case, a exists before c, so the assignment a = &c is invalid. A simple fix can be to create a copy of the reference after c is created and work on this copy:
fn foo(vec_ref: &Vec<i32>){
let mut c: Vec<i32>;
let mut a = vec_ref
if a[0] == 0 {
c = vec![1; 3];
a = &c;
}
println!("{:?}",a);
}
or in a more rusty way:
fn foo(vec_ref: &Vec<i32>){
let mut c: Vec<i32>;
let a = if vec_ref[0] == 0 {
c = vec![1; 3];
&c
} else {
vec_ref
};
println!("{:?}",a);
}