1

I'm working on an API written in rust, the API connects to a MySql database and does some basic read/writes to it.To make it as simple as possible, this is approximately what the two relevant rust functions do

#[no_mangle]
pub unsafe extern "C" fn Init()
{
    const DB_CREDENTIALS: DatabaseCredentials<'static> = DatabaseCredentials {
        username: "root",
        password: "password",
        host: "localhost",
        port: "3306"
    };
    let rt  = Runtime::new().unwrap();
    let handle = rt.handle();
    let dbms = handle.block_on(async  {
        return DBMS::connect_with_credentials(DB_CREDENTIALS).await.unwrap();
    });
    handle.block_on(async {
        return dbms.ensure_database("test_db").await.unwrap();
    });
    let db = handle.block_on(async {
        return dbms.connect_database("test_db").await.unwrap();
    });
}

and

#[no_mangle]
pub  unsafe extern "C" fn SaveValues(container_struct: &container)
{
    db.insert_table(container); //NEED db from the Init function
}

Both of these functions are exported to a DLL file and called from typescript with node-ffi like this

var ffi = require('ffi-napi');
    const quancy = ffi.Library('D:/Program files/node/sdk/src/framework.dll', {
      "Init":["void",[]],
      "SaveValues":["int", [container_pointer/*Don't worry about what this pointer is exactly stack overflow */]]
    });

The problem is that I need the db variable from the init function in the SaveValues function. I can't return it as a reference because it isn't FFI safe and I can't create an identical struct in typescript. There aren't really any global variables in rust either, so how can I do this?

I tried using lazy_static in rust to make the db variable global. That doesn't seem to work because there isn't a default constructor for the database.

lazy_static!{ static ref db : DBConn<MySql> = None; //error }

Here's the DBConn struct:

pub struct DBConn<DB: Database> {
    pub connection: Pool<DB>,
    pub credentials: DatabaseCredentials<'static>,
    pub current_database: &'static str,
}

I know that assigning it to None isn't supposed to work, but I don't know what to assign it to which would work. It seems to get compilable values to Pool you need to call connect_database or something like that, which I don't want to do before Init function.

  • What makes you think there aren't global variables in Rust? Globals are hard to get right though and Rust makes it obvious. Anyways you should read the error message you're getting for your example, `DBConn` doesn't have type `Option` so you can't assign `None` a value of the `Option` enum. – cafce25 Feb 25 '23 at 23:55
  • @cafce25 what should I assign it then? Nothing seems to work. Not even sure I want to use lazy_static, stack overflow just made me write something that I've tried. – Håkan Troberg Feb 26 '23 at 00:02
  • Either create a `DBConn` after all that's what `lazy_static!` is for, execute something on first access or change the type to `Option>` or sth. – cafce25 Feb 26 '23 at 00:05
  • @cafce25 Option> doesn't work because DBConn has no clone function. DBConn has some variables like pub connection: Pool which I don't know how to assign without calling connect_database() or something like that, so I don't know what to execute on "first access" – Håkan Troberg Feb 26 '23 at 00:08
  • @cafce25 are there any reasonable alternatives to lazy_static! ? Why does such a simple thing have to cause a major headache? Is rust only for masochists? – Håkan Troberg Feb 26 '23 at 00:11
  • This might be a dupe of [How do I create a global mutable singleton?](https://stackoverflow.com/questions/27791532/how-do-i-create-a-global-mutable-singleton) – cafce25 Feb 26 '23 at 00:22
  • @cafce25 I don't think it is. All of the solutions mentioned require the global variable to be initialized in a one liner. I don't see how I can do that in my case. – Håkan Troberg Feb 26 '23 at 00:45
  • The examples use a oneliner, doesn't mean you can't use a block: `let a = {first_line; second_line; third_line; result};` – cafce25 Feb 26 '23 at 00:49
  • How do I get a compilable version that works for the Pool variable? I edited my question so you can see that DBConn struct. What do you want me to write exactly? I can't get a compilable value for Pool before calling some other Init functions first – Håkan Troberg Feb 26 '23 at 00:51
  • I don't understand why you think it's not a duplicate of [How do I create a global, mutable singleton?](https://stackoverflow.com/questions/27791532/how-do-i-create-a-global-mutable-singleton). – Chayim Friedman Feb 26 '23 at 11:27
  • @Chayim Friedman I'm (obviously) a beginner with rust but my main problem with all the answers on that question is that they require some way of initializing the global variables in compile time. Example: static mut SINGLETON: MaybeUninit = MaybeUninit::uninit(); I can't seem to be able to set the variable of type Pool to anything which will compile without calling some init functions first, so I can't do the equivalent of those answers to my problem. – Håkan Troberg Feb 26 '23 at 11:45
  • You need to initialize it just like you're doing currently, with `connect_with_credentials()` etc.. – Chayim Friedman Feb 26 '23 at 11:46
  • @ChayimFriedman I can't really do that. The credentials aren't known to the rust program at compile time, they will be sent as arguments from typescript. Why is that even necessary? Why can't I leave a struct variable uninitialized? I have really had a terrible experience with rust so far. Sorry for ranting, do you know of any alternatives to using global variables with lazy static? – Håkan Troberg Feb 26 '23 at 12:03
  • If that is the case, then instead of [`Lazy`](https://docs.rs/once_cell/latest/once_cell/sync/struct.Lazy.html) you should use [`OnceCell`](https://docs.rs/once_cell/latest/once_cell/sync/struct.OnceCell.html) and [`set()`](https://docs.rs/once_cell/latest/once_cell/sync/struct.OnceCell.html#method.set) it in `Init()`. – Chayim Friedman Feb 26 '23 at 12:06
  • But if you're using async, use [tokio's `OnceCell`](https://docs.rs/tokio/latest/tokio/sync/struct.OnceCell.html). – Chayim Friedman Feb 26 '23 at 12:08
  • @ChayimFriedman Thanks! This definetly seems to be on the right track. Do you know need if I need any special features in cargo.toml to use tokio's oncecell? When I copy the example static ONCE: OnceCell = OnceCell::const_new(); the compiler tells me that const_new isn't found in the scope for tokio::sync::OnceCell – Håkan Troberg Feb 26 '23 at 12:15
  • You need to enable the `parking_lot` feature. – Chayim Friedman Feb 26 '23 at 12:26

1 Answers1

0

Use tokio's OnceCell:

static DB: tokio::sync::OnceCell<Pool<DB>> = tokio::sync::OnceCell::const_new();

#[no_mangle]
pub unsafe extern "C" fn Init() {
    const DB_CREDENTIALS: DatabaseCredentials<'static> = DatabaseCredentials {
        username: "root",
        password: "password",
        host: "localhost",
        port: "3306"
    };
    Runtime::new().unwrap().block_on(async {
        DB.get_or_init(|| async {
            let dbms = DBMS::connect_with_credentials(DB_CREDENTIALS).await.unwrap();
            dbms.ensure_database("test_db").await.unwrap();
            dbms.connect_database("test_db").await.unwrap();
        }).await;
    });
}

#[no_mangle]
pub unsafe extern "C" fn SaveValues(container_struct: &container) {
    DB.get().expect("Init() was not called").insert_table(container);
}

You also need to enable the parking_lot feature of tokio.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77