Cannot move out of borrowed content

I do not understand why this works:

enum Allergen {
    Cats=1,
    Chocolate=2,
}

fn is_allergic_to(n:i32, a: Allergen) -> bool {
    match n & (a as i32) {
        0 => false,
        _ => true,
    }   
}

fn main (){
    is_allergic_to(1, Allergen::Cats);
}

but this does not:

enum Allergen {
    Cats=1,
    Chocolate=2,
}

fn is_allergic_to(n:i32, a: &Allergen) -> bool {
    match n & (*a as i32) {
        0 => false,
        _ => true,
    }   
}

fn main (){
    is_allergic_to(1, &Allergen::Cats);

}

<anon>:7:16: 7:18 error: cannot move out of borrowed content 
<anon>:7 match n & (*a as i32) {

You can’t move out of something that is borrowed, because the move would destroy it. The reason why it would work if you changed Allergen to i32 is that i32 implements Copy, so it can be copied without any risk of ending up with an invalid state. You can safely implement Copy (and clone) for Allergen as well, since it’s meant to represent a number, and your second piece would work perfectly. You would, in fact, not even have to take a reference if it’s Copy because copying it would not cost any more.

It’s like if I borrowed a book from you and then burned the book. You’d rightfully be mad because I burned your book. It’d be okay if I burned my book but not your book. You’d be mad you (or someone else) didn’t enforce respectful behaviour towards your book.

In the same circumstance, Rust will actively enforce reasonable behaviour toward a borrow. When Rust recognizes a borrow, it forbids things which only the owner is allowed to do (such as taking possession of it); hence, this is borrowed, you cannot take ownership of it (that is, cannot move out of borrowed content).

1 Like

Thank you.
I guess I don’t understand how *a is “moving outside of borrowed content”.
I realize that a is borrowed, but I didn’t realize that I could not dereference it…

Deriving the Copy trait solved my problem:

#[derive(Eq, PartialEq, Debug, Copy, Clone)]
enum Allergen {
    Cats=1,
    Chocolate=2,
}

fn is_allergic_to(n:i32, a: &Allergen) -> bool {
    match n & (*a as i32) {
        0 => false,
        _ => true,
    }   
}

fn main (){
    is_allergic_to(1, &Allergen::Cats);
}

Just as a note, this isn’t always possible. For example, if your struct Allergen had contained a Vec, then deriving copy wouldn’t work and you would have to manually clone.

The argument a: &Allergen is already borrowed in the first place.
Lets try to figure this out in memory for more clarity: lets say Allergen enum is on the stack at 0xFF.
At an offset of 4 bytes, we come across memory address for Allergen::Cats (so at 0xFB).
You create a pointer 'a' (assigned to some stack location - lets say 0x110) that stores 0xFB. Important point to remember is although a points to Allergen::Cats, it doesn't own it.
Now you dereference 'a' - which is nothing but a memory fetch operation (load 0xFB and fetch the data there) and should give you 1, BUT it is not an i32, instead is an Allergen type. Until now everything is ok.

The Allergen type is however a reference type (i.e. there is no copy trait implemented for it) so when a copy operation is run on it, the compiler will instead attempt an implicit move operation (because that is how reference types are copied).
The casting operation 'as' does a few things: it allocates new memory based on the new type, copies the value over to the new location etc.
Therefore when rustc encounters the copy operation, it attempts a move and immediately fails since you borrowed in the first place and aren't supposed to move the value. (as its against the ownership principles)

However a borrowed type is still allowed to be copied (because that creates a separate memory and doesn't go against the ownership principles and they will both be popped off the stack at the end of scope). Therefore if you implement a copy trait for Allergen the second way works too.

As for the first solution, since you moved the enum into the argument with a: Allergen , the move operation attempt on Allergen type 'a' during the cast operation will go through. Point to be noted however is that 'a' is moved and not copied. For e.g. if you tweak your code as follows:

fn is_allergic_to(n:i32, a: Allergen) -> Allergen {
    match n & (a as i32) {
        0 => Allergen::Chocolate,
        _ => a,
    }   
}

you will see the compiler complains that a is moved and cannot be used.