为什么Rust借用检查器拒绝此代码?

时间:2014-07-20 04:58:52

标签: rust

我从借用检查器收到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?
}

2 个答案:

答案 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(或调用更多的方法)。

总之,the full, working code

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_tokenfirst_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提供的内存安全性。