4

Preface: I have done my research and know that it is really not a good idea/nor is it idiomatic Rust to have one. Completely open to suggestions of other ways to solve this issue.

Background: I have a console application that connects to a websocket and once connected successfully, the server sends a "Connected" message. I have the sender, and the receiver is separate threads and all is working great. After the connect() call a loop begins and places a prompt in the terminal, signaling that the application is ready to receive input from the user.

Problem: The issue is that the current flow of execution calls connect, and then immediately displays the prompt, and then the application receives the message from the server stating it is connected.

How I would solve this in higher level languages: Place a global bool (we'll call it ready) and once the application is "ready" then display the prompt.

How I think this might look in Rust:

//Possible global ready flag with 3 states (true, false, None)
let ready: Option<&mut bool> = None;

fn main(){
    welcome_message(); //Displays a "Connecting..." message to the user

    //These are special callback I created and basically when the
    //message is received the `connected` is called.
    //If there was an error getting the message (service is down)
    //then `not_connected` is called. *This is working code*
    let p = mylib::Promise::new(connected, not_connected);

    //Call connect and start websocket send and receive threads
    mylib::connect(p);

    //Loop for user input
    loop {
        match ready {
            Some(x) => {
                if x == true { //If ready is true, display the prompt
                    match prompt_input() {
                        true => {},
                        false => break,
                    }
                } else {
                    return; //If ready is false, quit the program
                }
            },
            None => {} //Ready is None, so continue waiting
        }
    }
}

fn connected() -> &mut bool{
    println!("Connected to Service! Please enter a command. (hint: help)\n\n");
    true
}

fn not_connected() -> &mut bool{
    println!("Connection to Service failed :(");
    false
}

Question: How would you solve this issue in Rust? I have tried passing it around to all the libraries method calls, but hit some major issues about borrowing an immutable object in a FnOnce() closure.

SnareChops
  • 13,175
  • 9
  • 69
  • 91
  • While I'd encourage you do *not* use global mutable state, I'd be remiss if I didn't at least point to [this answer](http://stackoverflow.com/questions/27791532/how-do-i-create-a-global-mutable-singleton) that shows you how to do just that. – Shepmaster Feb 09 '15 at 02:45

1 Answers1

3

It really sounds like you want to have two threads that are communicating via channels. Check out this example:

use std::thread;
use std::sync::mpsc;
use std::time::Duration;

enum ConsoleEvent {
    Connected,
    Disconnected,
}

fn main() {
    let (console_tx, console_rx) = mpsc::channel();

    let socket = thread::spawn(move || {
        println!("socket: started!");

        // pretend we are taking time to connect
        thread::sleep(Duration::from_millis(300));

        println!("socket: connected!");
        console_tx.send(ConsoleEvent::Connected).unwrap();

        // pretend we are taking time to transfer
        thread::sleep(Duration::from_millis(300));

        println!("socket: disconnected!");
        console_tx.send(ConsoleEvent::Disconnected).unwrap();

        println!("socket: closed!");
    });

    let console = thread::spawn(move || {
        println!("console: started!");

        for msg in console_rx.iter() {
            match msg {
                ConsoleEvent::Connected => println!("console: I'm connected!"),
                ConsoleEvent::Disconnected => {
                    println!("console: I'm disconnected!");
                    break;
                }
            }
        }
    });

    socket.join().expect("Unable to join socket thread");
    console.join().expect("Unable to join console thread");
}

Here, there are 3 threads at play:

  1. The main thread.
  2. A thread to read from the "socket".
  3. A thread to interface with the user.

Each of these threads can maintain it's own non-shared state. This allows reasoning about each thread to be easier. The threads use a channel to send updates between them safely. The data that crosses threads is encapsulated in an enum.

When I run this, I get

socket: started!
console: started!
socket: connected!
console: I'm connected!
socket: disconnected!
socket: closed!
console: I'm disconnected!
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Yes, that makes perfect sense. Thank you very much (again). This is a much cleaner and succinct way of doing this. As everything I read stated, there is usually a better way than using a global, I just couldn't think of one. – SnareChops Feb 09 '15 at 02:40