Creating a Dynamic object selection/reassignment mechanism for a MetaObject Protocol in Rust

102 Views Asked by At

I want to create a way for object functionality to be dynamically altered at runtime in Rust, similar to how MetaObjects are changed in languages like CLISP or Groovy.

The idea is to offer a black box to the user (or other components) through some kind of interface or pointer to a mutable pointer which in turn points to a mutable struct. This way the means of accessing the functionality won't change, but the underlying implementation can be changed on the fly (preferably by creating a new struct and then pointing the mutable pointer to it & then destroying the old struct). Currently I'm trying to figure out how to do this while keeping the borrow checker happy.

In short: {External (immutable) Pointer} -> {Private Mutable Pointer} -> {Mutable (or otherwise replaceable) Struct}

The intended purpose for this is to have a means for an AI or other program to dynamically improve an object or method while maintaining the same interface to the rest of the program so nothing breaks (same inputs & outputs, but the underlying code can change).

Please let me know if you have any ideas.

Currently trying to get the borrow checker to go along this while calling 'println!'. Currently getting an error saying that Mobj doesn't implement the Copy trait.

    #[derive(Debug)]
    struct Mobj {
        str: String
    }

    let mut m_ptr = Mobj {str: String::from("Object 1")};
    let ptr = m_ptr;

    println!("{:?}", m_ptr.str);
    println!("{:?}", ptr.str);

    // Reassign pointer to new object
    let m_ptr = Mobj {str: String::from("Object 2")};
    println!("{:?}", m_ptr.str);
    println!("{:?}", ptr.str); // Call to same, unchanged immutable pointer that points to mutable pointer
    // ^^^^ Still points to old 'Object 1'


    // Now points to the new 'Object 2', but might not be doing exactly what I want it to do.
    let ptr = &m_ptr;
    println!("{:?}", ptr.str);
1

There are 1 best solutions below

10
On BEST ANSWER

What you are trying to do won't work with normal references.

The biggest issue is the requirement to have an immutable reference to a mutable reference that then does some mutable stuff. If that were allowed, you could duplicate the immutable reference to then get multiple mutable references to the same object at the same time, which is considered undefined behavior.

To achieve that, you need some kind of runtime aliasing check. Depending on your usecase, this will be either RefCell (single-threaded) or Mutex (multi-threaded). This creates something called "interior mutability", meaning the runtime conversion from & to &mut. If you then also don't want to deal with lifetimes and simply want to use the variable in multiple parts of your program, you could further wrap it in an Rc (single-threaded) or Arc (multi-threaded). This isn't strictly necessary, but in many usecases, those two are used in conjunction.

In my example I assume you have no threads and therefore will use Rc<RefCell>.

Further, the usecase of having a fixed set of functionality while having the possibility to replace the actual object that provides said functionality fits perfectly to a trait. Then, you can store your object as Box<dyn Trait> to abstract away the real type.

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

trait MyTrait {
    fn do_something(&mut self);
}

#[derive(Debug)]
struct Mobj {
    str: String,
}

impl MyTrait for Mobj {
    fn do_something(&mut self) {
        println!("Mobj: \"{}\"", self.str);
    }
}

fn main() {
    let shared_obj: Rc<RefCell<Box<dyn MyTrait>>> = Rc::new(RefCell::new(Box::new(Mobj {
        str: String::from("Object 1"),
    })));

    let second_ref = Rc::clone(&shared_obj);

    second_ref.borrow_mut().do_something();

    *shared_obj.borrow_mut() = Box::new(Mobj {
        str: String::from("Object 2"),
    });

    second_ref.borrow_mut().do_something();
}
Mobj: "Object 1"
Mobj: "Object 2"

Of course you don't necessarily need the Rc, you could also do this:

fn main() {
    let shared_obj: RefCell<Box<dyn MyTrait>> = RefCell::new(Box::new(Mobj {
        str: String::from("Object 1"),
    }));

    let second_ref = &shared_obj;

    second_ref.borrow_mut().do_something();

    *shared_obj.borrow_mut() = Box::new(Mobj {
        str: String::from("Object 2"),
    });

    second_ref.borrow_mut().do_something();
}

But your usecase seems complex enough that it might be useful. But decide by yourself ;)