Finding A Solution to Ownership

I have the following code:

struct Variable {
    value: i32,
}

struct Statement<'a> {
    variables: Vec<&'a Variable>,
}

impl Statement<'_> {
    fn to_string(&self) -> String {
        let mut result = String::new();
        for variable in &self.variables {
            result += &variable.value.to_string();
            result += ", ";
        }
        result
    }
}

struct Module<'a> {
    variables: Vec<Variable>,
    statements: Vec<Statement<'a>>,
}

impl<'a> Module<'a> {
    fn add_statement(&mut self, statement: Statement<'a>) -> &Statement {
        self.statements.push(statement);
        self.statements.last().unwrap()
    }

    fn add_variable(&mut self, variable: Variable) -> &Variable {
        self.variables.push(variable);
        self.variables.last().unwrap()
    }
}

fn main() {
    let mut module = Module {
        variables: Vec::new(),
        statements: Vec::new(),
    };

    let var0 = module.add_variable(Variable { value: 10 });
    println!("Init var0: {}", var0.value);

    let var1 = module.add_variable(Variable { value: 11 });
    println!("Init var1: {}", var1.value);

    let stmt0 = module.add_statement(Statement {
        variables: Vec::new(),
    });
    stmt0.variables.push(var0);
}

There are errors in main, as I believe var0 and var1 both borrow module as mutable. So when I try to mutate module, conflicts arise. I'm wondering what's the best solution to this.

Ideally, I want module to own all the variables and statements; and statements only have a reference to the variables it needs (e.g. for self-stringification). Therefore, the lifetime of statements and variables should be less than the module that owns it. I do see a problem with the possibility of a module's variable being removed and therefore there being an invalid reference to a variable, but I'm unsure about the best way to address this issue as well.

Below is more or less my end goal, but with extreme usage of Rc and RefCell, avoiding much of Rust's ownership principles.

use std::cell::RefCell;
use std::rc::Rc;

struct Variable {
    value: i32,
}

struct Statement {
    variables: Vec<Rc<RefCell<Variable>>>,
}

impl Statement {
    fn to_string(&self) -> String {
        let mut result = String::new();
        for variable in &self.variables {
            result += &variable.borrow().value.to_string();
            result += ", ";
        }
        result
    }
}

struct Module {
    variables: Vec<Rc<RefCell<Variable>>>,
    statements: Vec<Rc<RefCell<Statement>>>,
}

impl Module {
    fn add_statement(&mut self, statement: Statement) -> Rc<RefCell<Statement>> {
        let rc_statement = Rc::new(RefCell::new(statement));
        self.statements.push(rc_statement.clone());
        rc_statement
    }

    fn add_variable(&mut self, variable: Variable) -> Rc<RefCell<Variable>> {
        let rc_variable = Rc::new(RefCell::new(variable));
        self.variables.push(rc_variable.clone());
        rc_variable
    }
}

fn main() {
    let mut module = Module {
        variables: Vec::new(),
        statements: Vec::new(),
    };

    let var0 = module.add_variable(Variable { value: 10 });
    println!("Init var0: {}", var0.borrow().value);

    let var1 = module.add_variable(Variable { value: 11 });
    println!("Init var1: {}", var1.borrow().value);

    let stmt0 = module.add_statement(Statement {
        variables: Vec::new(),
    });
    stmt0.borrow_mut().variables.push(var0.clone());
    stmt0.borrow_mut().variables.push(var1.clone());
    println!("Init stmt0: {}", stmt0.borrow().to_string());

    let var2 = module.add_variable(Variable { value: 12 });

    let stmt1 = module.add_statement(Statement {
        variables: Vec::new(),
    });
    println!("Init stmt1: {}", stmt1.borrow().to_string());
    println!("Init var1: {}", var1.borrow().value);

    stmt1.borrow_mut().variables.push(var1.clone());
    println!("After 1 stmt1: {}", stmt1.borrow().to_string());

    stmt1.borrow_mut().variables.push(var2.clone());

    println!("End stmt0: {}", stmt0.borrow().to_string());
    println!("End stmt1: {}", stmt1.borrow().to_string());
}

Module is self-referential, so

  • split the ownership and its reference data structure apart
  • or use self-referential crates like self_cell - Rust

I recommend the usual graph way to store Variables and Statements in module, don't give out direct references but use indices. see also:

this indicates to me your variables are not lexically scoped, so in a sense variables are just keys to a (runtime) environment dictionary, so using the variable names in your statement instead could also be a solution.

I don't think I have much to add regarding your actual question, but I recommend using

#![deny(elided_lifetimes_in_paths)]

As it will highlight errors such as this one:

-fn add_statement(&mut self, statement: Statement<'a>) -> &Statement {
+fn add_statement(&mut self, statement: Statement<'a>) -> &Statement<'a> {

Sometimes you can get away with the shorter return lifetime, but often you can't.


Therefore, the lifetime of statements and variables should be less than the module that owns it.

I also suggest keeping the concept of value liveness scopes and Rust lifetimes distinct.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.