【问题标题】:Rust error "cannot infer an appropriate lifetime for borrow expression" when attempting to mutate state inside a closure returning an Iterator尝试在返回 Iterator 的闭包内改变状态时,Rust 错误“无法推断借用表达式的适当生命周期”
【发布时间】:2019-12-03 20:16:26
【问题描述】:

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

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

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

我认为这个问题与point 的可变借用如何存在于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`.

【问题讨论】:

  • "借来的就不能和关闭一样长吗?"显然不是point life 会在你的函数结束时结束,所以你不能借用它超过你的函数范围。
  • @Stargateur,谢谢,但your version 没有产生正确的输出。它不是将“步骤”链接在一起以在 (3, 0) 处结束,而是从每个“步骤”开始时的 (0, 0) 开始并在 (2, 0) 处结束。
  • 在 reddit 上,clair_resurgent explained what's wrong in detail。我需要思考一下……
  • 我并没有真正看输出是否相同 ^^',您的最后一个链接也失败了,但我敢打赌,解释很难,这就是为什么我试图不回答 :p 当你发布几个平台上的相同问题最好在问题中指出。另外,如果你开始生锈,永远不要忘记,KISS,如果你想让 python 使用 python,尝试重现 python 的特性不是要走的路(首先)。好吧,生成器对 Rust 来说完全没问题,但手头上的操作可能非常困难。

标签: rust lifetime


【解决方案1】:

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

Playground link

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

【讨论】:

  • 我开始认为我的根本问题是试图依赖副作用(可变状态),同时还使用功能更强大的flat_map()。为了好玩,我开始研究一种函数式方法,它使用flat_mapfold 创建类似[|p| step(p), |p| step(step(p)), |p| step(step(step(p)))] 的东西,您可以通过使用p = Point::new(0, 0) 调用每个函数来评估它。如果我能弄清楚类型,我会发布它。
  • 解决方案的关键似乎是保留StepTracer 发出的最新Point 的副本。然后MultiStepTracer 可以使用最新的点创建一个新的StepTracer 迭代器。我想探索是否有可能构建一个利用原始trace_step() 函数的MultiStepTracer 版本,可能通过将最新Point 的副本保留在MultiStepTracer 而不是@ 987654339@.
  • 迭代器是用fn next(&amp;mut self) -&gt; ... 实现的,所以我很遗憾认为这是不可能的。您不能将&amp;mut self 传递给next(),同时将&amp;mut self.field 存储在某处。但是,如果MultiStepTracer 拥有该点而不是可变地借用它,那么可行的方法是。我不太明白您所说的功能示例是什么意思,示例代码会很好。当您考虑是否要复制任何内容或在堆上存储数据时,这会变得更加复杂。
  • FWIW,我发布了一个功能版本。
【解决方案2】:

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

下面的代码通过放弃可变状态并完全采用在原始问题的混乱代码中努力突破通过flat_map 的功能方法,实现了基本相同的结果。

Run on the Rust playground.

代码:

#[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 }

【讨论】:

    猜你喜欢
    • 2019-06-12
    • 2022-01-06
    • 2020-01-29
    • 2015-02-22
    • 1970-01-01
    • 2021-09-10
    • 2015-09-01
    • 2020-05-18
    相关资源
    最近更新 更多