如果您在if
测试中不变地借用,则借用将持续整个if
表达式。 这意味着子句中的可变借用将导致借用检查器失败。
这也可能发生在 match 表达式中借用时,并且需要在其中一个分支中进行可变借用。
有关 if 借用框的示例,请参见此处,这会导致最近的向上@mut
冻结。 然后remove_child()
需要借用可变冲突。
https://github.com/mozilla/servo/blob/master/src/servo/layout/box_builder.rs#L387 -L411
来自@Wyverald 的更新示例
fn main() {
let mut vec = vec!();
match vec.first() {
None => vec.push(5),
Some(v) => unreachable!(),
}
}
提名生产就绪
我会称之为定义明确或向后兼容。
经过一些讨论,很明显这里的真正问题是借用检查器不努力跟踪别名,而是始终依赖区域系统来确定借用何时超出范围。 我不愿意改变这一点,至少在短期内不会,因为我想首先解决许多其他未决问题,任何改变都将是对借用检查器的重大修改。 有关另一个相关示例和详细说明,请参阅问题 #6613。
我想知道我们是否可以改进错误消息以使其更清楚发生了什么? 词法作用域相对容易理解,但在我偶然发现的这个问题的例子中,发生了什么并不明显。
只是一个错误,删除里程碑/提名。
接受未来里程碑
分类碰撞
我对解决此问题的最佳方法有一些想法。 我的基本计划是我们会有一个值何时“逃逸”的概念。 将这个概念正式化需要一些工作。 基本上,当一个借用的指针被创建时,我们将跟踪它是否已经转义。 当指针死了,如果还没有逃脱,这可以考虑杀掉贷款。 这个基本思想涵盖了“让 p = &...; use-pa-bit-but-never-again; expect-loan-to-be-expired-here;” 之类的情况。 分析的一部分将是一条规则,该规则指示何时可以认为包含借用指针的返回值尚未转义。 这将涵盖诸如“匹配 table.find(...) { ... None => { expect-table-not-to-be-loaned-here; } }”之类的情况
当然,所有这些中最有趣的部分是转义规则。 我认为规则必须考虑函数的正式定义,特别是要利用生命周期给我们的知识绑定。 例如,大多数转义分析会考虑使用指针p
来转义,如果他们看到类似foo(p)
的调用。 但我们不一定必须这样做。 如果函数被声明为:
fn foo<'a>(x: &'a T) { ... }
那么事实上我们知道foo
不会比生命周期a
持有p
更长的时间。 但是,像bar
这样的函数必须被视为转义:
fn bar<'a>(x: &'a T, y: &mut &'a T)
因此,推测转义规则必须考虑绑定生存期是否出现在可变位置。 这实际上是一种基于类型的别名分析。 我认为类似的推理适用于函数返回值。 因此find
应该被认为是返回一个非转义的结果:
fn find<'a>(&'a self, k: &K) -> Option<&'a V>
这里的原因是因为'a
绑定在find
,所以它不能出现在Self
或K
类型参数中,因此我们知道它可以' t 存储在那些中,并且它不会出现在任何可变位置。 (请注意,我们可以应用与今天使用的相同的推理算法,该算法将用作 #3598 修复程序的一部分,以告诉我们生命周期是否出现在可变位置)
考虑这一点的另一种方式不是贷款提前_到期,而是贷款的范围开始(通常)与借用的 _variable_ 相关联,而不是完整的生命周期,并且只有当变量出现时才会被提升到完整的生命周期_逃脱_。
再借是一种轻微的复杂性,但可以通过多种方式处理。 重新借用是当您借用借用指针的内容时——它们_一直_发生,因为编译器会自动将它们插入到几乎每个方法调用中。 考虑一个借用的指针let p = &v
和像let q = &*p
这样的再借用指针。 如果q
死了,你可以再次使用p
就好了——如果p
和q
都死了,你可以使用v
再次(假设p
和q
转义)。 这里的复杂之处在于,如果q
转义,则p
必须被视为已转义,直到q
的生命周期到期。 但我认为这与我们今天处理它的方式_有点_自然地不同:也就是说,编译器注意到q
已经借用了p
用于(最初)生命周期“q”(即,变量本身),如果q
应该转义,那将被提升到完整的词法生命周期。 我想棘手的部分是在数据流中,知道在哪里插入杀戮——我们不能在p
被重新借用时立即为p
插入杀戮。 哦,好吧,我不会在这上面浪费更多时间,这似乎可行,而且在最坏的情况下,有更简单的解决方案足以应对常见情况(例如,考虑p
已逃脱整个生命周期q
,无论q
贷款是否逃逸)。
无论如何,需要更多的思考,但我开始看到这是如何工作的。 在#2202 和#8624 修复之前,我仍然不愿意开始任何这样的扩展,这是borrowck 的两个已知问题。 在我们开始扩展系统之前,我还希望在健全性证明方面取得更多进展。 时间线上的另一个扩展名是#6268。
我相信我遇到了这个错误。 我的用例和解决方法尝试:
这是此错误的另一个示例(我认为):
use std::util;
enum List<T> {
Cons(T, ~List<T>),
Nil
}
fn find_mut<'a,T>(prev: &'a mut ~List<T>, pred: |&T| -> bool) -> Option<&'a mut ~List<T>> {
match prev {
&~Cons(ref x, _) if pred(x) => {}, // NB: can't return Some(prev) here
&~Cons(_, ref mut rs) => return find_mut(rs, pred),
&~Nil => return None
};
return Some(prev)
}
我想写:
fn find_mut<'a,T>(prev: &'a mut ~List<T>, pred: |&T| -> bool) -> Option<&'a mut ~List<T>> {
match prev {
&~Cons(ref x, _) if pred(x) => return Some(prev),
&~Cons(_, ref mut rs) => return find_mut(rs, pred),
&~Nil => return None
}
}
推断一旦我们完成对谓词的评估, x
借用就会失效,但当然,借用现在扩展到整个匹配。
我有更多关于如何编码的想法。 我的基本计划是每笔贷款都有两个位:一个转义版本和一个非转义版本。 最初我们添加非转义版本。 当引用转义时,我们添加转义位。 当变量(或临时变量等)失效时,我们会杀死未转义的位——但保留转义位(如果设置)不变。 我相信这涵盖了所有主要的例子。
抄送@flaper87
这个问题包括这个吗?
use std::io::{MemReader, EndOfFile, IoResult};
fn read_block<'a>(r: &mut Reader, buf: &'a mut [u8]) -> IoResult<&'a [u8]> {
match r.read(buf) {
Ok(len) => Ok(buf.slice_to(len)),
Err(err) => {
if err.kind == EndOfFile {
Ok(buf.slice_to(0))
} else {
Err(err)
}
}
}
}
fn main() {
let mut buf = [0u8, ..2];
let mut reader = MemReader::new(~[67u8, ..10]);
let mut block = read_block(&mut reader, buf);
loop {
//process block
block = read_block(&mut reader, buf); //error here
}
抄送我
#9113 中的好例子
抄送我
我可能弄错了,但以下代码似乎也遇到了这个错误:
struct MyThing<'r> {
int_ref: &'r int,
val: int
}
impl<'r> MyThing<'r> {
fn new(int_ref: &'r int, val: int) -> MyThing<'r> {
MyThing {
int_ref: int_ref,
val: val
}
}
fn set_val(&'r mut self, val: int) {
self.val = val;
}
}
fn main() {
let to_ref = 10;
let mut thing = MyThing::new(&to_ref, 30);
thing.set_val(50);
println!("{}", thing.val);
}
理想情况下,由调用 set_val 引起的可变借用将在函数返回后立即结束。 请注意,从结构(和相关代码)中删除“int_ref”字段会导致问题消失。 行为不一致。
@SergioBenitez我认为这不是同一个问题。 您明确要求&mut self
引用的生命周期与结构的生命周期相同。
但是你不需要这样做。 你根本不需要set_val()
的一生。
fn set_val(&mut self, val: int) {
self.val = val;
}
我发现了另一个很难修复的案例:
/// A buffer which breaks chunks only after the specified boundary
/// sequence, or at the end of a file, but nowhere else.
pub struct ChunkBuffer<'a, T: Buffer+'a> {
input: &'a mut T,
boundary: Vec<u8>,
buffer: Vec<u8>
}
impl<'a, T: Buffer+'a> ChunkBuffer<'a,T> {
// Called internally to make `buffer` valid. This is where all our
// evil magic lives.
fn top_up<'b>(&'b mut self) -> IoResult<&'b [u8]> {
// ...
}
}
impl<'a,T: Buffer+'a> Buffer for ChunkBuffer<'a,T> {
fn fill_buf<'a>(&'a mut self) -> IoResult<&'a [u8]> {
if self.buffer.as_slice().contains_slice(self.boundary.as_slice()) {
// Exit 1: Valid data in our local buffer.
Ok(self.buffer.as_slice())
} else if self.buffer.len() > 0 {
// Exit 2: Add some more data to our local buffer so that it's
// valid (see invariants for top_up).
self.top_up()
} else {
{
// Exit 3: Exit on error.
let read = try!(self.input.fill_buf());
if read.contains_slice(self.boundary.as_slice()) {
// Exit 4: Valid input from self.input. Yay!
return Ok(read)
}
}
// Exit 5: Accumulate sufficient data in our local buffer (see
// invariants for top_up).
self.top_up()
}
}
…这使:
/path/to/mylib/src/buffer.rs:168:13: 168:17 error: cannot borrow `*self` as mutable more than once at a time
/path/to/mylib/src/buffer.rs:168 self.top_up()
^~~~
/path/to/mylib/src/buffer.rs:160:33: 160:43 note: previous borrow of `*self.input` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `*self.input` until the borrow ends
/path/to/mylib/src/buffer.rs:160 let read = try!(self.input.fill_buf());
^~~~~~~~~~
<std macros>:1:1: 3:2 note: in expansion of try!
/path/to/mylib/src/buffer.rs:160:28: 160:56 note: expansion site
/path/to/mylib/src/buffer.rs:170:6: 170:6 note: previous borrow ends here
/path/to/mylib/src/buffer.rs:149 fn fill_buf<'a>(&'a mut self) -> IoResult<&'a [u8]> {
...
/path/to/mylib/src/buffer.rs:170 }
这基本上相当于#12147。 变量read
隐藏在内部作用域中,但return
将read
的生命周期绑定到整个函数的生命周期。 大多数明显的解决方法都失败了:
input.fill_buf
,因为Buffer
接口不能保证它返回我刚刚第二次验证的数据。 如果我 _do_ 尝试这个,代码在技术上是不正确的,但类型检查器很高兴地通过了它。top_up
无能为力,因为这是一段邪恶的代码,需要以复杂的方式改变一切。if let
允许我测试 _then_ bind?)。几乎感觉好像'a
约束在理想情况下不应该一直传播回read
。 但我在这里不知所措。 我接下来要试试if let
。
好吧, if let
昨晚没有进入构建,但由于它应该只是 AST 重写,我想它可能以与match
相同的方式失败(我已经也试过here)。
我不知道如何继续,没有使用unsafe
。
我目前的 hack 看起来是这样的:
impl<'a,T: Buffer+'a> Buffer for ChunkBuffer<'a,T> {
fn fill_buf<'a>(&'a mut self) -> IoResult<&'a [u8]> {
// ...
{ // Block A.
let read_or_err = self.input.fill_buf();
match read_or_err {
Err(err) => { return Err(err); }
Ok(read) => {
if read.contains_slice(self.boundary.as_slice()) {
return Ok(unsafe { transmute(read) });
}
}
}
}
self.top_up()
这里的理论是,我放弃了read
(绑定到self.input
)的生命周期,并立即应用基于self
新生命周期,它拥有self.input
。 理想情况下,我希望read
的词法生命周期等于 Block A,并且我不希望它仅仅因为我将它传递给return
就被提升到 _lexical_ 块级别。 显然生命周期检查器仍然需要证明结果具有与'a
兼容的生命周期,但我不明白为什么这意味着 LIFETIME( read
) 需要与 LIFETIME( 'a
)。
我完全有可能感到非常困惑,或者我的代码非常不安全。 :-) 但感觉这应该可行,因为我可以毫无问题地调用return self.input.fill_buf()
。 有没有办法将这种直觉形式化?
@emk所以这是 SEME 区域(即非词法区域)无法修复的“硬代码”,至少不能由它们自己修复。 我有一些关于如何在编译器中很好地修复它的想法,但它是对 SEME 区域的重要扩展。 通常有一种方法可以通过重构代码来解决此问题。 让我看看我是否可以玩弄它并产生一个很好的例子。
我想知道这是否会在 1.0.0 中重新考虑。 最近这件事一直在出现,我担心一旦 1.0 引起关注,这会从剪纸变成肉伤。 作为 Rust 最明显和最常被谈论的特性,借用被打磨和可用是非常重要的。
对此 RFC 是否有时间表?
@nikomatsakis我有一个现实世界的简单例子,它在 Entry API 上工作失败了,如果有帮助的话:
use std::collections::SmallIntMap;
enum Foo<'a>{ A(&'a mut SmallIntMap<uint>), B(&'a mut uint) }
fn main() {
let mut map = SmallIntMap::<uint>::new();
do_stuff(&mut map);
}
fn do_stuff(map: &mut SmallIntMap<uint>) -> Foo {
match map.find_mut(&1) {
None => {}, // Definitely can't return A here because of lexical scopes
Some(val) => return B(val),
}
return A(map); // ERROR: borrowed at find_mut???
}
@bstrie @pcwalton和@zwarich 都花了一些时间尝试实际实施这项工作(可能会同时推出 RFC)。 他们遇到了一些意想不到的复杂性,这意味着需要做的工作比预期的要多得多。 我认为每个人都同意你的观点,这些限制很重要,可能会影响语言的第一印象,但很难在已经安排的向后不兼容的更改之间取得平衡。
我觉得如果 1.0 没有解决这个问题,那么这种事情会导致人们完全归咎于借用检查方法,而这个问题并不是借用检查 AFAIK 固有的无法解决的问题。
@blaenk很难不责怪借用检查器,我每天都遇到过类似的情况(如@Gankro)。 当通常的解决方案是旋转(例如变通)/或注释来重构您的代码以使其更加“不可变”,功能性等时,这令人沮丧。
@mtanski是的,但错误
对于这种情况:“让 p = &...; use-pa-bit-but-never-again; expect-loan-to-be-expired-here;” 我现在认为可以接受 kill(p) 指令来手动声明该借用的范围结束。 如果不需要此指令,以后的版本可以简单地忽略它,或者如果在它之后检测到 p 的重用,则将其标记为错误。
/* (wanted) */
/*
fn main() {
let mut x = 10;
let y = &mut x;
println!("x={}, y={}", x, *y);
*y = 11;
println!("x={}, y={}", x, *y);
}
*/
/* had to */
fn main() {
let mut x = 10;
{
let y = &x;
println!("x={}, y={}", x, *y);
}
{
let y = &mut x;
*y = 11;
}
let y = &x;
println!("x={}, y={}", x, *y);
}
前奏中有 drop() 方法可以做到这一点。 但是好像没有
帮助处理可变借款。
在Sun,2015年4月5日,下午1时41分axeoth [email protected]写道:
/* (想要) _//_fn main() { let mut x = 10; 让 y = &mut x; println!("x={}, y={}", x, _y); *y = 11; println!("x={}, y={}", x, *y);}_/
/* 必须 */fn main() {let mut x = 10; { let y = &x; println!("x={}, y={}", x, *y); } { let y = &mut x; *y = 11; } let y = &x; println!("x={}, y={}", x, *y);
}
—
直接回复此邮件或在 GitHub 上查看
https://github.com/rust-lang/rust/issues/6393#issuecomment -89848449。
@metajack你的原始代码的链接是 404。你能把它内联给阅读这个 bug 的人吗?
或者更确切地说,这是我提交此错误时使用的解决方法。 更改之前的原始代码似乎是这样的:
https://github.com/servo/servo/blob/7267f806a7817e48b0ac0c9c4aa23a8a0d288b03/src/servo/layout/box_builder.rs#L387 -L399
我不确定这些特定示例现在的相关性如何,因为它们是 Rust 1.0 之前的版本。
@metajack在这个问题的顶部有一个超简单(1.0 后)的例子会很棒。 这个问题现在是https://github.com/rust-lang/rfcs/issues/811 的一部分
fn main() {
let mut nums=vec![10i,11,12,13];
*nums.get_mut(nums.len()-2)=2;
}
我想我抱怨的是这样的:
https://is.gd/yfxUfw
这个特殊情况现在似乎有效。
@vitiral我认为今天 Rust 中的一个例子适用:
fn main() {
let mut vec = vec!();
match vec.first() {
None => vec.push(5),
Some(v) => unreachable!(),
}
}
None
分支失败了borrowck。
奇怪的是,如果您不尝试在Some
分支中捕获int
(即使用Some(_)
),它会编译。
@wyverland哦,是的,我昨天刚碰到那个,很烦人。
@metajack您可以编辑第一篇文章以包含该示例吗?
它尚未每晚播放,但我只想说现在可以编译:
#![feature(nll)]
fn main() {
let mut vec = vec!();
match vec.first() {
None => vec.push(5),
Some(v) => unreachable!(),
}
}
最有用的评论
它尚未每晚播放,但我只想说现在可以编译: