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