你正在rustc迷宫中寻宝,忽然你撞上了一堵墙,上面写着:
error[E0597]: `x` does not live long enough
--> src\mov_iter.rs:47:20
|
47 | stack.push(&x)
| ----- ^^ borrowed value does not live long enough
| |
| borrow later used here
48 | }
| - `x` dropped here while still borrowed
这是你妄图将局部变量借走(出其作用域)的证据,你无法逃出borrow checker的法眼,眼看便被困在此处,无法逃脱。局部变量的生命过于短暂,你要借走它,你就不可以让它轻易狗带,你要拯救它。
你摸摸口袋,摸索出一张破旧的羊皮纸,上面歪歪扭扭地写着一些字,看来上面写的是逃生的提示:
俗话说,“你借不走它,便带走它”。(并没有这样一句俗话)
这大概是最直接的方法,直接把局部变量返回出来,然后再借也不迟。
xstruct A;
fn _err() {
let mut stack = vec![];
foo(&mut stack);
fn foo(stack: &mut Vec<&A>) {
let x = A;
stack.push(&x); // 你妄图将局部变量借走!
}
}
fn _ok() {
let mut stack = vec![];
let f = foo();
f.call(&mut stack);
struct Foo(A);
fn take_it_away() -> Foo {
let x = A;
Foo(x) // 但你可以把局部变量直接带走
}
impl Foo {
fn call<'a>(&'a self, stack: &mut Vec<&'a A>) {
stack.push(&self.0) // 再借不难
}
}
}
但是你并不是常山赵子龙,这样的方法不能让你七进七出:
xxxxxxxxxx
for _ in 0..7 {
let f = take_it_away();
f.call(&mut stack);
// 撞上了循环的墙
}
这时你又撞上了写着同样文字的墙(又或者是另外一个写着不能同时存在可变和不可变的借用),这时循环拦住了你们。
不过你可以一次性将所有的局部变量打包带走,再慢慢借:
xxxxxxxxxx
let mut stack: Vec<&A> = vec![];
let mut f = Foo::default();
f.take_them_away().consume(&mut stack);
struct Foo(Vec<A>);
impl Foo {
fn take_them_away(&mut self) -> &Self {
for _ in 0..7 {
self.0.push(A); // 打包带走
}
self
}
fn consume<'a>(&'a self, stack: &mut Vec<&'a A>) {
self.0.iter().for_each(|x| stack.push(x));
}
}
这是个好方法,但有很大的局限性,你并不怎么喜欢使用这样的方法。因为稍不注意,又该碰壁。
分享是一种美德,分享也可解决问题。
你要与rustc达成协议,这个变量你与callee共同拥有:
xxxxxxxxxx
fn _ok() {
let mut stack = vec![];
for _ in 0..7 {
foo(&mut stack);
}
fn foo(stack: &mut Vec<Rc<A>>) {
let x = A;
stack.push(Rc::new(x)); // 你与rustc签订了名为Rc的契约,这份x,你与foo共同拥有
}
}
这的确是一个简单的方法,但是这样的共享是有代价的(而且我不认为这是使用Rc的场景)。而且你听喵老大说,“在API中出现Rc的那一刻,你就输了”。你并不想输。
俗话说,”退一步海阔天空“。选择后退并不是逃避,不是妥协,而是解决问题的一种智慧。
你不打算继续逃走,而是退后一步选择向迷宫的深处进发,你相信迷宫深处会有出口:
xxxxxxxxxx
// err
let x = A;
return &x; // 问题等价于返回局部变量
// ok
let x = A;
k(&x); // CPS变换,将返回转化为调用执行接下来的逻辑(continuation)
但迷宫深处等待你的可能不是出口,而是万丈深渊,那时候将万劫不复(stackoverflow)
Arena是一个区域,是用来存放无处安置的流浪者的。你可以把局部变量放在那里:
xxxxxxxxxx
fn _ok() {
let mut stack = vec![];
let arena = Arena::new();
for _ in 0..7 {
foo(&mut stack, &arena);
}
fn foo<'a>(stack: &mut Vec<&'a A>, arena: &'a Arena<A>) {
let x = A;
stack.push(arena.alloc(x)); // 你把局部变量交给Arena之后再借它
}
}
arena里面存在着一些规则以外的机制,你并不想将局部变量交给他。(我觉得用了就很不Rusty。而且多了一个额外的东西并不好看)
目之所见,皆为clone。全部地方都拥有自己的一份。
xxxxxxxxxx
fn _ok() {
let mut stack = vec![];
for _ in 0..7 {
foo(&mut stack);
}
fn foo(stack: &mut Vec<A>) {
let x = A;
stack.push(x.clone());
// do something with x
}
}
这虽是最安全的方法,但很显然,这花销十分昂贵,你不希望支出大于收入(指rust的性能)。
这其实是一个关于ownership分配的问题。
1和4是将ownership交给caller;
2将ownership交给Rc等智能指针(Cow同理,但这场景更不合理);
5所有地方都拥有ownership
3将callee转换为caller,ownership不需要变化了。
3和4都会在同一时刻一起析构所有”暂存“的值,相当于不管理,这是个潜在问题。
- 心智负担很大
- 多余的共享的开销,可能入侵到API
- 调用开销,容易爆栈,影响API设计
- Arena内有锁等等的开销
- clone开销太大
你不喜欢以上的所有方法,那么怎么办呢,你发出了求救。(rust还是💊吧(狗头))。