Rust错误“无法推断出借用表达式的适当生存期".当试图改变闭包内的状态以返回迭代器时 [英] Rust error "cannot infer an appropriate lifetime for borrow expression" when attempting to mutate state inside a closure returning an Iterator

查看:54
本文介绍了Rust错误“无法推断出借用表达式的适当生存期".当试图改变闭包内的状态以返回迭代器时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试学习Rust,并在尝试模拟嵌套的Python生成器时遇到了与生命周期相关的问题.问题在于编译器报告的闭包突变值的生命周期.代码的症结是flat_mapping一个闭包,该闭包调用一个函数,该函数会改变其返回的Iterator中从外部作用域提供的值.参见 Rust操场示例.

I'm trying to learn Rust and have encountered a lifetime-related problem while trying to emulate nested Python generators. The problem is with the lifetime of a value mutated by a closure, as reported by the compiler. The crux of the code is flat_mapping a closure that calls a function that mutates a value supplied from the outer-scope in its returned Iterator. See line 39 in the Rust playground example.

此处的代码是原始程序的简化版本.由于我的最终目标是要了解有关Rust的更多信息,所以我不仅仅希望为我的代码提供一些见识,还希望获得一些见识!

The code here is a simplified, trivial version of the original program. Since my ultimate goal is to learn more about Rust, I'd appreciate some insight even more than a fix for my code!

例如,第44行的注释掉的代码是一个解决方案".它有效",但是它总是通过分配一个包含跟踪中所有点的 Vec 来遗漏该点,即使用户只想检查轨迹上的第一个点.

For example, one "solution" is the commented-out code on line 44. It "works" but it misses the point by always allocating a Vec that contains all points on the trace even if the user only wants to check the first Point on a trace.

我认为问题与 trace 返回的Iterator中的可变借入 point 的方式有关.从传入从 main 突变而来的 point 以来,我尝试了太多的变化以在此处列出(更类似于 trace_step 的工作方式)当我开始感到绝望时,尝试盲目使用 Rc< RefCell< Point>> .

I think the problem has something to do with how the mutable borrow to point lives on in the Iterator that trace_steps returns. I have tried far too many variations to list here, from passing in the point that is mutated from main (more similar to how trace_step works) to attempts at blindly using Rc<RefCell<Point>> when I started to get desperate.

下面是从铁锈操场是:

#[derive(Debug, Eq, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Point {
    fn new(x: i32, y: i32) -> Point {
        Point { x, y }
    }
}

// Intention is that this is like a Python generator.  Normally the "step" would
// be a struct with a direction and a length but this is a simplified version.
fn trace_step<'a>(point: &'a mut Point, step: u8) -> impl Iterator<Item = Point> + 'a {
    let mut len = step;
    std::iter::from_fn(move || {
        if len == 0 {
            None
        } else {
            len -= 1;
            point.x += 1;
            Some(Point { ..*point })
        }
    })
}

// FIXME: See compiler error!!!
// Compiler cannot infer an appropriate lifetime for the borrow &mut point.
// Can't the borrow just live as long as the closure?
//
// Intention is that this produces points along a path defined by multiple
// steps.  Simplified.
fn trace_steps(steps: Vec<u8>) -> impl Iterator<Item = Point> {
    let mut point: Point = Point::new(0, 0);

    // FIXME: This doesn't work.
    let f = |x: &u8| trace_step(&mut point, *x);
    steps.iter().flat_map(f)

    // This works, but we don't want to commit to allocating the space for all
    // points if the user only needs to, for example, count the number of points.
    /*
    let mut ret: Vec<Point> = Vec::new();
    for step in steps {
        ret.extend(trace_step(&mut point, step));
    }
    ret.into_iter()
    */
}

fn main() {
    let mut point: Point = Point::new(0, 0);
    let points: Vec<Point> = trace_step(&mut point, 3).collect();

    // Outputs: [Point { x: 1, y: 0 }, Point { x: 2, y: 0 }, Point { x: 3, y: 0 }]
    println!("{:?}", points);

    // Should trace the first from (0, 0) to (1, 0) and then trace the second step
    // from (1, 0) to (2, 0) to (3, 0).
    let points: Vec<Point> = trace_steps(vec![1, 2]).collect();
    println!("{:?}", points);
}

推荐答案

问题是Rust在复制可变引用方面非常严格.这是一个问题,因为当您在 flat_map 中返回迭代器时,该迭代器必须具有指向该点的可变(唯一)引用,但是 flat_map 不够健壮将迭代器还给您,因此Rust无法证明在再次调用闭包时最后一个迭代器仍未引用该点.发电机稳定后,正常工作将变得很简单.同时,它仍然可能,但是 MUCH 比我预期的要难,您需要手动实现 Iterator 特性.在这里:

The problem is that Rust is very strict about copying mutable references. This is a problem because when you return the iterator inside flat_map, that iterator has to have a mutable (sole) reference to the point, but flat_map isn't robust enough to give the iterator back to you, and therefore Rust can't prove that the last iterator doesn't still reference the point by the time the closure is called again. Once generators are stabilized, this will be trivial to do properly. In the meantime, it is still possible, but MUCH harder than I expected, you need to manually implement the Iterator trait. Here you go:

游乐场链接

use std::iter::{ExactSizeIterator, FusedIterator};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Point {
    fn new(x: i32, y: i32) -> Point {
        Self { x, y }
    }
}

#[derive(Debug)]
struct StepTracer<'a> {
    point: &'a mut Point,
    len: u8,
}

impl<'a> StepTracer<'a> {
    fn new(point: &'a mut Point, len: u8) -> Self {
        Self { point, len }
    }

    fn into_inner(self) -> &'a mut Point {
        self.point
    }
}

impl<'a> Iterator for StepTracer<'a> {
    type Item = Point;

    fn next(&mut self) -> Option<Self::Item> {
        if self.len == 0 {
            None
        } else {
            self.len -= 1;
            self.point.x += 1;
            Some(*self.point)
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        (self.len as usize, Some(self.len as usize))
    }
}

impl FusedIterator for StepTracer<'_> {}
impl ExactSizeIterator for StepTracer<'_> {}

// You may also want to consider implementing DoubleEndedIterator
// Additional traits: https://doc.rust-lang.org/std/iter/index.html#traits

enum MultiStepTracerState<'a> {
    First(&'a mut Point),
    Second(&'a mut Point),
    Tracer(StepTracer<'a>),
    Done,
}

/// Intention is that this produces points along a path defined by multiple
/// steps. Simplified.
struct MultiStepTracer<'a, I: Iterator<Item = u8>> {
    steps: I,
    state: MultiStepTracerState<'a>,
}

impl<'a, I: Iterator<Item = u8>> MultiStepTracer<'a, I> {
    fn new(point: &'a mut Point, steps: I) -> Self {
        Self {
            steps,
            state: MultiStepTracerState::First(point),
        }
    }
}

impl<I: Iterator<Item = u8>> Iterator for MultiStepTracer<'_, I> {
    type Item = Point;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            let mut temp_state = MultiStepTracerState::Done;
            std::mem::swap(&mut self.state, &mut temp_state);
            let point_ref = match temp_state {
                MultiStepTracerState::First(point) => {
                    let result = *point;
                    self.state = MultiStepTracerState::Second(point);
                    return Some(result);
                }
                MultiStepTracerState::Second(point) => point,
                MultiStepTracerState::Tracer(mut tracer) => {
                    if let Some(result) = tracer.next() {
                        self.state = MultiStepTracerState::Tracer(tracer);
                        return Some(result);
                    } else {
                        tracer.into_inner()
                    }
                }
                MultiStepTracerState::Done => {
                    return None;
                }
            };

            if let Some(len) = self.steps.next() {
                self.state = MultiStepTracerState::Tracer(StepTracer::new(point_ref, len));
            } else {
                self.state = MultiStepTracerState::Done;
                return None;
            }
        }
    }
}

impl<I: Iterator<Item = u8>> FusedIterator for MultiStepTracer<'_, I> {}

fn main() {
    let mut point: Point = Point::new(0, 0);
    let points: Vec<Point> = StepTracer::new(&mut point, 3).collect();

    // Outputs: [Point { x: 1, y: 0 }, Point { x: 2, y: 0 }, Point { x: 3, y: 0 }]
    println!("{:?}", points);

    // Should trace the first from (0, 0) to (1, 0) and then trace the second step
    // from (1, 0) to (2, 0) to (3, 0).
    let points: Vec<Point> =
        MultiStepTracer::new(&mut Point::new(0, 0), [1, 2].iter().copied()).collect();
    println!("{:?}", points);
}

这篇关于Rust错误“无法推断出借用表达式的适当生存期".当试图改变闭包内的状态以返回迭代器时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆