Cannot move out of borrowed content - fn parameter

I'm trying to create a visitor pattern for traversing an AST, and I have the following repro case which is similar to my actual project.

By searching through, I was able to solve it by using .clone() in the place where the error happens. I want to expose this traversal as a library and I don't think it's a good API if the users are expected to clone an input parameter if they want to even use it?

I'm not able to understand how to use lifetime parameter here in visit_* functions - because it always returns () and only reads that part of the AST and never returns anything.

struct Assignment {
    left: Identifier,
    right: Identifier,
}

struct Identifier {
    name: String,
    alias: Option<String>,
}

#[allow(unused_variables)]
trait Visitor {
    fn visit_assignment(&mut self, assignment: &Assignment) -> () {}
    fn visit_identifier(&mut self, id: &Identifier) -> () {}
}

struct Traversal {
    visitor: Box<Visitor>,
}

impl Traversal {
    fn handle_identifier(&mut self, identifier: &Identifier) {
        self.visitor.visit_identifier(&identifier);
    }
    fn handle_assignment(&mut self, assignment: &Assignment) {
        self.visitor.visit_assignment(&assignment);
        let left = &assignment.left;
        let right = &assignment.right;
        self.handle_identifier(&left);
        self.handle_identifier(&right);
    }
}

fn traverse(visitor: Box<Visitor>, root: &Assignment) {
    let mut traversal = Traversal { visitor };
    traversal.handle_assignment(&root);
}

struct PrintVisitor;
impl Visitor for PrintVisitor {
    fn visit_identifier(&mut self, id: &Identifier) {
        let alias = &id.alias.unwrap();
        println!("{}", alias);
    }
}

fn main() {
    let name = Identifier {
        name: "name".to_string(),
        alias: None,
    };
    let value = Identifier {
        name: "value".to_string(),
        alias: Some("foo".to_string()),
    };
    let assignment = Assignment {
        left: name,
        right: value,
    };

    let visitor = Box::new(PrintVisitor {});

    traverse(visitor, &assignment);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (file:///playground)
error[E0507]: cannot move out of borrowed content
  --> src/main.rs:42:22
   |
42 |         let alias = &id.alias.unwrap();
   |                      ^^ cannot move out of borrowed content

error: aborting due to previous error

For more information about this error, try `rustc --explain E0507`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.

id.alias.as_ref() will give you an Option<&String>, which you can turn into Option<&str> with (..).as_ref().map(|s| &s[..]).

Thanks a lot. That works, but I don't understand why.

My confusion is the following -

42 |         let alias = &id.alias.unwrap();
   |                      ^^ cannot move out of borrowed content

Here, the error is pointed at id. So I'd assume the problem is with using id. But in the solution, id.alias.as_ref(), the as_ref is called on the alias field. This is confusing to me as to how to figure out similar problems when the error is not pointed to the right location (or) am I reading the error message wrong?.

Also, since as_ref is for the alias and not the id, I'd assume the following works -

let alias = id.alias;
let alias = alias.as_ref();

However, here, it errors again

42 |         let alias = id.alias;
   |                     ^^------
   |                     |
   |                     cannot move out of borrowed content
   |                     help: consider using a reference instead: `&id.alias`

Also, as the compiler suggests, this is fixed with -

let alias = &id.alias;
// ---------^
let alias = alias.as_ref();

Any time you try to assign the field of a struct, borrowed or not, the compiler will attempt to move it (keep in mind that Rust is a move-by-default language). Unless the field type is Copy, you will be moving the value out. If you try to do that to a reference, you’ll get this type of error because you cannot move out from references.

let alias = &id.alias;

The above borrows the alias field rather than moving it.

let alias = id.alias.as_ref();

This calls as_ref on the alias field, which gives you an Option<&String> in this case but in general, it gives you an owned Option with a reference (borrow) inside of the value in the original Option (or None, of course, if the original is None).

These are just different ways to get references rather than causing a move to occur.

1 Like