How to get a variable out of main without passing on to use Actix with rusqlite

Hello, I am making a simple project with Actix and rusqlite, Actix splits itself into multiple functions like, the
page /message, will have it's own function in which it is processed. rusqlite requires a initialization like this taken from the front page of the rusqlite docs:

fn main() -> Result<()> {
    let conn = Connection::open_in_memory()?;

    conn.execute(
        "CREATE TABLE person (
            id   INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            data BLOB
        )",
        (), // empty list of parameters.
    )?;
    let me = Person {
        id: 0,
        name: "Steven".to_string(),
        data: None,
    };
    conn.execute(
        "INSERT INTO person (name, data) VALUES (?1, ?2)",
        (&me.name, &me.data),
    )?;

    let mut stmt = conn.prepare("SELECT id, name, data FROM person")?;
    let person_iter = stmt.query_map([], |row| {
        Ok(Person {
            id: row.get(0)?,
            name: row.get(1)?,
            data: row.get(2)?,
        })
    })?;

    for person in person_iter {
        println!("Found person {:?}", person.unwrap());
    }
    Ok(())
}

But Actix requires, something like this:

use actix_web::{get, web, App, HttpServer, Responder};

#[get("/")]
async fn index() -> impl Responder {
    "Hello, World!"
}

#[get("/{name}")]
async fn hello(name: web::Path<String>) -> impl Responder {
    format!("Hello {}!", &name)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(index).service(hello))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

It's multifunctional, How do I get the conn var out of main without passing it on through params, which can't be done here?

You can do so with the application state extractor web::Data:

actix-web has an example on integrating it with SQLite https://github.com/actix/examples/tree/master/databases/sqlite. Note though that it uses rusqlite together with r2d2 and r2d2_sqlite to manage a connection pool. If you want to avoid them you'll probably have to wrap Connection in a Arc<Mutex<Connection>>. This is required because actix-web spawns multiple workers which may all execute concurrently.

In any case on the actix side you'll have to use the previously mentioned web::Data to get access to the pool/connection in the single handler functions.

Person has a different type outside of the main function as vs code says, which makes person.unwrap() not work:



Is the conn.clone thing I did doing something? Sorry for the bad var names.
Here is the whole code as text:

use std::sync::{Mutex, Arc};

use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use rusqlite::{params, Connection, Result};

#[derive(Debug)]
struct Person {
    id: i32,
    name: String,
    data: Option<Vec<u8>>,
}

impl ToString for Person {
    fn to_string(&self) -> String {
        self.name.to_owned()
    }
}

#[get("/")]
async fn hello(conn: web::Data<Connection>) -> impl Responder {
    let conn_clone = conn.clone();
    let mut stmt = conn_clone.prepare("SELECT id, name, data FROM person").expect("REASON");
    let person_iter = &stmt.query_map([], |row| {
        Ok(Person {
            id: row.get(0)?,
            name: row.get(1)?,
            data: row.get(2)?,
        })
    });
    
    let people: Vec<Person> = Vec::new();
    for person in person_iter {
        // println!("Found person {:?}", person.unwrap());
    }

    HttpResponse::Ok().body("Hello world 2!")
}

#[post("/echo")]
async fn echo(req_body: String) -> impl Responder {
    HttpResponse::Ok().body(req_body)
}

// fn init_db() -> Result<(), rusqlite::Error> {

//     Ok(())
// }

// //http://127.0.0.1:8080/
#[actix_web::main]
async fn main() -> Result<(), Box<dyn std::error::Error>>{
    let conn = Connection::open_in_memory()?;

    conn.execute(
        "CREATE TABLE person (
            id   INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            data BLOB
        )",
        (), // empty list of parameters.
    )?;
    let me = Person {
        id: 0,
        name: "Steven".to_string(),
        data: None,
    };
    conn.execute(
        "INSERT INTO person (name, data) VALUES (?1, ?2)",
        (&me.name, &me.data),
    )?;

    {
        let mut stmt = conn.prepare("SELECT id, name, data FROM person")?;
        let person_iter = stmt.query_map([], |row| {
            Ok(Person {
                id: row.get(0)?,
                name: row.get(1)?,
                data: row.get(2)?,
            })
        })?;
        

        for person in person_iter {
            println!("Found person {:?}", person.unwrap());
        };
    }

    let newconn: Arc<Mutex<Connection>> = Arc::new(Mutex::new(conn));
    
    HttpServer::new(move || {
        App::new()
            .service(hello)
            .app_data(newconn.clone())
            .service(echo)
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await;
    Ok(())
}

It is much easier for us to debug your code if you'd copy the error messages you get when you run cargo check in your console, rather than providing screenshots from your editor.

As to your error, you need to unwrap person_iter (you use the error propagator ? in your main, which is the difference):

    let person_iter = &stmt.query_map([], |row| {
        Ok(Person {
            id: row.get(0)?,
            name: row.get(1)?,
            data: row.get(2)?,
        })
-    });
+   })
+   .unwrap();

Also you don't need to clone conn, just use it directly instead of conn_clone.

Beyond the compile time error, I think the signature of hello is slightly off (the types must exactly match, you can't omit Arc<Mutex<...>>) and that you don't provide newconn wrapped in a web::Data. This will lead to a runtime error, I think. You can fix this with:

- async fn hello(conn: web::Data<Connection>) -> impl Responder {
+ async fn hello(conn: web::Data<Arc<Mutex<Connection>>>) -> impl Responder {

and

- let newconn: Arc<Mutex<Connection>> = Arc::new(Mutex::new(conn));
+ let newconn: web::Data<Arc<Mutex<Connection>>> = web::Data::new(Arc::new(Mutex::new(conn)));

Since conn is now wrapped in a Mutex, you need to lock it in order to use the connection contained within:

- async fn hello(conn: web::Data<Connection>) -> impl Responder {
-    let conn_clone = conn.clone();
+ async fn hello(conn: web::Data<Arc<Mutex<Connection>>>) -> impl Responder {
+    let conn_lock = conn.lock().unwrap();

Now you can use conn_lock throughout your function to query your database (instead of conn_clone).

1 Like

Thanks it works now, Can you suggest better names for the vars newconn, and conn_lock. I don't want to go back to this project after a few months and not understand anything?

I personally like to shadow my variable names. So I'd name everything related to your connection (newconn and conn_lock) conn. If you don't need to access the shadowed conn beyond creating newconn and conn_lock, that is.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.