3

I'm building an ncurses interface for a little thing I'm working on. For the input, I want to read it non blocking. I figured I could wrap the WINDOW in an Arc Mutex but this doesn't seem to work, as it still complains about send. Is this because the implementation of ncurses is unsafe? How can I solve this? Ideally, I'd have this work with a callback instead of the tx, so I can cut the dependency from the view up the stack, but I couldn't get that closure to Send either.

I'm using this library: https://github.com/jeaye/ncurses-rs

Simplified code:

pub struct View {
    max_x: i32,
    max_y: i32,
    messages_window: WINDOW,
    input_window: Arc<Mutex<WINDOW>>
}

pub fn init(&mut self, input_tx: mpsc::Sender<DispatchMessage>) {
        let input_window = self.input_window.clone();
        thread::spawn(move || {
            loop {
                let input_window = input_window.lock().unwrap();
                draw_prompt(input_window);
                let input = prompt_input(input_window);
                input_tx.send(input_to_message(input)).unwrap();
            }
        });
}

fn prompt_input(input: WINDOW) -> String {
    let mut string = String::new();
    wgetstr(input, &mut string);
    string
}

fn draw_prompt(input: WINDOW) {
    wclear(input);
    let top = 0 as chtype;
    let bottom = ' ' as chtype;
    let empty = ' ' as chtype;
    wborder(input, empty,empty,top,bottom,empty,empty,empty,empty);
    mvwprintw(input, 1, 1, ">> ");
    wrefresh(input);
}

And the errors I get:

src/view.rs:40:33: 40:45 error: mismatched types:
 expected `*mut ncurses::ll::WINDOW_impl`,
    found `std::sync::mutex::MutexGuard<'_, *mut ncurses::ll::WINDOW_impl>`
(expected *-ptr,
    found struct `std::sync::mutex::MutexGuard`) [E0308]
src/view.rs:40                     draw_prompt(input_window);
                                               ^~~~~~~~~~~~
note: in expansion of closure expansion
src/view.rs:37:27: 44:14 note: expansion site
src/view.rs:40:33: 40:45 help: run `rustc --explain E0308` to see a detailed explanation
src/view.rs:41:46: 41:58 error: mismatched types:
 expected `*mut ncurses::ll::WINDOW_impl`,
    found `std::sync::mutex::MutexGuard<'_, *mut ncurses::ll::WINDOW_impl>`
(expected *-ptr,
    found struct `std::sync::mutex::MutexGuard`) [E0308]
src/view.rs:41                     let input = prompt_input(input_window);
                                                            ^~~~~~~~~~~~
note: in expansion of closure expansion
src/view.rs:37:27: 44:14 note: expansion site
src/view.rs:41:46: 41:58 help: run `rustc --explain E0308` to see a detailed explanation
src/view.rs:37:13: 37:26 error: the trait `core::marker::Send` is not implemented for the type `*mut ncurses::ll::WINDOW_impl` [E0277]
src/view.rs:37             thread::spawn(move || {
                           ^~~~~~~~~~~~~
src/view.rs:37:13: 37:26 note: `*mut ncurses::ll::WINDOW_impl` cannot be sent between threads safely
src/view.rs:37             thread::spawn(move || {
                           ^~~~~~~~~~~~~
error: aborting due to 3 previous errors

Manually dereferencing the window removes the type errors, but I figured I'd leave it in as it might be an indication of what's wrong.

Timon Vonk
  • 439
  • 3
  • 11
  • does the playpen work with external crates? I remember it didn't. – Timon Vonk Jun 11 '15 at 07:16
  • Like Chris Morgan pointed out, the problem is a raw pointer, which does not support send. So figuring out how to implement send for a raw pointer (or wrap it in something that makes it so) is the way to go. I'm not sure how. – Timon Vonk Jun 11 '15 at 08:40
  • i think the safer alternative is to do all ncurses stuff in a thread and use channels to communicate with that thread. then your main thread simply sends commands to the ncurses thread instead of using ncurses directly – oli_obk Jun 11 '15 at 08:46
  • The IO needs to be non blocking as messages are written while it might be waiting for input. – Timon Vonk Jun 11 '15 at 09:14
  • well... this might blow up in your face if used wrongly, but you can always do `unsafe impl Send for Wrapper {}` where `Wrapper` contains your pointer. Basically you are guaranteeing the compiler that you checked and there's not way this is thread-unsafe. If you did not do that, then the compiler is permitted to eat your laundry – oli_obk Jun 11 '15 at 09:18
  • This will be the perefered approach for now. There's some dark corners of the web covering non blocking IO in ncurses, but I don't see that working. Maybe it's worth experimenting with creating the window in the thread instead and see how ncurses handles it. Thanks for the help! – Timon Vonk Jun 11 '15 at 13:57
  • After some searching, https://stackoverflow.com/questions/14082887/ncurses-and-realtime-implemented-in-c-unix is probably a better solution. – Timon Vonk Jun 11 '15 at 14:30

1 Answers1

4

Arc<T> implements Send where T implements both Send and Sync. Mutex<T> implements Send and Sync where T implements Send. So Arc<Mutex<T>> only implements Send if T implements Send. Remember that Send means “Types able to be transferred across thread boundaries.” Arc<Mutex<T>> allows access to its contents from multiple threads, purely taking care of ownership and mutability issues, so if the underlying type cannot be transferred across thread boundaries it won’t help. You may well need to do all your ncurses operations from one thread.

Raw pointers explicitly do not implement Send because there can be no guarantees about it. You can construct types on top of it which explicitly implement Send, thus providing a guarantee that the contained raw pointer is in fact safe for transferring across thread boundaries.

Chris Morgan
  • 86,207
  • 24
  • 208
  • 215