Sudden requirement of lifetime


#1

First I made a project (crate) to see how to load an external library using the “libloading” crate. That worked fine like this.

let lib = Library::new("someLib.dll").unwrap();

let answer: Symbol<unsafe extern fn() -> i32> = unsafe {
    lib.get(b"answer\0").unwrap()
};

let a = unsafe { answer() };
println!("The answer is: {}", a);

Then I made a project in order to save function pointers in a HashMap witch also worked fine (thanks to some help from here). I ended up having something like this:

pub type UserFunctionCallback = fn(&[Value], Element) -> Option<Value>;

pub struct EventHandler {
    pub user_function: HashMap<String, UserFunctionCallback>,
}
...

Complete see here in the help mentioned earlier.

Now I wanted to see if I can combine the two and save a function pointer to a function loaded from an external lib. So, I had to adjust the type alias.

pub type UserFunctionCallback = Symbol<unsafe extern fn(&[Value], Element) -> Option<Value>>;

and load the lib like this:

fn main() {
    let lib = Library::new("someLib.dll").unwrap();

    let script_foo: UserFunctionCallback = unsafe {
        lib.get(b"script_foo\0").unwrap()
    };

    handler.add_user_function("FooTest".to_string(), script_foo);
}

After doing this, I get an error for my type alias:
“wrong number of lifetime parameters: expected 1, found 0”

I checked the Symbol definition.

#[derive(Clone)]
pub struct Symbol<'lib, T: 'lib> {
    inner: imp::Symbol<T>,
    pd: marker::PhantomData<&'lib T>
}

I have these two questions:

  1. How come, that I could use the “libloading” crate without having to specify a lifetime before, but now it is required?
  2. How do I add the lifetime properly to my type alias?

pub type UserFunctionCallback = Symbol<unsafe extern fn(&[Value], Element) -> Option<Value>>;

p.s. I am struggling with the rust syntax quite a lot. If you could describe the logic why and how to add the lifetime it would help me understand the rust syntax.


#2

I’m not an expert on this yet, but I’ll try:

Previously Rust didn’t track when the memory was freed. The function pointer was copied, as if it was just an integer, and it wasn’t seen as a reference to anything that could be freed.

With Symbol Rust now tracks where memory for that symbol was allocated, and wants to ensure that the memory won’t be freed while there are any Symbol references still in use.

PhantomData<&'lib T> is a sort-of a hack to say “although this not an actual pointer to T, this object indirectly depends on T being still in memory, in ways the Rust compiler can’t see”.

Try this:

pub type UserFunctionCallback<'a> = Symbol<'a, unsafe extern fn(&[Value], Element) -> Option<Value>>;

The lifetimes are a bit “infectious”, so any data type that contains Foo<'lifetime>, will have to have <'lifetime> on it too. PhantomData contains a lifetime, so Symbol must declare a lifetime, and if you put the Symbol in your own struct, your struct will need to have a lifetime too.


#3

Thanks for your help!

I followed your tips and added the lifetime as described. Due to the infectious nature of lifetimes I basically just followed the compiler complaints to find all the places that need adjustment… (My “struct EventHandler” and the implementations it has). That seams to work!

I don’t know why, but so far I can’t get used to the rust syntax :frowning: I never find the right way to declare or use things even though I read the rust book, search online and try various ways I can think of. When I ask here it seams quite simple in hindsight. It’s increasingly frustrating…


#4

Yes, it’s true. The learning curve is steep, and there’s a lot angled brackets there. If it’s any consolation, all these complicated declarations do make sense, and once you get hang of it, they’re not hard (sort of like learning to ski).