0

I'm quite new to rust and want to do two function in parallel.

shoot(&fleet_a, &mut fleet_b)
shoot(&fleet_b, &mut fleet_a)

since they only take information from one and modify some values of the other one this should be save to do. However it always complains that i am using them after moving, can I somehow tell the rust compiler that it is save to do so?

Here an example code:

use std::sync::Arc;
use std::thread;
pub struct Ship {
    pub hull: f32,
    pub shield: f32,
    pub info: Arc<ShipInfo>,
}
#[derive(Clone)]
pub struct ShipInfo {
    pub attack: f32,
    pub ship_id: usize,
}

fn shoot(attacker:&[Ship],defender:&mut [Ship]){
    //do some calculations here, reading the info of the attacker and changing values of the defender
}

fn main(){
    let sinfo=Arc::new( ShipInfo{
        attack:10.0,
        ship_id:1,
    });
    let mut ships_a:Vec<Ship>=Vec::new();
    let mut ships_b:Vec<Ship>=Vec::new();

    for _ in 0..100{
         ships_a.push(Ship{
            hull:42.0,
            shield:44.0,
            info:sinfo.clone(),
        });

    }
    for _ in 0..1000{
         ships_b.push(Ship{
            hull:44.0,
            shield:23.0,
            info:sinfo.clone(),
        });
    }

    shoot(&ships_a, &mut ships_b);
    shoot(&ships_b, &mut ships_a);

}

pub fn parallel_shooting(
    fleet_a: &mut [Ship],
    fleet_b: &mut [Ship],
) {

    let t1 = thread::spawn(move || unsafe {
        shoot(&fleet_a, &mut fleet_b);
    });

    let t2 = thread::spawn(move || unsafe {
        shoot(&fleet_b, &mut fleet_a);
    });

    t1.join().unwrap();
    t2.join().unwrap();
}
3
  • 2
    "they only take information from one and modify some values of the other one this should be save to do" - reading and writing simultaneously can yield undefined behavior and thus Rust does not allow this. You could partition your data so the defenders only have mutable access to the portions being mutated (I'm guessing hull and shield) while the attackers only have read access to the values being read (and crucially not be able to access the hull and shield in any way). Commented 2 days ago
  • 1
    Pretend you have a Vec with an element in it. Thread one has a & to it, and takes &v[0]. Thread two has a &mut to it, and it later does v.clear(). What happens when the first thread tries to use the reference? Does this help you understand why &mut access must be exclusive to all other access? Commented yesterday
  • 1
    Great that you found a solution, but do note that any answer belongs in an answer post (the post answer button below) not as an edit to the question. Commented yesterday

3 Answers 3

1

since they only take information from one and modify some values of the other one this should be save to do.

No, why would you think that this is safe? What if first shot wants to read data which the second shot modifies? That's exactly why Rust won't allow you to do that.

Such operation can be safe, if the dev is extra careful, and doesn't read and write into overlapping pieces of Ship. Rust however doesn't analyze the code that deeply (such deep analysis is not possible in general). It only looks at the signature. You want to use both immutable and mutable reference at the same time? Ain't going to happen.

So what can you do? There are several solutions to your problem:

  1. Instead of passing &[Ship] and &mut [Ship] decompose those structs into distinct components X, Y and pass &[X] and &mut [Y] respectively. Basically work with ECS style of coding.

  2. Pass &[Ship] everywhere, but wrap the pieces of Ship you want to modify with a mutex.

Sign up to request clarification or add additional context in comments.

Comments

1

This is not safe according to rust aliasing rules. If mutable reference exists, then no other reference can exist. Here both mutable and immutable reference exists at same time. Although in different thread.

You can wrap every ship with arc mutex,then put arc in vector. Then clone the vector and send to thread.

Gamedev in pure rust generally uses some ecs library to manage resources due to these kind of problems.

I see that you are using unsafe keyword here. If you want to ignore rust safety and want to manually ensure safety in unsafe block, cast references to pointer and make shoot fn accept pointers. Then you can write c like code with pointers and everything that comes with it.

Comments

0

The following code fixes the issue, however when one wants to make it nice maybe use pointers like the comments suggest.

use crossbeam::thread;
use std::cell::Cell;
use std::sync::Arc;
pub struct ThreadCell<T>(pub Cell<T>);
unsafe impl<T> Send for ThreadCell<T> {}
unsafe impl<T> Sync for ThreadCell<T> {}

struct Ship {
    hull: ThreadCell<f32>,
    info: Arc<ShipInfo>,
}
fn shoot(slicea: &[Ship],sliceb: &[Ship]){
// Shooting logic here
}

fn multishoot(slicea: &[Ship],sliceb: &[Ship]){
thread::scope(|s| {
    s.spawn(|_| {
        shoot(slicea, sliceb);
    });
    s.spawn(|_| {
        shoot(sliceb, slicea);
    });
})
.unwrap();
}

Comments

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.