Search code examples
rustenumsownership

Rust 'use of moved value' error when passing value into Enum variant


I am very new to Rust (just a couple of days). Of course, I am already getting stuck with the concept of ownership.

I have a rather lengthy problem, so here are all the relevant declarations I used:

pub struct ThePrimaryStruct<'a> {
    frames: Vec<Frame<'a>>,
    stack: Vec<Object<'a>>,
    ip: usize
}

pub struct FunctionObject<'a> {
    pub min_arity: i32,
    pub max_arity: i32,
    pub chunk: Chunk<'a>,
    pub name: &'a str,
}

pub struct Frame<'a> {
    pub function: FunctionObject<'a>,
    // ... other struct members
}

pub struct Chunk<'a> {
    pub codes: Vec<OpCode>, // OpCode is an enum
    pub locations: Vec<(i64, i64)>,
    pub constants: Vec<Object<'a>>
}

pub enum Object<'a> {
    Function(FunctionObject<'a>),
    // Other enum variants
}

The above code is not the problem. The problem arises when I implement the following method for ThePrimaryStruct:

pub(crate) fn the_function(&mut self, source: &'a str) -> SomeResult {
    // `Compiler::compile()` returns a FunctionObject struct.
    let func: FunctionObject = Compiler::compile(source);

    // The enum variant `Object::Function` takes ownership of `func`
    // The code works fine up until this point.
    self.stack.push(Object::Function(func));

    self.frames.push(Frame {
        // The struct member `function` should have the
        // exact same value as the one we just pushed into
        // the `self.stack` vector.
        function: func, // <---- Source of conflict
        // ... other struct members
    });

    self.run() // self.run() returns `SomeResult`
}

Running this results in the error:

error[E0382]: use of moved value: `func`
   |
37 |         let func: FunctionObject = Compiler::compile(source);
   |             ---- move occurs because `func` has type `FunctionObject<'_>`, which does not implement the `Copy` trait
...
40 |         self.stack.push(Object::Function(func));
   |                                          ---- value moved here
...
44 |             function: func,
   |                       ^^^^ value used here after move

I understand why this error occurs (or at least I think I understand): The Object::Function variant takes ownership of func, which is then dropped from memory when we are done pushing the object into self.stack. This then creates a conflict with the initialization of the Frame struct because I am trying to use a value that no longer exists.

I have tried implementing the Copy trait for the struct FunctionObject, but that creates even more problems because FunctionObject has a member of type Chunk<'a> which itself has vector members.

EDIT: Cloning definitely solves the problem. However, by cloning the FunctionObject I would be duplicating the data inside the chunk which could be of an arbitrarily long size. Referencing the FunctionObject in both the stack with Object::Function(&func) and in the frame with Frame { function: &func, ... } then results in a 'func' does not live long enough error.

Is there something fundamentally wrong with what I am trying to do?


Solution

  • You seem to have a FunctionObject that you want to store both in stack and frame. A better solution might be to not store the FunctionObject directly on the stack and frame, but use smart pointers instead.

    I will show you a solution using Rc, however, you seem to be making a compiler, so you might want a different data structure to store your function objects in, like an arena.

    The following code includes only the changes I made to your original, but you can also have a look on the playground where I got it to compile:

    pub struct Frame<'a> {
        pub function: std::rc::Rc<FunctionObject<'a>>,
        // ... other struct members
    }
    
    pub enum Object<'a> {
        Function(std::rc::Rc<FunctionObject<'a>>),
        // Other enum variants
    }
    
    pub(crate) fn the_function(&mut self, source: &'a str) -> SomeResult {
        // `Compiler::compile()` returns a FunctionObject struct.
        let func: FunctionObject = Compiler::compile(source);
        let func = std::rc::Rc::new(func);
        
        
        // The enum variant `Object::Function` takes ownership of `func`
        // The code works fine up until this point.
        self.stack.push(Object::Function(func.clone()));
    
        self.frames.push(Frame {
            // The struct member `function` should have the
            // exact same value as the one we just pushed into
            // the `self.stack` vector.
            function: func, // <---- No longer source of conflict
            // ... other struct members
        });
    
        self.run() // self.run() returns `SomeResult`
    }
    

    Rc manages an object by reference counting. When the last Rc is dropped, the value (FunctionObject) will also be dropped. Using .clone() on func makes a copy of the reference and increments the count, but doesn't copy the underlying object.