为什么调用 `fn pop(&mut self) -> Result<T, &str>` 会继续借用我的数据结构?

3

我正在开发一些基本的数据结构,以了解语法和Rust的基础知识。以下是我为栈开发的代码:

#[allow(dead_code)]
mod stack {
    pub struct Stack<T> {
        data: Vec<T>,
    }

    impl<T> Stack<T> {
        pub fn new() -> Stack<T> {
            return Stack { data: Vec::new() };
        }

        pub fn pop(&mut self) -> Result<T, &str> {
            let len: usize = self.data.len();

            if len > 0 {
                let idx_to_rmv: usize = len - 1;
                let last: T = self.data.remove(idx_to_rmv);
                return Result::Ok(last);
            } else {
                return Result::Err("Empty stack");
            }
        }

        pub fn push(&mut self, elem: T) {
            self.data.push(elem);
        }

        pub fn is_empty(&self) -> bool {
            return self.data.len() == 0;
        }
    }
}

mod stack_tests {
    use super::stack::Stack;

    #[test]
    fn basics() {
        let mut s: Stack<i16> = Stack::new();

        s.push(16);
        s.push(27);

        let pop_result = s.pop().expect("");

        assert_eq!(s.pop().expect("Empty stack"), 27);
        assert_eq!(s.pop().expect("Empty stack"), 16);

        let pop_empty_result = s.pop();

        match pop_empty_result {
            Ok(_) => panic!("Should have had no result"),
            Err(_) => {
                println!("Empty stack");
            }
        }

        if s.is_empty() {
            println!("O");
        }
    }
}

我遇到了一个有趣的错误:

我遇到了一个有趣的错误:

error[E0502]: cannot borrow `s` as immutable because it is also borrowed as mutable
  --> src/main.rs:58:12
   |
49 |         let pop_empty_result = s.pop();
   |                                - mutable borrow occurs here
...
58 |         if s.is_empty() {
   |            ^ immutable borrow occurs here
...
61 |     }
   |     - mutable borrow ends here

为什么我不能只在我的可变结构上调用pop

为什么pop会借用这个值?如果我在它后面添加一个.expect(),它就可以使用,不会触发那个错误。我知道is_empty需要一个不可变引用,如果我将其切换为可变,我只会得到第二个可变的borrow。

2个回答

7
您的pop函数声明如下:
pub fn pop(&mut self) -> Result<T, &str>

Due to lifetime elision, this expands to

pub fn pop<'a>(&'a mut self) -> Result<T, &'a str>

这段话的意思是,Result::Err变量是一个字符串,其生命周期与调用它的栈相同。由于输入和输出生命周期相同,返回值可能指向Stack数据结构中的某个位置,因此返回值必须继续持有借用。
如果在其后添加.expect(),那么就不会触发该错误,这是因为expect会消耗Result,丢弃Err变体而不将其放入变量绑定中。由于从未存储,因此借用无法保存到任何地方并被释放。
要解决这个问题,需要在输入引用和输出引用之间具有不同的生命周期。由于使用了字符串字面量,最简单的解决方法是使用'static生命周期。
pub fn pop(&mut self) -> Result<T, &'static str>

额外说明:

  • 在块/方法末尾不需显式调用 returnreturn Result::Ok(last) => Result::Ok(last)
  • ResultResult::OkResult::Err 都已经通过prelude 导入,因此您不需要对它们进行限定: Result::Ok(last) => Ok(last)
  • 在许多情况下没有必要指定类型 let len: usize = self.data.len() => let len = self.data.len()

添加return语句会改变什么吗?我这样做是出于个人喜好,以及为了打印变量,以便我知道现在是否正确理解了返回类型。 - Samuel Yvon
@SamuelYvon 不,我的建议都不会改变任何事情。你可以随心所欲,但强烈建议遵循所在语言的编码规范。像其他 Rust 代码一样编写 Rust 代码,像其他 JS 代码一样编写 JS 代码,像其他 Go 代码一样编写 Go 代码,像其他 Ruby 代码一样编写 Ruby 代码等等。 - Shepmaster
我完全同意! - Samuel Yvon

4
这是由于lifetimes引起的。当你构造一个需要参考的方法时,编译器会检测到它,如果没有指定生命周期,它会“生成”它们。
pub fn pop<'a>(&'a mut self) -> Result<T, &'a str> {
    let len: usize = self.data.len();

    if len > 0 {
        let idx_to_rmv: usize = len - 1;
        let last: T = self.data.remove(idx_to_rmv);
        return Result::Ok(last);
    } else {
        return Result::Err("Empty stack");
    }
}

这实际上是编译器看到的内容。因此,如果您想返回静态字符串,则必须显式指定 &str 的生命周期,并自动推断对 mut self 引用的生命周期:
pub fn pop(&mut self) -> Result<T, &'static str> {

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接