3
votes

I am developing some basic data structures to learn the syntax and Rust in general. Here is what I came up with for a stack:

#[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");
        }
    }
}

I get this interesting error:

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

Why can't I just call pop on my mutable struct?

Why does pop borrow the value? If I add a .expect() after it, it is ok, it doesn't trigger that error. I know that is_empty takes an immutable reference, if I switch it to mutable I just get a second mutable borrow.

2

2 Answers

7
votes

Your pop function is declared as:

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>

This says that the Result::Err variant is a string that lives as long as the stack you are calling it on. Since the input and output lifetimes are the same, the returned value might be pointing somewhere into the Stack data structure so the returned value must continue to hold the borrow.

If I add a .expect() after it, it is ok, it doesn't trigger that error.

That's because expect consumes the Result, discarding the Err variant without ever putting it into a variable binding. Since that's never stored, the borrow cannot be saved anywhere and it is released.

To solve the problem, you need to have distinct lifetimes between the input reference and output reference. Since you are using a string literal, the easiest solution is to denote that using the 'static lifetime:

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

Extra notes:

  • Don't call return explicitly at the end of the block / method: return Result::Ok(last) => Result::Ok(last).
  • Result, Result::Ok, and Result::Err are all imported via the prelude, so you don't need to qualify them: Result::Ok(last) => Ok(last).
  • There's no need to specify types in many cases let len: usize = self.data.len() => let len = self.data.len().
4
votes

This happens because of lifetimes. When you construct a method which takes a reference the compiler detects that and if no lifetimes are specified it "generates" them:

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");
    }
}

This is what compiler sees actually. So, you want to return a static string, then you have to specify the lifetime for a &str explicitly and let the lifetime for the reference to mut self be inferred automatically:

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