我从借用检查器收到Rust编译错误,我不明白为什么。可能有些关于我不完全理解的生命周期。
我把它归结为一个简短的代码示例。主要是,我想这样做:
fn main() {
let codeToScan = "40 + 2";
let mut scanner = Scanner::new(codeToScan);
let first_token = scanner.consume_till(|c| { ! c.is_digit ()});
println!("first token is: {}", first_token);
// scanner.consume_till(|c| { c.is_whitespace ()}); // WHY DOES THIS LINE FAIL?
}
第二次尝试拨打scanner.consume_till
会给我这个错误:
example.rs:64:5: 64:12 error: cannot borrow `scanner` as mutable more than once at a time
example.rs:64 scanner.consume_till(|c| { c.is_whitespace ()}); // WHY DOES THIS LINE FAIL?
^~~~~~~
example.rs:62:23: 62:30 note: previous borrow of `scanner` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `scanner` until the borrow ends
example.rs:62 let first_token = scanner.consume_till(|c| { ! c.is_digit ()});
^~~~~~~
example.rs:65:2: 65:2 note: previous borrow ends here
example.rs:59 fn main() {
...
example.rs:65 }
基本上,我已经制作了类似于我自己的迭代器的东西,而等同于“next”方法的东西需要&mut self
。因此,我不能在同一范围内多次使用该方法。
但是,Rust std库有一个迭代器,可以在同一个范围内多次使用,并且它还需要一个&mut self
参数。
let test = "this is a string";
let mut iterator = test.chars();
iterator.next();
iterator.next(); // This is PERFECTLY LEGAL
那么为什么Rust std库代码编译,但我的不编译? (我确信终生注释是它的根源,但我对生命的理解并不会导致我期待一个问题。)
这是我的完整代码(只有60行,缩短了这个问题):
use std::str::{Chars};
use std::iter::{Enumerate};
#[deriving(Show)]
struct ConsumeResult<'lt> {
value: &'lt str,
startIndex: uint,
endIndex: uint,
}
struct Scanner<'lt> {
code: &'lt str,
char_iterator: Enumerate<Chars<'lt>>,
isEof: bool,
}
impl<'lt> Scanner<'lt> {
fn new<'lt>(code: &'lt str) -> Scanner<'lt> {
Scanner{code: code, char_iterator: code.chars().enumerate(), isEof: false}
}
fn assert_not_eof<'lt>(&'lt self) {
if self.isEof {fail!("Scanner is at EOF."); }
}
fn next(&mut self) -> Option<(uint, char)> {
self.assert_not_eof();
let result = self.char_iterator.next();
if result == None { self.isEof = true; }
return result;
}
fn consume_till<'lt>(&'lt mut self, quit: |char| -> bool) -> ConsumeResult<'lt> {
self.assert_not_eof();
let mut startIndex: Option<uint> = None;
let mut endIndex: Option<uint> = None;
loop {
let should_quit = match self.next() {
None => {
endIndex = Some(endIndex.unwrap() + 1);
true
},
Some((i, ch)) => {
if startIndex == None { startIndex = Some(i);}
endIndex = Some(i);
quit (ch)
}
};
if should_quit {
return ConsumeResult{ value: self.code.slice(startIndex.unwrap(), endIndex.unwrap()),
startIndex:startIndex.unwrap(), endIndex: endIndex.unwrap() };
}
}
}
}
fn main() {
let codeToScan = "40 + 2";
let mut scanner = Scanner::new(codeToScan);
let first_token = scanner.consume_till(|c| { ! c.is_digit ()});
println!("first token is: {}", first_token);
// scanner.consume_till(|c| { c.is_whitespace ()}); // WHY DOES THIS LINE FAIL?
}
答案 0 :(得分:17)
这是一个更简单的例子:
struct Scanner<'a> {
s: &'a str
}
impl<'a> Scanner<'a> {
fn step_by_3_bytes<'a>(&'a mut self) -> &'a str {
let return_value = self.s.slice_to(3);
self.s = self.s.slice_from(3);
return_value
}
}
fn main() {
let mut scan = Scanner { s: "123456" };
let a = scan.step_by_3_bytes();
println!("{}", a);
let b = scan.step_by_3_bytes();
println!("{}", b);
}
如果you compile that,您会收到类似问题代码的错误:
<anon>:19:13: 19:17 error: cannot borrow `scan` as mutable more than once at a time
<anon>:19 let b = scan.step_by_3_bytes();
^~~~
<anon>:16:13: 16:17 note: previous borrow of `scan` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `scan` until the borrow ends
<anon>:16 let a = scan.step_by_3_bytes();
^~~~
<anon>:21:2: 21:2 note: previous borrow ends here
<anon>:13 fn main() {
...
<anon>:21 }
^
现在,要做的第一件事是避免影响生命周期:也就是说,此代码有两个生命周期'a
,而'a
中的所有step_by_3_bytes
都指向{{ 1}}在那里声明,它们实际上都没有引用'a
中的'a
。我将重命名内部的一个,以便清楚地了解正在发生的事情
Scanner<'a>
此处的问题是impl<'a> Scanner<'a> {
fn step_by_3_bytes<'b>(&'b mut self) -> &'b str {
将'b
对象与self
返回值相关联。编译器必须假设从外部查看str
的定义时,调用step_by_3_bytes
可以进行任意修改,包括使先前的返回值无效(这是编译器的工作原理,类型检查纯粹是基于关于被调用的东西的类型签名,没有内省)。也就是说,可以定义为
step_by_3_bytes
现在,每次调用struct Scanner<'a> {
s: &'a str,
other: String,
count: uint
}
impl<'a> Scanner<'a> {
fn step_by_3_bytes<'b>(&'b mut self) -> &'b str {
self.other.push_str(self.s);
// return a reference into data we own
self.other.as_slice()
}
}
都会开始修改先前返回值来自的对象。例如。它可能导致step_by_3_bytes
重新分配,从而在内存中移动,将任何其他String
返回值保留为悬空指针。 Rust通过跟踪这些引用来防止这种情况,如果它可能导致此类灾难性事件,则禁止突变。回到我们的实际代码:编译器只是通过查看&str
/ main
的类型签名来检查{{1}},因此它只能假设最坏的情况(即示例)我刚才给了。)
让我们退后一步:好像我们刚刚开始并且不知道我们想要哪些生命周期的返回值,所以我们只是让他们匿名(实际上不是有效的Rust):
step_by_3_bytes
现在,我们可以问一个有趣的问题:我们想要哪些生命周期?
几乎总是最好注释最长的有效生命周期,并且我们知道我们的返回值适用于consume_till
(因为它直接来自impl<'a> Scanner<'a> {
fn step_by_3_bytes<'b>(&'_ mut self) -> &'_ str {
字段,并且'a
有效s
)。也就是说,
&str
对于其他'a
,我们实际上并不关心:作为API设计者,我们没有任何特殊的愿望或需要将impl<'a> Scanner<'a> {
fn step_by_3_bytes<'b>(&'_ mut self) -> &'a str {
借用与任何其他引用连接(与返回不同)值,我们想要/需要表达它来自哪个内存)。所以,我们不妨把它关掉
'_
self
未使用,因此可以将其杀死,留下我们
impl<'a> Scanner<'a> {
fn step_by_3_bytes<'b>(&mut self) -> &'a str {
这表示'b
指的是至少对impl<'a> Scanner<'a> {
fn step_by_3_bytes(&mut self) -> &'a str {
有效的某些内存,然后将引用返回到该内存中。 Scanner
对象基本上只是一个操作这些视图的代理:一旦你有了它返回的引用,就可以丢弃'a
(或调用更多的方法)。
self
Applying this change代码只是调整Scanner
的定义。
struct Scanner<'a> {
s: &'a str
}
impl<'a> Scanner<'a> {
fn step_by_3_bytes(&mut self) -> &'a str {
let return_value = self.s.slice_to(3);
self.s = self.s.slice_from(3);
return_value
}
}
fn main() {
let mut scan = Scanner { s: "123456" };
let a = scan.step_by_3_bytes();
println!("{}", a);
let b = scan.step_by_3_bytes();
println!("{}", b);
}
那么为什么Rust std库代码编译,但我的不编译? (我确信终生注释是它的根源,但我对生命的理解并不会导致我期待一个问题。)
这里存在轻微(但不是很大)差异:consume_till
只返回fn consume_till(&mut self, quit: |char| -> bool) -> ConsumeResult<'lt> {
,即返回值没有生命周期。 Chars
方法(基本上)具有签名:
char
(它实际上属于next
特质impl<'a> Chars<'a> {
fn next(&mut self) -> Option<char> {
,但这并不重要。)
你在这里的情况类似于写作
Iterator
(类似于“生命周期的错误链接”,细节不同。)
答案 1 :(得分:3)
让我们看一下consume_till
。
需要&'lt mut self
并返回ConsumeResult<'lt>
。这意味着,生成'lt
(输入参数self
的借用持续时间)将是输出参数(返回值)的持续时间。
另一种说法是,在调用consume_till
后,您不能再次使用self
,直到其结果超出范围。
该结果已放入first_token
,first_token
仍在最后一行的范围内。
为了解决这个问题,你必须让first_token
超出范围;在它周围插入一个新块将执行此操作:
fn main() {
let code_to_scan = "40 + 2";
let mut scanner = Scanner::new(code_to_scan);
{
let first_token = scanner.consume_till(|c| !c.is_digit());
println!("first token is: {}", first_token);
}
scanner.consume_till(|c| c.is_whitespace());
}
所有这一切都是有道理的:当你在Scanner
中引用某些内容时,让你修改它是不安全的,以免引用无效。这是Rust提供的内存安全性。