How to mutate values inside nested structs?

Problem

I'm having trouble figuring out how to mutate nested structs. I have a Folder struct that can hold Files and other Folders inside it.

type SubFolder = Vec<Box<Folder>>; 
type Files = Vec<String>;

#[derive(Debug)] 
struct Folder { 
    files: Files, 
    sub_folder: SubFolder 
}

I then want to add new files and folders to those nested folders after creating them, but I keep running into the same error when trying to do so.

impl Folder { 
    pub fn new() -> Self { 
        Self { 
            files: Vec::new(), 
            sub_folder: Vec::new(), 
        } 
    }

    pub fn add_file(&mut self, file: String) {
        self.files.push(file);
    }

    pub fn add_folder(&mut self, dir: Box<Folder>) {
        self.sub_folder.push(dir);
    }
}

fn main() { 
    let mut folder_1 = Folder::new(); 
    let mut folder_2 = Folder::new();

    folder_1.add_folder(Box::new(folder_2));
    folder_2.add_file(String::from("b.txt")); // Error
}

Error Message:

error[E0382]: borrow of moved value: `folder_2`
  --> src/main.rs:32:5
   |
29 |     let mut folder_2 = Folder::new();
   |         ------------ move occurs because `folder_2` has type `Folder`, which does not implement the `Copy` trait
30 |
31 |     folder_1.add_folder(Box::new(folder_2));
   |                                  -------- value moved here
32 |     folder_2.add_file(String::from("b.txt")); // Error
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ value borrowed here after move

Question

Is there a correct way to do this in Rust or is it only possible through unsafe means? Any suggestions and tips would be appreciated.

Depends on what exactly you want to achieve. Almost the whole point of Rust is to ensure that you can not have subfolder accessible simultaneously via folder which owns it and via some other variable.

After you did folder_1.add_folder you have made folder_2 impossible to use.

Of course it's still possible to access it via folder_1:

folder_1.sub_folder[0].add_file(String::from("b.txt")); // Works

You can also borrow it and use it this way:

let folder_2 = &mut folder_1.sub_folder[0];
folder_2.add_file(String::from("b.txt")); // Works

But you can never have two valid names for the same mutable object (except via interior mutability) — and, as I have already said, that that's the whole point!

I see. I have tried Rc and RefCell, but still got the same error. I probably misused it though.

My previous code:

use std::{cell::RefCell, rc::Rc};

type SubFolder = Vec<Rc<RefCell<Folder>>>; 
type Files = Vec<String>;

#[derive(Debug)] 
struct Folder { 
    files: Files, 
    sub_folder: SubFolder 
}

impl Folder { 
    pub fn new() -> Self { 
        Self { 
            files: Vec::new(), 
            sub_folder: Vec::new(), 
        } 
    }

    pub fn add_file(&mut self, file: String) {
        self.files.push(file);
    }

    pub fn add_folder(&mut self, dir: Rc<RefCell<Folder>>) {
        self.sub_folder.push(dir);
    }
}

fn main() { 
    let mut folder_1 = Folder::new(); 
    let mut folder_2 = Folder::new();

    folder_1.add_folder(Rc::new(RefCell::new(folder_2)));
    folder_2.add_file(String::from("b.txt")); // Error
}

You are still trying to invent some magic way of making sure that you can have shared access via two different variables without any trace of said shared access in the code where you use these.

Given the fact that Rust is made to ensure that's not possible… no wonder you are having trouble.

Your previous version was always correct, just you haven't done everything explicit.

fn main() { 
    let mut folder_1 = Folder::new(); 
    let mut folder_2 = Rc::new(RefCell::new(Folder::new()));

    folder_1.add_folder(folder_2.clone());
    folder_2.borrow_mut().add_file(String::from("b.txt")); // Works
}

Note that you still have to explicitly create two references with clone and then “activate” one of these shared references with borrow_mut… that way you can see that you are creating sharing on purpose and then you can also see where you are switching from “shared access” mode to “mutation” mode.

2 Likes

Got it, thank you! The code works!

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.