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.
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!
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.
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.
Below is the code copied from the Rust Playground is:
#[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);
}
And the error when this is run in the Rust Playground is:
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`.