0

I am trying to learn Rust, specifically lifetimes and smart pointers and would like to know the most Rusty way of doing this.

Specifically, assume we are designing a mock language and "analyzer":

a=1 # each line is of form name=number|name
b=2
c=a # a is "copied" to c and should have value 1
a=4 # a and all its references should now have value 4

Lets design some bogus "Memory Node" abstraction

Code Memory Nodes
a=1 Node { name: "a", value: 4 }
b=2 Node { name: "b", value: 2 }
c=a Node { name: "c", value: 1 }
a=4 Node { name: "a", value: 4 }

Note: We want only three memory nodes to exist and not four like a AST

So that I can resolve values of variables, I must keep a context from name to "Memory Node"

To keep my analyzer's API clean I don't want to expose this Context.

Lets say our base structs look like these:

struct Root {
    pub nodes: Vec<&Node>,
}

struct Node {
    pub name: String,
    pub value: u64,
}

struct Context {
    nodes: HashMap<String, Node>,
}

impl Node {
    fn new(line: &str, context: &mut Context) -> Node {
         // Add self to context
         // If node exists in context, update it and return the same
         // return new node
    }
}

However, we cant keep copies of Node in both Context and Root.

One way is to wrap node up like Rc<RefCell<Node>> everywhere, this would make the client API not neat. I would ideally like to maintain Root containing a Vec of &Node as the public API. An example implementation is here https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7ec7ecd29eea2626a11b90c829777e30

When I try this using lifetimes I get an error that I cant really circumvent - https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5c5054872806b94a5b3a8ad4a75c1282

It gives:

error[E0499]: cannot borrow `context` as mutable more than once at a time
  --> src/main.rs:49:39
   |
43 |     fn new(text: &str) -> Root {
   |                  - let's call the lifetime of this reference `'1`
...
49 |             let node = Node::new(row, &mut context);
   |                                       ^^^^^^^^^^^^ `context` was mutably borrowed here in the previous iteration of the loop
...
52 |         Root { nodes, context }
   |         ----------------------- returning this value requires that `context` is borrowed for `'1`

error[E0505]: cannot move out of `context` because it is borrowed
  --> src/main.rs:52:23
   |
43 |     fn new(text: &str) -> Root {
   |                  - let's call the lifetime of this reference `'1`
...
49 |             let node = Node::new(row, &mut context);
   |                                       ------------ borrow of `context` occurs here
...
52 |         Root { nodes, context }
   |         --------------^^^^^^^--
   |         |             |
   |         |             move out of `context` occurs here
   |         returning this value requires that `context` is borrowed for `'1`

Some errors have detailed explanations: E0499, E0505.
For more information about an error, try `rustc --explain E0499`.

Alternatively what are other Rusty ways to do this, without using Smart pointers if possible?

Megh Parikh
  • 924
  • 2
  • 7
  • 25
  • You haven't really described what `Root` and `Context` do differently -- what are they for? What is `Root` supposed to do, and which one is supposed to own the `Node`s? – cdhowie Feb 17 '22 at 01:27
  • @cdhowie lets assume this was a full featured language, Root would represent a tree-like structure consisting say blocks, statements, etc, while Context will have the relevant "Context" to look up variables from to do the correct semantic analysis – Megh Parikh Feb 17 '22 at 01:30
  • Okay, so what that sounds like to me is that `Root` should own the tree structure, and each node in the tree might implement a trait (what you call a context) allowing access to the particular variables available from that location. You can then pass a reference to that trait to the analyzer. However, all nodes existing on the root itself doesn't make a whole lot of sense either, if your hypothetical language allows shadowing or local variables. – cdhowie Feb 17 '22 at 01:31
  • This is *the* #1 most linked [tag:rust] question on SO. The tl;dr is: don't try to put references to things in the same data structure as the things themselves. If you insist on this self-referential design, you will be fighting the language every step of the way. But if you must, the answers to the linked question contain all the workarounds you need. – trent Feb 17 '22 at 01:47
  • @trent Thanks the linked question has very good detailed answers and was exactly what I was looking for! Sorry for asking the same question again. – Megh Parikh Feb 18 '22 at 02:55
  • No need to apologize; *most* questions are duplicates, but it may not always be obvious when you're asking it. – trent Feb 18 '22 at 06:04

0 Answers0