Beginner tips on implementation design (pointers, references)

I just started to learn Rust, so excuse me if I get terminlogy and/or ideas wrong.
Basically, I am coming from a C(++) background, so it's hard for me to adapt my code to Rust's memory management.
I am writing a simple toy language interpreter and I struggle with the design of my interpreter. I used a parser generator (lalrpop) to build an AST from a source code and use a Visitor pattern to interpret it.
The visitor function looks like this:
pub fn visit(c: &Context, n: &Node) -> Box<VVal> { ... }
The Context type contains runtime information, mainly variables:
pub struct Context { vars: HashMap<String, Box<VVal>> }
I managed to write a working 'visit' function without struggling much with references, borrowing etc.
However, as I added variables ('Context'), every time I use a variable (e.g. put into 'Context' or read out of it), I get an error:
cannot move out of xxx which is behind a shared reference
I "solved" this by cloning every time I set/get a variable, but that's not manageable.
I thought, where my design is wrong and thought about values borrowing and referencing, and I thought of this:
When a value is created in the 'visit' function and placed into the Context, it's not used in the function anymore => it really can be "moved" from the variable before into the Context. Then, when it's retrieved from the context, getting a mutable reference to it should be enough.
Finally, I put all 'VVal' instances in the Box, because I thought cloning the Box only makes a new pointer, not clones the whole value. This has proved not true.
Can anyone help me with what I'm not getting right?

Sounds like you are accessing the fields with context.field? You can get a borrow of the field with &context.field, which will not take ownership of the field and thus avoid the error you mentioned.

Of course you cannot modify the context through a &Context, as that is an immutable reference.

Sure, you can definitely get a mutable reference to something stored in the context as long as you have a mutable reference to the context itself. Note that getting multiple mutable references is more difficult, because you have to prove to the compiler that you only have mutable references to distinct elements — several mutable references to the same item is not allowed.

A Box owns its contents. It's basically a unique_ptr in C++ and creating a clone requires cloning the contents as otherwise they would no longer be uniquely owned by that box.

You can use an Rc to share the ownership of the value. This is a reference-counted smart pointer. Note that since it is shared, you cannot mutate the contents unless you use a RefCell inside.

Thanks for a fast reply!

This is just my mistake in rewriting the code into the post, of course I use a mutable reference. Sorry.

I thought that references (immutable or otherwise) can be multiple. My mistake. So if I understand, there is no way to have multiple pointers to data other than the Rc, right?

I modified the code (changed the visit function to take Node by value, instead of reference), which helped. Now all the errors I get are saying that I cannot move out of Vec<...>.
I looked through the code and assured the function doesn't use the Vector variable anymore, but I still can't move its contents anymore, why is that?

Well, as long as they are immutable references, it is ok to have several. It is perhaps better to think of immutable references as shared references and mutable references as unique references. Read e.g. this blog post that goes into details about this.

Well likely the issue is simply that you haven't convinced the compiler that the vector is no longer used. It may be easier to use mem::replace to take the vector out, leaving an empty vector in its place? Sometimes you can also use Vec::drain to take ownership of the items in the vector without taking ownership of the vector itself.

Thanks again. I reduced the amount of cloning and I understand references better. But I have two more questions.
First, in one function, I used this hack which I'm not sure why it works and whether it should at all (I removed unnecessary context):

pub enum Foo {
fn process(f: Foo) { // here f is moved into the function (I think)
    match f {
        A(...) => ...,
        B(v) => {
            let mut v = v; // <- this looks weird
            // do something that mutates v

Second, I ran the program through perf and got that the program spends around 18% of its runtime in the cfree@GLIBC function. Is it normal for a Rust app to spend so much time freeing memory? (on the other hand, malloc takes only third the time)

Yes, that f is indeed moved into process. Note that since you now have ownership of f (and all of its contents), you can freely modify them, even if you didn't mark the argument as mutable by later rebinding the variable to a mutable binding.

That's what happened at let mut v = v;. You had this item v that you had ownership of. You had not made the binding mutable (which you can do with B(mut v) instead), but since you have ownership, you can just move it to a mutable let binding and modify it.

As for the time spent deallocating memory: That's what happens when you have many expensive clones in your program.

I think you should be able to write B(mut v) => { instead of the let-statement.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.