Search code examples
rust

Rust - Passing Rc<> references between different structs


I'm currrently trying to implement a data structure which requires its instantiator to pass a reference to itself. I've heard about using Rc<> in these situations to avoid lifetime limitations, though I'm not entirely sure how to implement it in this particular structure.

The "parent" looks like this:

pub struct ModuleFile {
    /// Info relating to how the other fields should be read.
    pub header: ModuleHeader,
    /// Metadata regarding compression and layout of files (tags)
    pub files: Vec<ModuleFileEntry>,
    /* others */
}

And the "ModuleFileEntry" should include a reference to its respective ModuleFile:

pub struct ModuleFileEntry {
    pub module: Rc<ModuleFile>,
    pub unknown: u8,
    pub flags: FileEntryFlags,
 }

I've never properly worked with reference counting before, so this is more of a "How can I" question- or if there's a better approach to what I'm trying to do.


Solution

  • Rc

    Rc is like a immutable reference, you can have a lot of it but it is not mutable. Once you put your struct into an Rc<T> you cannot mutate it any futher. This make it impossible to do self reference.

    use std::rc::Rc;
    
    pub struct ModuleFile {
        pub files: Vec<ModuleFileEntry>,
    }
    
    pub struct ModuleFileEntry {
        pub module: Rc<ModuleFile>,
    }
    
    fn main() {
        let module = Rc::new(ModuleFile { files: vec![] });
    
        let entry = ModuleFileEntry {
            module: module.clone(),
        };
    
        module.files.push(entry);
        /*
        error[E0596]: cannot borrow data in an `Rc` as mutable
        --> ***\src/main.rs:18:5
           |
        18 |     module.files.push(entry);
           |     ^^^^^^^^^^^^ cannot borrow as mutable
           |
           = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<ModuleFile>`
    
        For more information about this error, try `rustc --explain E0596`.
        error: could not compile `fuck_around` (bin "fuck_around") due to 1 previous error
        */
    }
    

    Internal Mutability and RefCell

    Sometime you know that your code will not break the ownership rule, but your compile with static analysis cannot. Thus, we will want to make a contract to the compiler, to promise YOU will not break the ownership rule:

    • Only one mutable reference, or
    • One or more immutable reference.

    Edit

    Unlike in unsafe, RefCell will check and enforce ownership rule at runtime, if your code violate the ownership rule in runtime, it will panic.

    If you want you code to be thread safe, you will need to use Arc<Mutex<_>>.

    use std::cell::RefCell;
    use std::rc::Rc;
    
    pub struct ModuleFile {
        pub files: Vec<ModuleFileEntry>,
    }
    
    pub struct ModuleFileEntry {
        //          A counted reference
        //          |  that you can mut borrow 
        //          |  |     L> (I pinky promise I will follow ownership rule in runtime, just let me compile)
        //          |  |
        //          v  V
        pub module: Rc<RefCell<ModuleFile>>,
    }
    
    fn main() {
        let module = ModuleFile { files: vec![] };
        let module = Rc::new(RefCell::new(module));
    
        let entry_0 = ModuleFileEntry {
            module: module.clone(),
        };
        let entry_1 = ModuleFileEntry {
            module: module.clone(),
        };
    
        module.borrow_mut().files.push(entry_0);
        module.borrow_mut().files.push(entry_1);
    
        let _: &ModuleFileEntry = module
            // immutable borrow
            .borrow()  // `Ref<'_, ModuleFile>`
            .files
            .get(0)
            .unwrap()  // &ModuleFileEntry
            .module
            .borrow()  // `Ref<'_, ModuleFile>` again, multiple immutable borrow is allowed
            .files
            .get(1)
            .unwrap();
    
        let _: &mut ModuleFileEntry = module
            // mutable borrow
            .borrow_mut()  // `RefMut<'_, ModuleFile>`
            .files
            .get_mut(0)
            .unwrap();
    
        let _ = module
            // mutable borrow
            .borrow_mut()  // `RefMut<'_, ModuleFile>`
            .files
            .get(0)
            .unwrap()
            .module
            .borrow() // Opss, I break the pinky promise here, panic!
            /*
            thread 'main' panicked at fuck_around\src/main.rs:56:10:
            already mutably borrowed: BorrowError
            note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
            error: process didn't exit successfully: `***.exe` (exit code: 101)
            */
            .files
            .get(1)
            .unwrap();
    }