Init global struct in libs?

I'm face to a design problem I have hard time to solve.

I'm writing a lib which expose FFI C functions which will be called from a C app. Roughly:

#[no_mangle]
pub extern "C"
fn PluginStartup(usefull_infos) {
    // startup code here, build all "objects" you need,
}

#[no_mangle]
pub extern "C"
fn PluginExecute(other_infos) {
    // execution code here, use the objects created before
}

#[no_mangle]
pub extern "C"
fn PluginEnd() {
    // end code here, free the objects
}

As you can see, there is no main().

As I can't have any null pointer, how am I supposed to build the stuff I need in PluginStartup(), use it in PluginExecute() and clean it in PluginEnd(). Do I really need a Option? This would mean I always have to do match bla { Some(bla), None } or unwrap() each time I use the main structures (90% of the code)? It's crazy no?

In C I would have a null pointer, do the allocation stuff in PluginStartup() and free in PluginEnd(). I know rust don't do this but from your advanced rust perspective, what would be the smartest way to handle my situation?

A big thanks in advance! :smile:

In my tiny program, doing a global look like this.
First thing you declare enums and structures:

struct Plugin {
    config: Config,
}

enum ConfigRotate {
    r0,
    r90,
    r180,
    r270,
}

struct Config {
    /// Video General
    fullscreen:          bool,
    screen_width:        u32,
    screen_height:       u32,
    vertical_sync:       bool,
    rotate:              ConfigRotate,
}

And you create a static (aka global):

static mut g_plugin: Plugin = Plugin {
    config: Config {
            fullscreen:          false,
            screen_width:        320,
            screen_height:       240,
            vertical_sync:       false,
            rotate:              ConfigRotate::r0,
    }
};

This is a tiny structure for now but it will grow a lot. Am I really requiered to feed it entirely? And what if I have dynamic arrays and other more complex types?

Is there any way to do something like this:

impl Plugin {
    fn new() -> Plugin {
        Plugin {
            config: Config::new()
        }
    }
}

impl Config {
    fn new() -> Config {
        Config {
            fullscreen:          false,
            screen_width:        320,
            screen_height:       240,
            vertical_sync:       false,
            rotate:              ConfigRotate::r0,
        }
}

static mut g_plugin: Plugin = Plugin::new();

This way I can separate component initialization (because their will be many component with a huge number of values for each of them).

All tutorials use a main() but how handle the lack of main scope (like in my library)?

A big thanks in advance! :slight_smile:

there will be const fn, which will allow you to use functions in a static initialization.

For now, you could use an Option and create a function that internally does the matching/unwrapping so you don't have your code littered with it.

the issue with your big new function can be solved by deriving Default and calling Default::default() instead. Some values that have a special value like screen_width would obviously still have to be set manually. Things like Vec or String will be filled with a reasonable default without you having to code anything manually.

What you suggest is to have something like?:

static mut l_plugin: Option<Plugin> = None;

const fn plugin() -> Plugin {
    match l_plugin {
        Some(plugin) => plugin,
        None => exit("Outch!"),
    }
}

fn PluginStartup(usefull_infos) {
    l_plugin = Some(plugin()::default())
}


fn PluginExecute() {
    plugin()::BlahBlah
}

But from a performance perspective I will always check (via Option) a pointer setted once and never modified? Does not rust provide something to avoid this?

Can I call default() (or anything) outside a function scope.

Something like this doesn't seems valid:

static mut plugin: Plugin::default(); // << How can I do this?

fn PluginStartup(usefull_infos) {
    l_plugin.bla = usefull_infos.bla
}


fn PluginExecute() {
    l_plugin::BlahBlah()
}

Rust has a lot of libs, how rust lib devs deal with such situation?

Thanks in advance! :slight_smile:

I'm not an advanced rust developer but I do have some questions about your API. Why does it have three functions and a global variable? This sounds like an inherently fragile design, and attempting to work around that fragility seems weird. What happens if someone calls PluginExecute from two different threads simultaneously? Presumably you need locking to deal with this correctly, which will dwarf the cost of checking the Option.

It seems a better approach would be to simply not use a global variable at all, and make PluginExecute be a method of an object created by PluginStartup. Then you don't need to check whether it has been started, since the compiler will do that for you. And then you also don't need to create a user-facing PluginEnd, since you can use the Drop trait to automatically call that function when the object goes out of scope.

2 Likes

As described earlier, the functions are exposed as C-friendly functions so they can be called from outside, which means that the Drop approach won't work.

It's the mupen64plus API quite old despite some modifications from the original "Zilmar spec". We talk about something very old and I thought it was a good use case to know how rust can work with C code.

I know this API is not a good design but that was not the point here (I can't control this). I try to write a mupen64plus plugin in rust and I have to match the API.

There is many old API or plugin system implying globals.

That's why, regarding this constraints, I try to figure out what the best approach would be.

If you had to write a rust code to match such API, how would you handle your global structure? :slight_smile:

Ah, I had misunderstood and thought you were binding to C functions. That
clears up my confusion.

I would make the interface functions unsafe, allocate state in PluginStartup and return raw pointer to this state, and then accept raw pointer to the state in PluginExecute and PluginEnd. That should look C-ish, but that's the point here, so you can talk to C natively.

2 Likes

Thanks a lot kstep, I'm on the good way but there is still one last problem (the biggest one actually):

You say:

But even before this how can I create a static (aka: global, outside any function) "state" pointer in rust without relying on Option?

Big thanks in advance! :blush:

Since you are using raw pointers and unsafe code, think like C: use a NULL pointer!

static mut state : *mut State = 0 as *mut State;

In the rest of the code, you can turn it in a safe Rust reference:

let s = unsafe { &*state };

And just like C, it will crash if it is executed before you have stored a valid pointer in state.

2 Likes

This made my day! Thanks a lot, I wasn't aware about the = 0 as *mut State; syntax!

I was not aware of it either, but I googled “Rust raw pointer NULL”, found null and null_mut in the documentation of std::ptr, tried it, failed because a function is not allowed in a static constant, clicked on the “[src]” button at the up-right corner of the documentation page and observed how std::ptr::null_mut is defined.

1 Like

The more I think to all of this, the more I realize this is not about raw pointer (we've maybe focused too much on it), it's about design: How am I supposed to write a rust dynamic library file that will do things but only called from three FFI C functions (PluginStartup, PluginExecute multiple times, PluginEnd`).

There shouldn't be any unsafe code here because FFI C functions are not a problem because we don't talk about arguments/raw pointer but design.

Here are the three solutions I seems to have:

  1. Declare the whole structures:
static mut g_plugin: Plugin = Plugin {
    config: Config {
        ...
    }
};

Impossible in practice because this structure will have many arguments and many references.

  1. Use Option (oli_obk): my attempt here.

  2. Use raw pointer + unsafe (Cigaes).

I have the impression I can't initialize anything without loosing it after the call but it's what any dynamic library is supposed to do no?

The Option approach looks something like this: Rust Playground . If you want to avoid unsafe code, you can wrap the whole thing in a Mutex and use https://crates.io/crates/lazy_static to initialize it.

2 Likes

I know it was quite a while since this thread started, but have you tried the Once struct?

A synchronization primitive which can be used to run a one-time global
initialization. Useful for one-time initialization for FFI or related
functionality.

1 Like

Interesting! Thanks! :smile: