How to implement a lightweight long-lived thread b

2019-07-25 09:27发布

I want to implement a user interaction script in the form of a lightweight, long-lived thread written in Rust. Inside the script, I have points where I asynchronously await user input.

In JavaScript, I would use a generator, inside which you can pass a question, and get back an answer, for example:

function* my_scenario() {
    yield "What is your name?";
    let my_name = yield "How are you feeling?";
    let my_mood = yield "";
    ...
}

let my_session = my_scenario();
...
my_session.next("Peter");
my_session.next("happy");

However, Rust's generator method resume() contains no parameters! I cannot clone a generator or return it from a function in order to have many user sessions with different states. Instead of a generator, I thought of using an async fn(), but I do not understand how to call it at each step, passing the value there.

标签: rust
1条回答
贪生不怕死
2楼-- · 2019-07-25 10:04

The return value from yield is effectively just another generator that has been implicitly passed to the first generator, except that it forces the two to be tied together in weird ways.

You can see that in your original code by the junk yield "" that you need in order to get a value even though you don't have anything to return. Additionally, your example requires that the user of the generator know the answer to the question before it is asked, which seems very unorthodox.

Explicitly pass in a second generator:

#![feature(generators, generator_trait)]

use std::{
    io,
    ops::{Generator, GeneratorState},
};

fn user_input() -> impl Generator<Yield = String> {
    || {
        let input = io::stdin();
        loop {
            let mut line = String::new();
            input.read_line(&mut line).unwrap();
            yield line;
        }
    }
}

fn my_scenario(
    input: impl Generator<Yield = String>,
) -> impl Generator<Yield = &'static str, Return = String> {
    || {
        let mut input = Box::pin(input);

        yield "What is your name?";
        let my_name = match input.as_mut().resume() {
            GeneratorState::Yielded(v) => v,
            GeneratorState::Complete(_) => panic!("input did not return a value"),
        };

        yield "How are you feeling?";
        let my_mood = match input.as_mut().resume() {
            GeneratorState::Yielded(v) => v,
            GeneratorState::Complete(_) => panic!("input did not return a value"),
        };

        format!("{} is {}", my_name.trim(), my_mood.trim())
    }
}

fn main() {
    let my_session = my_scenario(user_input());
    let mut my_session = Box::pin(my_session);

    loop {
        match my_session.as_mut().resume() {
            GeneratorState::Yielded(prompt) => {
                println!("{}", prompt);
            }
            GeneratorState::Complete(v) => {
                println!("{}", v);
                break;
            }
        }
    }
}
$ cargo run
What is your name?
Shep
How are you feeling?
OK
Shep is OK

You can provide hard-coded data as well:

let user_input = || {
    yield "Peter".to_string();
    yield "happy".to_string();
};

let my_session = my_scenario(user_input);
查看更多
登录 后发表回答