如何在Rust中基于生成器或异步函数实现轻量级的长寿命线程?

时间:2019-06-02 14:21:45

标签: rust

我想以用Rust编写的轻量,长寿命线程的形式实现用户交互脚本。在脚本中,我有一些要异步等待用户输入的地方。

在JavaScript中,我将使用生成器,在该生成器中您可以传递问题,并获取答案,例如:

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");

但是,Rust的生成器方法resume()不包含任何参数!我无法克隆生成器或从函数返回它,以使许多用户会话具有不同的状态。我想到了使用async fn()而不是生成器,但是我不明白如何在每个步骤中调用它,并在其中传递值。

2 个答案:

答案 0 :(得分:3)

yield的返回值实际上只是另一个已隐式传递给第一个生成器的生成器,除了它迫使两者以奇怪的方式绑定在一起。

您可以在原始代码中用垃圾yield ""看到,即使没有任何可返回的内容,您也需要获取值。此外,您的示例还要求生成器的用户在提出问题之前先知道问题的答案 ,这似乎很不合常规。

明确传递第二个生成器:

#![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

您也可以提供硬编码数据:

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

let my_session = my_scenario(user_input);

答案 1 :(得分:0)

大约在Rust每晚2020-02-08为止,Rust的生成器现在接受resume的参数,与原始JavaScript示例更加匹配:

#![feature(generators, generator_trait)]

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

fn my_scenario() -> impl Generator<String, Yield = &'static str, Return = String> {
    |_arg: String| {
        let my_name = yield "What is your name?";
        let my_mood = yield "How are you feeling?";

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

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

    let stdin = io::stdin();
    let mut lines = stdin.lock().lines();

    let mut line = String::new();

    loop {
        match my_session.as_mut().resume(line) {
            GeneratorState::Yielded(prompt) => {
                println!("{}", prompt);
            }
            GeneratorState::Complete(v) => {
                println!("{}", v);
                break;
            }
        }

        line = lines.next().expect("User input ended").expect("User input malformed");
    }
}