Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to store a reference without having to deal with lifetimes?

As suggested by the dynamic_reload crate's example, I collected Symbols instead of extracting them every time, but Symbol requires a lifetime. Using a lifetime changes method signatures and breaks the compatibility with method DynamicReload::update.

Is it a valid workaround to use std::mem::transmute to change Symbol's lifetime to 'static?

extern crate dynamic_reload;

use dynamic_reload::{DynamicReload, Lib, Symbol, Search, PlatformName, UpdateState};
use std::sync::Arc;
use std::time::Duration;
use std::thread;
use std::mem::transmute;

struct Plugins {
    plugins: Vec<(Arc<Lib>, Arc<Symbol<'static, extern "C" fn() -> i32>>)>,
}

impl Plugins {
    fn add_plugin(&mut self, plugin: &Arc<Lib>) {
        match unsafe { plugin.lib.get(b"shared_fun\0") } {
            Ok(temp) => {
                let f: Symbol<extern "C" fn() -> i32> = temp;
                self.plugins.push((plugin.clone(), Arc::new(unsafe { transmute(f) })));
            },
            Err(e) => println!("Failed to load symbol: {:?}", e),
        }
    }

    fn unload_plugins(&mut self, lib: &Arc<Lib>) {
        for i in (0..self.plugins.len()).rev() {
            if &self.plugins[i].0 == lib {
                self.plugins.swap_remove(i);
            }
        }
    }

    fn reload_plugin(&mut self, lib: &Arc<Lib>) {
        Self::add_plugin(self, lib);
    }

    // called when a lib needs to be reloaded.
    fn reload_callback(&mut self, state: UpdateState, lib: Option<&Arc<Lib>>) {
        match state {
            UpdateState::Before => Self::unload_plugins(self, lib.unwrap()),
            UpdateState::After => Self::reload_plugin(self, lib.unwrap()),
            UpdateState::ReloadFailed(_) => println!("Failed to reload"),
        }
    }
}

fn main() {
    let mut plugs = Plugins { plugins: Vec::new() };

    // Setup the reload handler. A temporary directory will be created inside the target/debug
    // where plugins will be loaded from. That is because on some OS:es loading a shared lib
    // will lock the file so we can't overwrite it so this works around that issue.
    let mut reload_handler = DynamicReload::new(Some(vec!["target/debug"]),
                                                Some("target/debug"),
                                                Search::Default);

    // test_shared is generated in build.rs
    match reload_handler.add_library("test_shared", PlatformName::Yes) {
        Ok(lib) => plugs.add_plugin(&lib),
        Err(e) => {
            println!("Unable to load dynamic lib, err {:?}", e);
            return;
        }
    }

    //
    // While this is running (printing a number) change return value in file src/test_shared.rs
    // build the project with cargo build and notice that this code will now return the new value
    //
    loop {
        reload_handler.update(Plugins::reload_callback, &mut plugs);

        if plugs.plugins.len() > 0 {
            let fun = &plugs.plugins[0].1;
            println!("Value {}", fun());
        }

        // Wait for 0.5 sec
        thread::sleep(Duration::from_millis(500));
    }
}

I still have to keep Arc<Lib> inside the vector because Symbol doesn't implement PartialEq.

like image 900
Marco Napetti Avatar asked Mar 07 '23 16:03

Marco Napetti


1 Answers

How to store a reference without having to deal with lifetimes?

The answer in 98% of the cases is: you don't. Lifetimes are one of the biggest reasons to use Rust. Lifetimes enforce, at compile time, that your references will always refer to something that is valid. If you wish to "ignore" lifetimes, then perhaps Rust may not the best language to realize a particular design. You may need to pick a different language or design.

Is it a valid workaround to use std::mem::transmute to change Symbol's lifetime to 'static?

transmute is The Big Hammer, suitable for all sorts of good and bad ideas and implementations. I would encourage never using it directly, but instead wrapping it in a layer of abstraction that somehow helps you enforce the appropriate restrictions that make that particular transmute correct.

If you choose to use transmute, you are assuming the full responsibility that the compiler previously had. It will be up to you to ensure that the reference is always valid, otherwise you are invoking undefined behavior and your program is allowed to do any number of Very Bad things.


For your specific case, you may be able to use the Rental crate to keep around "the library" and "references into the library" in a single struct that hides the lifetimes of the Symbols. In fact, Rental uses libloading as the motivating example and libloading powers dynamic_reload. See Why can't I store a value and a reference to that value in the same struct? for more details and pitfalls.

I'm not optimistic that this will work because DynamicReload::update requires a &mut self. During that method call, it could easily invalidate all of the existing references.

See also:

  • Why can't I store a value and a reference to that value in the same struct?
  • How can I avoid a ripple effect from changing a concrete struct to generic?
like image 54
Shepmaster Avatar answered Apr 07 '23 11:04

Shepmaster