1
votes

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?

1
you can #[derive(Clone)] and clone it. If the same func is supposed to be shared, then you have a self-referential struct problem, and you might want to look into reference counted pointers. - Ibraheem Ahmed
You seem to have a FunctionObject that you want to store both in stack and frame. Maybe a better solution is to reference the FunctionObject instead in stack and frame, or if that is not possible, simplify FunctionObject to itself contain a reference to non-copiable data and then be trivially copiable. - Emoun
@Emoun I have tried passing the reference to both the stack and the frame but that results in the error 'func' does not live long enough. Could you explain what you mean by "simplify FunctionObject to itself contain a reference to non-copiable data and then be trivially copiable"? - Fausto German

1 Answers

1
votes

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.