Locally owned reference considered borrowed

201 views Asked by At

I've got a structural type with an Option<String> field. Within a method on my optional type, I want to match on that field and extract the value into the local scope. I understand that I need to convince the borrow checker not to drop the memory pointed to within my structural type; I'm not sure how to do that.

For context, here's an obviously wrong example.

struct Cell {
    data: Option<String>,
}

impl Cell {
    fn match_me(&self) -> String {
        match self.data {
            Some(x) => x,
            None => "match failed".to_owned(),
        }
    }
}

fn main() {
    let data = Some("hello".to_owned());
    let my_cell = Cell { data };
    let result = my_cell.match_me();
    print!("{}", result);
}

This program is obviously wrong because I'm moving the value inside of x into the local scope, which means it will be dropped when the method returns; however, since the struct outlives the method call, the value will still be accessible elsewhere, which would produce a use after free error.

Since I want to use the Some() value without discarding it, I figured I should reference count it. Attempt two:

use std::rc::Rc;

struct Cell {
    data: Rc<Option<Rc<String>>>,
}

impl Cell {
    fn match_me(&self) -> String {
        let local = self.data.clone();
        match *local {
            Some(x) => *Rc::clone(&x),
            None => "match failed".to_owned(),
        }
    }
}

fn main() {
    let data = Rc::new(Some(Rc::new("hello".to_owned())));
    let my_cell = Cell { data };
    let result = my_cell.match_me();
    print!("{}", result);
}

However, despite cloning these references, I'm still getting the borrow error.

   Compiling playground v0.0.1 (file:///playground)
error[E0507]: cannot move out of borrowed content
  --> src/main.rs:10:15
   |
10 |         match *local {
   |               ^^^^^^ cannot move out of borrowed content
11 |             Some(x) => *Rc::clone(&x),
   |                  - hint: to prevent move, use `ref x` or `ref mut x`

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:11:24
   |
11 |             Some(x) => *Rc::clone(&x),
   |                        ^^^^^^^^^^^^^^ cannot move out of borrowed 
content

Do I really have no recourse except to clone the item itself?

Playground Link to the obviously wrong example.

Playground Link to a reference counted nightmare.

2

There are 2 answers

4
Sven Marnach On BEST ANSWER

It is unclear to me what you are trying to achieve, but I can offer a few options that work.

  1. If you only want to return a reference to the string without changing anything in Cell, you should return &str rather than String from match_me(). Apart from the return type, you only need minor changes to match_me() in your first example:

    fn match_me(&self) -> &str {
        match &self.data {
            Some(x) => x,
            None => "match failed",
        }
    }
    

    The rest of your code can remain unchanged.

  2. If you want to move the string out of your structure, you need to receive self as mutable reference:

    fn match_me(&mut self) -> String {
        match self.data.take() {
            Some(x) => x,
            None => "match failed".to_owned(),
        }
    }
    

    This will leave a None in self.data after calling the function, since we are moving the string out and transferring ownership back to the caller.

  3. And finally, if for some reason you really need shared ownership of the string, you can also use a reference-counted pointer:

    struct Cell {
        data: Option<Rc<String>>,
    }
    
    impl Cell {
        fn match_me(&self) -> Rc<String> {
            match &self.data {
                Some(x) => x.clone(),
                None => Rc::new("match failed".to_owned()),
            }
        }
    }
    

    This is a lot more uncommon than the other options, and nothing in your question hints that you actually need this, so I'm only including this for completeness.

My best guess is that you actually want the first option.

2
Artemij Rodionov On

I want to extend the answer of Sven Marnach and propose an another one option if you want to return &String and avoid clone

impl Cell {
    // it is better to use `Result` type in case when an error may be occurred
    fn match_me(&self) -> Result<&String, &'static str> {
        match self.data {
            // `ref` provides to bind a reference to a variable
            // cel: &String 
            Some(ref cel) => Ok(cel),
            None => Err("match failed"),
        }
    }
}

fn main() {
    ...
    // add unwrap to get a value
    let result = my_cell.match_me().unwrap();
    ...
}