Как я могу передавать некоторые значения в генератор Rust на каждом шаге? - PullRequest
0 голосов
/ 05 июня 2019

Я использую генераторы в качестве долгоживущих асинхронных потоков (см. Как реализовать облегченный долгоживущий поток на основе генератора или асинхронной функции в Rust? ) в сценарии взаимодействия с пользователем.Мне нужно передать пользовательский ввод в генератор на каждом шаге.Я думаю, что могу сделать это с RefCell, но не ясно, как передать ссылку на RefCell внутри генератора при создании его экземпляра?

fn user_scenario() -> impl Generator<Yield = String, Return = String> {
    || {
        yield format!("what is your name?");
        yield format!("{}, how are you feeling?", "anon");
        return format!("{}, bye !", "anon");
    }
}

Структура UserData содержитпользовательский ввод, вторая структура содержит пользовательский сеанс, состоящий из UserData и экземпляр генератора.Сессии собраны в HashMap.

struct UserData {
    sid: String,
    msg_in: String,
    msg_out: String,
}

struct UserSession {
    udata_cell: RefCell<UserData>,
    scenario: Pin<Box<dyn Generator<Yield = String, Return = String>>>,
}

type UserSessions = HashMap<String, UserSession>;

let mut sessions: UserSessions = HashMap::new();

UserData, созданный во время получения пользовательского ввода - в этот момент мне нужно отправить ссылку на UserData внутри генератора, обернув егов RefCell, но я не знаю, как это сделать, поскольку у генератора есть 'static время жизни, а RefCell живет меньше!

let mut udata: UserData = read_udata(&mut stream);
let mut session: UserSession;

if udata.sid == "" { //new session
    let sid = rnd.gen::<u64>().to_string();
    udata.sid = sid.clone();
    sessions.insert(
        sid.clone(),
        UserSession {
            udata_cell: RefCell::new(udata),
            scenario: Box::pin(user_scenario())
        }
    );
    session = sessions.get_mut(&sid).unwrap();
} 

Полный код здесь, но генератор здесь не видит пользовательский ввод.

1 Ответ

0 голосов
/ 05 июня 2019

Отказ от ответственности: аргументы возобновления являются запланированным расширением для генераторов, поэтому в некоторый момент в будущем будет возможно возобновить аргумент с помощью &UserData.


На данный момент, я буду рекомендовать разделить собственность. Стоимость довольно незначительна (одно выделение памяти, одно перенаправление) и избавит вас от многих хлопот:

struct UserSession {
    user_data: Rc<RefCell<UserData>>,
    scenario: ..,
}

Который построен с:

let user_data = Rc::new(RefCell::new(udata));
UserSession {
    user_data: user_data.clone(),
    scenario: Box::pin(user_scenario(user_data))
}

Тогда и сеанс, и генератор имеют доступ к UserData каждый по очереди, и все в порядке.

Есть одна маленькая морщинка: будьте осторожны с областями видимости. Если вы поддерживаете .borrow() в точке yield, что возможно, то у вас будет ошибка времени выполнения при попытке записи в него вне генератора.


Более сложным решением было бы использование очереди сообщений; что также потребовало бы выделения памяти и т. д. Я бы посчитал, что ваша структура UserData является вырожденной формой пары очередей: это две очереди с емкостью для одного сообщения. Вы могли бы сделать это более явным с обычной очередью, но это не принесло бы вам большой выгоды.

...