0

Safe Rust demands the following from all references:

  1. One or more references (&T) to a resource,
  2. Exactly one mutable reference (&mut T).

I want to have one Vec that can be read by multiple threads and written by one, but only one of those should be possible at a time (as the language demands).

So I use an RwLock.

I need a Vec<i8>. To let it outlive the main function, I Box it and then I RwLock around that, like thus:

fn main() {
    println!("Hello, world!");
    let mut v = vec![0, 1, 2, 3, 4, 5, 6];
    let val = RwLock::new(Box::new(v));
    for i in 0..10 {
        thread::spawn(move || threadFunc(&val));
    }
    loop {
        let mut VecBox = (val.write().unwrap());
        let ref mut v1 = *(*VecBox);
        v1.push(1);
        //And be very busy.
        thread::sleep(Duration::from_millis(10000));
    }
}
fn threadFunc(val: &RwLock<Box<Vec<i8>>>) {
    loop {
        //Use Vec
        let VecBox = (val.read().unwrap());
        let ref v1 = *(*VecBox);
        println!("{}", v1.len());
        //And be very busy.
        thread::sleep(Duration::from_millis(1000));
    }
}

Rust refuses to compile this:

 capture of moved value: `val`
   --> src/main.rs:14:43
      |
   14 |         thread::spawn(move || threadFunc(&val));
      |                       -------             ^^^ value captured here after move
      |                       |
      |                       value moved (into closure) here

Without the thread:

for i in 0..10 {
    threadFunc(&val);
}

It compiles. The problem is with the closure. I have to "move" it, or else Rust complains that it can outlive main, I also can't clone val (RwLock doesn't implement clone()).

What should I do?

Shepmaster
  • 274,917
  • 47
  • 731
  • 969
Charles Shiller
  • 873
  • 2
  • 9
  • 31

1 Answers1

2

Note that there's no structural difference between using a RwLock and a Mutex; they just have different access patterns. See Concurrent access to vector from multiple threads using a mutex lock for related discussion.

The problem centers around the fact that you've transferred ownership of the vector (in the RwLock) to some thread; therefore your main thread doesn't have it anymore. You can't access it because it's gone.

In fact, you'll have the same problem as you've tried to pass the vector to each of the threads. You only have one vector to give away, so only one thread could have it.

You need thread-safe shared ownership, provided by Arc:

use std::sync::{Arc, RwLock};
use std::thread;
use std::time::Duration;

fn main() {
    println!("Hello, world!");
    let v = vec![0, 1, 2, 3, 4, 5, 6];
    let val = Arc::new(RwLock::new(v));

    for _ in 0..10 {
        let v = val.clone();
        thread::spawn(move || thread_func(v));
    }

    for _ in 0..5 {
        {
            let mut val = val.write().unwrap();
            val.push(1);
        }
        thread::sleep(Duration::from_millis(1000));
    }
}

fn thread_func(val: Arc<RwLock<Vec<i8>>>) {
    loop {
        {
            let val = val.read().unwrap();
            println!("{}", val.len());
        }
        thread::sleep(Duration::from_millis(100));
    }
}

Other things to note:

  1. I removed the infinite loop in main so that the code can actually finish.
  2. I fixed all of the compiler warnings. If you are going to use a compiled language, pay attention to the warnings.
    • unnecessary parentheses
    • snake_case identifiers. Definitely do not use PascalCase for local variables; that's used for types. camelCase does not get used in Rust.
  3. I added some blocks to shorten the lifetime that the read / write locks will be held. Otherwise there's a lot of contention and the child threads never have a chance to get a read lock.
  4. let ref v1 = *(*foo); is non-idiomatic. Prefer let v1 = &**foo. You don't even need to do that at all, thanks to Deref.
Community
  • 1
  • 1
Shepmaster
  • 274,917
  • 47
  • 731
  • 969
  • Thanks, although wouldn't it be more idiomatic to `drop()` variables rather than to hack scope? – Charles Shiller Apr 24 '17 at 16:12
  • 1
    @CharlesShiller See [What are the options to end a mutable borrow in Rust?](http://stackoverflow.com/q/35765440/1233251). So no, blocks are as idiomatic as you can get. – SE_net4 the downvoter Apr 24 '17 at 16:20
  • @E_net4 in this case, `drop` should work because the lock doesn't look like a mutable borrow. I don't know that *either* is more idiomatic than the other here; `drop(vec)` just looks strange to me though. – Shepmaster Apr 24 '17 at 17:15
  • Also, maybe it's the C in me, but is `&**foo` different than `*foo`? – Charles Shiller Apr 24 '17 at 18:26
  • 1
    @CharlesShiller see http://stackoverflow.com/a/28552082/155423 for full details. TL;DR, Rust has the `Deref` trait, which means that `&*` isn't always a no-op. – Shepmaster Apr 24 '17 at 19:19