Rust中可恢复的延续传递样式迭代器约简

3
我正在尝试编写一个支持继续传递风格的“reduce”函数,可以在任何时候恢复。我已经有一个可用的版本,但是如果我想使用某个状态的借用,就需要显式地编写该函数的新版本。 Rust Playground链接
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链接
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 中的生命周期?
即使我没有为 &mut Store 指定显式生命周期,并且它可以存活到 'static,我的第一个例子中的 reduce_async_with_store 为什么被允许?
因为我调用的第三方 API 函数需要这样做,所以 some_operation 接受一个包含闭包的盒子。 我最终想要用异步迭代器替换这段代码,但是我使用的库尚未支持 futures。
1个回答

2

让我们从 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);
}

在这里,我们明确标记了两个生命周期应该相同,现在代码可以编译而没有任何错误。

请注意,在您的第一个代码片段中没有任何问题,因为每次调用reduce_async_with_storestore:&mut Store参数的生命周期是不同的!在第二个片段中,它被固定为'state

我认为,最简单的解决方法是完全摆脱可变引用,并通过传递所有权来传递Store

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: 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);
        },
    )
}

请注意,继续调用不是尾递归,因此堆栈将在每次迭代中增长。您可能需要在这里使用一个跳板函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接