Возобновляемый итератор в стиле продолжения-сокращения в Rust - PullRequest
2 голосов
/ 05 июня 2019

Я пытаюсь написать функцию уменьшения в стиле продолжения, которую можно возобновить в любой момент.У меня есть работающая версия, но в этом я должен явно написать новую версию функции, если я хочу, чтобы она могла использовать заимствование некоторого состояния. Rust Playground Link

fn reduce_async_with_store<'a, I, A, F, C>(
    store: &mut Store,
    mut iterator: I,
    accumulator: A,
    mut f: F,
    continuation: C,
) where
    I: Iterator + 'a,
    F: FnMut(&mut Store, I::Item, A, Box<dyn FnOnce(&mut Store, A) + 'a>) + Clone + 'a,
    C: FnOnce(&mut Store, A) + 'a,
{
    match iterator.next() {
        None => continuation(store, accumulator),
        Some(item) => {
            let next: Box<dyn FnOnce(&mut Store, A) + 'a> = {
                let f = f.clone();
                Box::new(move |store, accumulator| {
                    reduce_async_with_store(store, iterator, accumulator, f, continuation)
                })
            };
            f(store, item, accumulator, next);
        }
    }
}

fn some_operation(state: &mut Store, continuation: Box<dyn FnOnce(&mut Store) + 'static>) {
    let mut new_state = Store { foo: state.foo };
    continuation(&mut new_state);
}

#[derive(Debug)]
pub struct Store {
    foo: u8,
}

fn main() {
    let mut some_state = Store { foo: 0 };
    let arr = vec![1u8, 2u8, 3u8];
    reduce_async_with_store(
        &mut some_state,
        arr.into_iter(),
        Vec::new(),
        |store, item, mut acc, continuation| {
            println!("Item: {}", item);
            store.foo += item;
            acc.push(item);
            some_operation(
                store,
                Box::new(move |stor| {
                    continuation(stor, acc);
                }),
            );
        },
        |store, acc| {
            println!("Done!! {:?} {:?}", store, acc);
        },
    )
}

Вот версия этой функции, которую я хотел бы написать, где я могу передать Store как часть аккумулятора и вытащить его- однако, если я сделаю это, я получу cannot infer an appropriate lifetime due to conflicting requirements.

Rust Playground Link

fn reduce_async<'a, I, A, F, C>(mut iterator: I, accumulator: A, mut f: F, continuation: C)
where
    I: Iterator + 'a,
    F: FnMut(I::Item, A, Box<dyn FnOnce(A) + 'a>) + Clone + 'a,
    C: FnOnce(A) + 'a,
{
    match iterator.next() {
        None => continuation(accumulator),
        Some(item) => {
            let next: Box<dyn FnOnce(A) + 'a> = {
                let f = f.clone();
                Box::new(move |accumulator| reduce_async(iterator, accumulator, f, continuation))
            };
            f(item, accumulator, next);
        }
    }
}

fn some_operation(state: &mut Store, continuation: Box<dyn FnOnce(&mut Store) + 'static>) {
    let mut new_state = Store { foo: state.foo };
    continuation(&mut new_state);
}

#[derive(Debug)]
pub struct Store {
    foo: u8,
}

fn main() {
    let mut some_state = Store { foo: 0 };
    let arr = vec![1u8, 2u8, 3u8];
    reduce_async(
        arr.into_iter(),
        (&mut some_state, Vec::new()),
        |item, mut acc, continuation| {
            let (store, vec) = acc;
            println!("Item: {}", item);
            store.foo += item;
            vec.push(item);
            some_operation(
                store,
                Box::new(move |store| {
                    continuation((store, vec));
                }),
            );
        },
        |(store, vec)| {
            println!("Done!! {:?} {:?}", store, vec);
        },
    )
}

Как я могу написать эту неспециализированную версию своей функциии передать такие вещи, как &mut Store, соблюдая при этом времена жизни Rust?

Почему мой первый пример с reduce_async_with_store разрешен, даже если я не указываю явное время жизни для &mut Store, ион может существовать до тех пор, пока 'static?

some_operation не будет заключен в рамку, потому что это то, что принимает сторонняя API-функция, которую я вызываю.Я хотел бы в конечном итоге заменить этот код асинхронными итераторами, но библиотека, которую я использую, пока не поддерживает фьючерсы.

1 Ответ

1 голос
/ 10 июня 2019

Давайте начнем с some_operation;всегда проще проверять обычные функции, чем замыкания, потому что компилятор проверяет только их сигнатуры.

Если отбросить элиминированные времена жизни, это выглядит так:

fn some_operation<'s>(state: &'s mut Store, continuation: Box<dyn for<'r> FnOnce(&'r mut Store) + 'static>) {
    let mut new_state = Store { foo: state.foo };
    continuation(&mut new_state);
}

Существует два различных времени жизни: 's и 'r - между ними нет связи.

Теперь давайте посмотрим здесь:

Box::new(move |store| {
    continuation((store, vec));
}),

Тип continuation должен быть Box<dyn FnOnce(A) + 'a> в соответствии с reduce_async 'подписьКакой тип A после мономорфизации?Аргумент, переданный функции, является кортежем:

(&mut some_state, Vec::new()),

Первый элемент имеет тип &'state mut State для некоторого 'state, а второй - Vec<u8>.Возвращаемся к подписи some_operation: первый аргумент - &'s mut State, поэтому мы выбрали 'state = 's здесь.Затем мы вызываем замыкание, используя аргумент с типом &'r mut State.

Вернувшись в основную процедуру, мы пытаемся собрать аккумулятор из значения типа (&'r mut State, Vec<u8>), которое не совпадает с (&'state mut State, Vec<u8>),

Вот что пытается объяснить компилятор :) Давайте проверим это объяснение, изменив сигнатуру some_operation:

fn some_operation<'s>(state: &'s mut Store, continuation: Box<dyn FnOnce(&'s mut Store) + 's>) {
    continuation(state);
}

Здесь мы явно отмечаем, что оба времени жизни должны быть одинаковыми,и теперь код компилируется без ошибок.

Обратите внимание, что в вашем первом фрагменте кода не было проблем, поскольку время жизни аргумента store: &mut Store меняется каждый раз, когда вы вызываете reduce_async_with_store!Во втором фрагменте он зафиксирован на 'state.

На мой взгляд, самым простым решением было бы вообще избавиться от изменяемых ссылок и передать Store путем передачи права собственности.

Rust детская площадка

fn reduce_async<'a, I, A, F, C>(mut iterator: I, accumulator: A, mut f: F, continuation: C)
where
    I: Iterator + 'a,
    F: FnMut(I::Item, A, Box<dyn FnOnce(A) + 'a>) + Clone + 'a,
    C: FnOnce(A) + 'a,
{
    match iterator.next() {
        None => continuation(accumulator),
        Some(item) => {
            let next: Box<dyn FnOnce(A) + 'a> = {
                let f = f.clone();
                Box::new(move |accumulator| reduce_async(iterator, accumulator, f, continuation))
            };
            f(item, accumulator, next);
        }
    }
}

fn some_operation(state: Store, continuation: Box<dyn FnOnce(Store) + 'static>) {
    let new_state = Store { foo: state.foo };
    continuation(new_state);
}

#[derive(Debug)]
pub struct Store {
    foo: u8,
}

fn main() {
    let some_state = Store { foo: 0 };
    let arr = vec![1u8, 2u8, 3u8];
    reduce_async(
        arr.into_iter(),
        (some_state, Vec::new()),
        |item, acc, continuation| {
            let (mut store, mut vec) = acc;
            println!("Item: {}", item);
            store.foo += item;
            vec.push(item);
            some_operation(
                store,
                Box::new(move |store| {
                    continuation((store, vec));
                }),
            );
        },
        |(store, vec)| {
            println!("Done!! {:?} {:?}", store, vec);
        },
    )
}

Имейте в виду, что вызовы продолжения не являются хвостово-рекурсивными, поэтому стек будет расти на каждой итерации.Вам, вероятно, понадобится батут здесь.

...