尝试在返回迭代器的闭包内改变状态时出现 Rust 错误 "cannot infer an appropriate lifetime for borrow expression"
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 一个调用函数的闭包,该函数在其返回的迭代器中改变从外部作用域提供的值。请参阅 Rust playground example.
中的第 39 行
此处的代码是原始程序的简化版本。由于我的最终目标是学习更多关于 Rust 的知识,我希望能有一些见解,而不是修复我的代码!
例如,一个 "solution" 是第 44 行的注释掉的代码。它 "works" 但是它总是分配一个包含所有点的 Vec
而错过了要点trace 即使用户只想检查轨迹上的第一个点。
我认为问题与 point
的可变借用如何在 trace_steps
returns 的迭代器中存在有关。我尝试了太多变体无法在此处列出,从传入从 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 对复制可变引用非常严格。这是一个问题,因为当您 return 内部的迭代器 flat_map
时,该迭代器必须具有指向该点的可变(唯一)引用,但 flat_map
不够健壮,无法提供迭代器返回给你,因此 Rust 无法证明最后一个迭代器在再次调用闭包时仍未引用该点。一旦发电机稳定下来,这将是微不足道的正确做法。与此同时,它仍然是可能的,但是 MUCH 比我预期的要难,你需要手动实现 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
。
代码:
#[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 }
我正在尝试学习 Rust,但在尝试模拟嵌套 Python 生成器时遇到了与生命周期相关的问题。正如编译器所报告的那样,问题在于被闭包改变的值的生命周期。代码的关键是 flat_mapping 一个调用函数的闭包,该函数在其返回的迭代器中改变从外部作用域提供的值。请参阅 Rust playground example.
中的第 39 行此处的代码是原始程序的简化版本。由于我的最终目标是学习更多关于 Rust 的知识,我希望能有一些见解,而不是修复我的代码!
例如,一个 "solution" 是第 44 行的注释掉的代码。它 "works" 但是它总是分配一个包含所有点的 Vec
而错过了要点trace 即使用户只想检查轨迹上的第一个点。
我认为问题与 point
的可变借用如何在 trace_steps
returns 的迭代器中存在有关。我尝试了太多变体无法在此处列出,从传入从 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 对复制可变引用非常严格。这是一个问题,因为当您 return 内部的迭代器 flat_map
时,该迭代器必须具有指向该点的可变(唯一)引用,但 flat_map
不够健壮,无法提供迭代器返回给你,因此 Rust 无法证明最后一个迭代器在再次调用闭包时仍未引用该点。一旦发电机稳定下来,这将是微不足道的正确做法。与此同时,它仍然是可能的,但是 MUCH 比我预期的要难,你需要手动实现 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
。
代码:
#[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 }