One global variable for MySQL connection

I have a struct MySQLData holding a field conn with type mysql::Pool and some functions to initialize a connection, add a user to the database,...

Now I want to have some kind of 'global/static' variable in my code ONE TIME. So I can always use the same variable to access all the functions in MySQLData.

Simplified version of code:


pub mod mysqldata {


    pub struct MySQLData {
        pub conn: u8
    }



    impl MySQLData {
        pub fn init_connection() -> MySQLData {
            MySQLData {
                conn: 1
            }
        }

        pub fn init_tables(&mut self) {
            println!("Tables initialized...");
        }

        pub fn add_user(&mut self, data: String) {
            self.conn = self.conn +1;
        }

        pub fn read_user(&mut self, id : u8) -> String {
            unimplemented!()
        }
    }


}

fn register() {
    unsafe {
        //HERE I JUST WANT TO USE THE STATIC VARIABLE
        db.unwrap().add_user("test".to_string());
    }
}

//STATIC VARIABLE I WANT
static mut db : Option<mysqldata::MySQLData> = None;


fn main() {
    unsafe {
        //HERE I WANT TO DEFINE THE VARIABLE ONCE
        db = Some(mysqldata::MySQLData::init_connection())
    }

    register();
}

This generates the following error:

  db.unwrap().add_user("test".to_string());
   |         ^^ move occurs because `db` has type `std::option::Option<mysqldata::MySQLData>`, which does not implement the `Copy` trait

How would I achieve this? In this project I achieved this by defining the variable db in my main and passing it as a parameter all functions. But I guess there should be a more elegant solution for this.

Full code:

use actix_cors::Cors;
use actix_web::{web, get, middleware, Result, Responder, HttpResponse, HttpServer, App};
use actix_web::http::StatusCode;
use serde::Deserialize;
use mysql::prelude::Queryable;


// Module for MySQL
pub mod mysqldata {
    use mysql::prelude::Queryable;
    use crate::RegistrationForm;


    pub struct MySQLData {
        pub conn: mysql::Pool
    }



    impl MySQLData {
        //Return MySQLData object with conn field
        pub fn init_connection(database_name : &String, database_user : &String, database_pass : &String) -> MySQLData {
            let conn_str : String = format!("mysql://{user}:{pass}@localhost/{name}", user=database_user, pass=database_pass, name=database_name);
            let conn = mysql::Pool::new(conn_str);

            match conn {
                Ok(_) => {

                    println!("Connection to {} successful!", database_name);

                    MySQLData {
                        conn: conn.unwrap()
                    }
                },
                Err(e) => {
                    eprintln!("Connection to {} failed: {}", database_name, e.to_string());
                    std::process::exit(-1);
                }
            }
        }

        // Initialize all needed tables
        pub fn init_tables(&mut self) {
            let mut conn = self.conn.get_conn().unwrap();
            conn.query_drop(
                "CREATE TABLE IF NOT EXISTS users (
                    ID BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
                    username VARCHAR(255) NOT NULL,
                    email VARCHAR(256) NOT NULL,
                    birthdate DATETIME,
                    password VARCHAR(255) NOT NULL
                )"
            );

            println!("Tables initialized...");
        }

        pub fn add_user(&mut self, data: RegistrationForm) {

        }

        pub fn read_user(&mut self, id: u8) -> RegistrationForm {
            unimplemented!()
        }
    }


}

#[derive(Deserialize)]
struct RegistrationForm {
    username: String,
    email: String,
    birthdate: String,
    password: String,
    rp_password: String
}

//Function which gets executed with correct route
async fn register(form: web::Form<RegistrationForm>) {
    unsafe {
        //HERE I JUST WANT TO USE THE STATIC VARIABLE
        db.unwrap().add_user(form.into_inner());
    }
}

//STATIC VARIABLE I WANT
static mut db : Option<mysqldata::MySQLData> = None;

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    unsafe {
        //HERE I WANT TO DEFINE THE VARIABLE ONCE
        db = Some(mysqldata::MySQLData::init_connection(&String::from("rustsite"), &String::from("root"), &String::from("toor")))
    }

    HttpServer::new(|| App::new()
        .wrap(
            Cors::new()
            .allowed_origin("http://rustsite.local")
            .finish()
        )
        .service(
            web::resource("/register").route(web::post().to(register))
        )
    )
    .bind("127.0.0.1:8088")?
    .run()
    .await
}

Solution:

lazy_static! {
    static ref db : mysqldata::MySQLData = mysqldata::MySQLData::init_connection(&String::from("rustsite"), &String::from("root"), &String::from("toor"));
}

Is it correct that your primary need is actually building a Singleton, and the MySQL is juat an aux detail? If so,


might be useful.

2 Likes

Believe me it's 2020 and you should not write any static mut by hand. Nowadays its only valid use case is to machine-translate existing C code, and you should fix it after translate. If you need some lazy initialization, use lazy_static. If you need to mutate global variable, use Mutex.

Unlike many other languages, in Rust multithreading is not a pain but a default choice. Safe Rust is a data race free language so you can't observe any of them even by mistake, unless you've write some unsafe {} block yourself. But it doesn't means the language inserts some magic instructions to bypass the data race, it's the compiler who reject the code if it's possible to trigger data race at runtime. Mutating global variable without proper synchronization is definitely one of them.

But it's allowed with some unsafe {} blocks, right? Because the unsafe {} block means, you know better than the compiler about the deep internals of the language semantics, knowing every invariants the language requires, and you managed all of them so the compiler should stop bothering you. Safe Rust gives you tons of restriction to provide tons of safety guarantees. In the unsafe {} block, not much restrictions become silent, but all those safety guarantees now become your duty to be satisfied and checked by your hand. If you mistakenly violate one of them, good luck, it's UB(Undefined Behavior).

Conclusion, do not use static mut. Do never use unsafe {} while you're in learning period. Do your absolute best to avoid unsafe {} even after learning period.

4 Likes

Great advice! Many newcomers to Rust fail to understand that unsafe removes some of the rustc front-end's error checks but does nothing to make the LLVM backend generate usable code. So when the newbie lies to the compiler that something is safe, by improperly using unsafe, they should have no expectation that the compiled program does what they want or expect.

By lying to the compiler they've given the hyper-optimizing LLVM backend complete freedom to "optimize" their untruthful code into erratic behavior, now or whenever any recompilation occurs, and they have no basis for complaining that Rust did not do what they wanted it to do. This is simply a case of GIGO!

You definitely shouldn't be using an unsafe with static mut for this

you can do it by Lazy initializing a Mutex

the lazy initialization can be done using lazy_static! as mentioned before, or my preference OnceCell::Lazy. It'll take care of lazy initializing something even with a non static constructor, and the Mutex will take care of interior mutability to let you mutate it.

A snippet from something I once did

use async_std::sync::Mutex;
use once_cell::sync::Lazy;

static MONGODB: Lazy<Mutex<Option<Database>>> = Lazy::new(|| Mutex::new(None));

pub async fn initialize() {
    if let Ok(connection_string) = env::var("CONNECTION") {
        if let Ok(client_options) = ClientOptions::parse(connection_string.as_str()).await {
            if let Ok(client) = Client::with_options(client_options) {
                *MONGODB.lock().await = Some(client.database("my_db"));
            }
        } 
    } 
}

Edit: Added Non lazy version which doesn't require Mutex and Option

Like @Hyeonu suggested you can do this, the uninitialized state can represent a failed connection, and since we aren't swapping Some and None we don't need a Mutex either (If for some reason the db connection needs to be mutable then you'll still need a mutex for that)

use once_cell::sync::OnceCell;

static MONGODB: OnceCell<Database> = OnceCell::new();

pub async fn initialize() {
    if MONGODB.get().is_some() {
        return;
    }

    if let Ok(token) = env::var("CONNECTION") {
        if let Ok(client_options) = ClientOptions::parse(token.as_str()).await {
            if let Ok(client) = Client::with_options(client_options) {
                let _ = MONGODB.set(client.database("my_db"));
            }
        }
    }
}
2 Likes

once_cell::sync::OnceCell<T> would be better than the Lazy<Mutex<Option<T>>>.

Edit: fix async_std into once_cell

Does async_std re-export OnceCell::Lazy? and without the Mutex<Option<T>> how would I represent a failed initialization?

OnceCell has 2 state - uninitialized and initialized. You can use uninitialized state as a signal of failed initialization like you did with the mutexed option.

2 Likes

Thanks, that's a good tip. I'll do it that way.

Thanks for the answers all! I decided to accept the one guiding me to lazy_static. This is very easy to understand. The oncecell and mutex things are still a vague concept to grasp.

Well OnceCell::Lazy is more or less the same as lazy_static!, most people recommend OnceCell since it's not a macro. and the Mutex stuff is for mutability purposes, lazy_static! won't provide you that.