繁体   English   中英

尝试在返回迭代器的闭包内改变状态时,Rust 错误“无法推断借用表达式的适当生命周期”

[英]Rust error "cannot infer an appropriate lifetime for borrow expression" when attempting to mutate state inside a closure returning an Iterator

我正在尝试学习 Rust,并且在尝试模拟嵌套 Python 生成器时遇到了与生命周期相关的问题。 问题在于编译器报告的由闭包改变的值的生命周期。 代码的关键是 flat_mapping 一个闭包,该闭包调用一个函数,该函数在其返回的 Iterator 中改变从外部作用域提供的值。 请参阅Rust 游乐场示例中的第 39 行。

这里的代码是原始程序的简化版本。 由于我的最终目标是了解更多关于 Rust 的知识,我希望得到一些见解,而不是修复我的代码!

例如,一个“解决方案”是第 44 行注释掉的代码。它“有效”但它总是分配一个包含轨迹上所有点的Vec ,即使用户只想检查第一个点一丝痕迹。

我认为问题与可变借用point如何在trace_steps返回的迭代器中存在trace_steps 我尝试了太多的变化,无法在此列出,从传入从main突变的point (更类似于trace_step工作方式)到尝试在我开始绝望时盲目使用Rc<RefCell<Point>>

下面是从Rust Playground复制的代码:

#[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 Playground 中运行时的错误是:

   Compiling playground v0.0.1 (/playground)
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
  --> src/main.rs:38:33
   |
38 |     let f = |x: &u8| trace_step(&mut point, *x);
   |                                 ^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime '_ as defined on the body at 38:13...
  --> src/main.rs:38:13
   |
38 |     let f = |x: &u8| trace_step(&mut point, *x);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that closure can access `point`
  --> src/main.rs:38:33
   |
38 |     let f = |x: &u8| trace_step(&mut point, *x);
   |                                 ^^^^^^^^^^
note: but, the lifetime must be valid for the destruction scope surrounding expression at 34:63...
  --> src/main.rs:34:63
   |
34 |   fn trace_steps(steps: Vec<u8>) -> impl Iterator<Item = Point> {
   |  _______________________________________________________________^
35 | |     let mut point: Point = Point::new(0, 0);
36 | |     
37 | |     // FIXME: This doesn't work.
...  |
49 | |     */
50 | | }
   | |_^
note: ...so that references are valid when the destructor runs
  --> src/main.rs:34:63
   |
34 |   fn trace_steps(steps: Vec<u8>) -> impl Iterator<Item = Point> {
   |  _______________________________________________________________^
35 | |     let mut point: Point = Point::new(0, 0);
36 | |     
37 | |     // FIXME: This doesn't work.
...  |
49 | |     */
50 | | }
   | |_^

error: aborting due to previous error

error: could not compile `playground`.

问题是 Rust 对复制可变引用非常严格。 这是一个问题,因为当您在flat_map返回迭代器时,该迭代器必须具有对该点的可变(唯一)引用,但是flat_map不够健壮,无法将迭代器还给您,因此 Rust 无法证明到再次调用闭包时,最后一个迭代器仍然没有引用该点。 一旦发电机稳定下来,这将是微不足道的。 在此期间,它仍然是可能的,但更难比我预想的,你需要手动实现Iterator特性。 干得好:

游乐场链接

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

原始问题要求沿由运行长度定义的某些路径的点的迭代器,下面的答案不提供迭代器。 上面接受的答案仍然值得称赞,因为它是原始问题的最佳答案。

下面的代码通过抛弃可变状态并完全采用函数式方法来实现基本相同的结果,该方法在原始问题的代码混乱中努力突破 via flat_map

在 Rust 游乐场上奔跑。

代码:

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

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

fn main() {
    let origin: Point = Point::new(0, 0);
    let lengths: Vec<u16> = vec![1, 2];

    // Function that returns the next point after "taking a step"
    fn step(p: Point) -> Point {
         Point {x: p.x + 1, y: p.y }
    };

    /*****************************************
     * ORIGINAL EXAMPLE: Collect all points along the path
     *****************************************/

    // The crux of this version of the answer is to create all of the steps we 
    // intend to take for each length.  Steps will be an iterator that is 
    // something like: [|x| step(x), |x| step(x), |x| step(x)]
    let steps = lengths.iter().flat_map(|num_steps: &u16| (0..*num_steps).map(|_| |x| step(x)) );

    // `fold` lets us chain steps one after the other.  Unfortunately, this
    // doesn't give us an iterator, so it's not a good answer to the original 
    // question.
    let path_points: Vec<Point> = steps.fold(vec![origin], |mut acc, f| {
        acc.push(f(*acc.last().unwrap()));
        acc
    }).split_off(1);  // split_off gets rid of the initial "origin" point at (0, 0)
    println!("Path for original example: {:?}", path_points);

    /*****************************************
     * BONUS EXAMPLE: Get just the endpoint
     *****************************************/

    // Same as above
    let steps = lengths.iter().flat_map(|num_steps: &u16| (0..*num_steps).map(|_| |x| step(x)) );

    // Note that this has the same space-saving benefits of the iterator 
    // solution, but it requires the user to do more work in general having to
    // think about how to write the folding function
    let end_point: Point = steps.fold(origin, |acc, f| {
        f(acc)
    });
    println!("End point for bonus example: {:?}", end_point);
}

输出:

Path for original example: [Point { x: 1, y: 0 }, Point { x: 2, y: 0 }, Point { x: 3, y: 0 }]
End point for bonus example: Point { x: 3, y: 0 }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM