1

I am new to threads in Rust. I am struggling to pass the RustBox type from the rustbox crate within threads.

I want to press the q key and have it display a + symbol for 2 secs at (1, 1) while I press w key within those 2 secs which shows another + symbol at (1, 2).

I wrote some code for the same logic:

extern crate rustbox;

use std::thread;
use std::time::Duration;

use rustbox::{Color, RustBox};
use rustbox::Key;


fn mark_at(x: usize, y: usize, rustbox: &RustBox) {
    rustbox.print(x, y, rustbox::RB_BOLD, Color::Black, Color::White, "+");
    thread::spawn(move || {
        let delay = Duration::from_millis(2000);
        thread::sleep(delay);
        rustbox.print(x, y, rustbox::RB_BOLD, Color::Black, Color::White, " ");
    });
}


fn main() {
    let rustbox = match RustBox::init(Default::default()) {
        Result::Ok(v) => v,
        Result::Err(e) => panic!("{}", e),
    };
    rustbox.print(1, 1, rustbox::RB_BOLD, Color::Black, Color::White, " ");
    rustbox.print(1, 2, rustbox::RB_BOLD, Color::Black, Color::White, " ");

    loop {
        rustbox.present();
        match rustbox.poll_event(false) {
            Ok(rustbox::Event::KeyEvent(key)) => {
                match key {
                    Key::Char('q') => {
                        mark_at(1, 1, &rustbox);
                    }
                    Key::Char('w') => {
                        mark_at(1, 2, &rustbox);
                    }
                    Key::Esc => { break; }
                    _ => { }
                }
            },
            Err(e) => panic!("{}", e),
            _ => { }
        }
    }
}

It gives me:

error[E0277]: the trait bound `*mut (): std::marker::Sync` is not satisfied in `rustbox::RustBox`
  --> src/main.rs:12:5
   |
12 |     thread::spawn(move || {
   |     ^^^^^^^^^^^^^ `*mut ()` cannot be shared between threads safely
   |
   = help: within `rustbox::RustBox`, the trait `std::marker::Sync` is not implemented for `*mut ()`
   = note: required because it appears within the type `std::marker::PhantomData<*mut ()>`
   = note: required because it appears within the type `rustbox::RustBox`
   = note: required because of the requirements on the impl of `std::marker::Send` for `&rustbox::RustBox`
   = note: required because it appears within the type `[closure@src/main.rs:12:19: 16:6 rustbox:&rustbox::RustBox, x:usize, y:usize]`
   = note: required by `std::thread::spawn`

error: aborting due to previous error

How do I implement Sync for the RustBox type so that above code could work?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
ritiek
  • 2,477
  • 2
  • 18
  • 25
  • 1
    Do you understand the traits [`Send` and `Sync`](https://doc.rust-lang.org/book/second-edition/ch16-04-extensible-concurrency-sync-and-send.html)? It's best that you understand the implications of a type not being `Sync`. One does not just implement `Sync`. Relevant question: https://stackoverflow.com/q/36649865/1233251 – E_net4 Oct 20 '17 at 14:32

1 Answers1

1

RustBox doesn't implement Send, so there is no (safe) way to share it between threads (it seems you already found the not yet merged pull request https://github.com/gchp/rustbox/pull/65 which would support Send).

If the pull request gets merged you could wrap the RustBox in a Mutex, i.e. Mutex<RustBox>, and references to that can be shared between threads.

But then you'll hit lifetime issues: your rustbox reference doesn't live long enough to spawn a new thread using it, so you'll have to wrap it in Arc.

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

fn mark_at(x: usize, y: usize, rustbox: &Arc<Mutex<RustBox>>) {
    rustbox.lock().unwrap().print(x, y, rustbox::RB_BOLD, Color::Black, Color::White, "+");
    let rustbox = rustbox.clone(); // increment reference counter
    thread::spawn(move || {
        let delay = Duration::from_millis(2000);
        thread::sleep(delay);
        rustbox.lock().unwrap().print(x, y, rustbox::RB_BOLD, Color::Black, Color::White, " ");
    });
}

In your main function you'll have to wrap the rustbox:

let rustbox = Arc::new(Mutex::new(rustbox));

and lock() it every time you use it.

Make sure you don't keep the lock too long; it might help to use helper variables to make this more explicit, e.g:

let pe = rustbox.lock().unwrap().poll_event(false);
match pe {
// ...
}
ritiek
  • 2,477
  • 2
  • 18
  • 25
Stefan
  • 5,304
  • 2
  • 25
  • 44
  • Hi, thanks for response! I tried out as you said and it indeed seems to work with (at the moment) unmerged PR. However, I also tried running without using the helper variable `pe` but it would totally ignore any input key I press. Why is it necessary to use helper variable here? – ritiek Oct 31 '17 at 16:13
  • 2
    Sadly I don't have a reference for this; but the temporary `MutexGuard` returned by `lock()` is only dropped after the "current statement" is finished; if you lock in the `match` it will keep the lock until the match is over, which means it will deadlock in `mark_at` called within the match. With a `let` statement the lock is dropped after the `let`. – Stefan Oct 31 '17 at 16:52
  • Have a look at it yourself in https://play.rust-lang.org/?gist=94690e7a2bec2c6b11b26c50fd3d9c3c&version=stable - I faked a Mutex implementation with logging on `lock()` and the `Drop`. – Stefan Oct 31 '17 at 16:53
  • That makes sense. Thanks again for the help! :) – ritiek Oct 31 '17 at 16:59
  • 1
    @Stefan It's not quite after the "current statement". When the `MutexGuard` is dropped, it unlocks. If the `MutexGuard` is put into a variable, the variable will be dropped at the end of the *scope*. If it's not put into a variable, it will be dropped immediately. – Shepmaster Oct 31 '17 at 19:28
  • @Shepmaster "Temporary" as in "not put into a variable". Define "immediately"... I think your comment doesn't help understanding the issue, so I created a new question https://stackoverflow.com/questions/47044453/what-is-the-scope-of-unnamed-values – Stefan Oct 31 '17 at 21:00