DEV Community

Naufal Rabbani
Naufal Rabbani

Posted on

I just Starting to learn Rust

I just want to paste all the notes or snippet here.

no explanation at all, just put many references and how I implement it.

use axum::{
    Json, Router,
    extract::State,
    http::StatusCode,
    routing::{get, post},
};
use rusqlite::Connection;
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
// use std::thread;
use tokio::task;

// Ref: how to use app state: https://users.rust-lang.org/t/how-to-pass-app-state-in-axum/104458/3

// define local module. the file at app_infra/mod.rs
mod app_infra;

#[derive(Debug, Clone)]
struct AppState {
    // Ref: how to make un-clonable object in external mod implementing Clone traits
    // here the link: https://itsallaboutthebit.com/arc-mutex/
    // Arc: will let the data / object shared between threads
    // Mutex: ensure only 1 thread at a time can access the data / object
    db: Arc<Mutex<Connection>>,
}

#[tokio::main]
async fn main() {
    // initialize tracing
    tracing_subscriber::fmt::init();

    let db = match app_infra::create_db_connection() {
        Ok(conn) => conn,
        Err(err) => panic!("error to connect to db {}", err.to_string()),
    };
    let cloneable_db = Arc::new(Mutex::new(db));

    let app_state = AppState { db: cloneable_db };

    // build our application with a route
    let app = Router::new()
        // `GET /` goes to `root`
        .route("/", get(root))
        // `POST /users` goes to `create_user`
        .route("/users", post(create_user))
        .with_state(app_state);

    // run our app with hyper, listening globally on port 3000
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

// basic handler that responds with a static string
async fn root(State(AppState { db }): State<AppState>) -> &'static str {
    let outside_int = 213123i32;

    // let mut all_item: Vec<String> = Vec::new();
    // ref: https://itsallaboutthebit.com/arc-mutex/
    // this is how to lock our mutex object
    // ref: use tokio:task instead: https://stackoverflow.com/a/75840352
    // let t1 = thread::spawn(move || -> Result<(), rusqlite::Error> {
    //     let locked_db = db.lock().unwrap();
    //     let mut stmt = locked_db.prepare("select * from memo limit 1")?;
    //     let mut rows = stmt.query([])?;
    //     let _row = match rows.next() {
    //         Ok(it) => {
    //             println!("{:?}", it);
    //         }
    //         Err(_) => (),
    //     };

    //     println!("testing outsider variable {:?}", all_item);
    //     println!("testing outsider variable {:?}", outside_int);
    //     all_item.push("Mantab".to_string());

    //     //  let mut names = Vec::new();
    //     //  let Some(row) = rows.next()

    //     // ref: https://itsallaboutthebit.com/arc-mutex/
    //     // after locked_user goes out of scope, mutex will be unlocked again,
    //     // but you can also explicitly unlock it with:
    //     // drop(locked_user);
    //     Ok(())
    // });
    // let _ = t1.join().unwrap();
    // println!("testing modify variable {:?}", all_item); // Error!

    // ref: spawn vs spawn_blocking https://stackoverflow.com/a/74547875
    // we use spawn_blocking here because it related to IO files like sqlite db.
    let res = task::spawn_blocking(move || -> Result<Vec<String>, rusqlite::Error> {
        let mut all_item: Vec<String> = Vec::new();
        let locked_db = db.lock().unwrap();
        let mut stmt = locked_db.prepare("select * from memo limit 1")?;
        let mut rows = stmt.query([])?;
        let _row = match rows.next() {
            Ok(it) => {
                println!("{:?}", it);
            }
            Err(_) => (),
        };

        println!("testing outsider variable {:?}", outside_int);
        all_item.push("Mantab".to_string());

        //  let mut names = Vec::new();
        //  let Some(row) = rows.next()

        // after locked_user goes out of scope, mutex will be unlocked again,
        // but you can also explicitly unlock it with:
        // drop(locked_user);
        Ok(all_item)
    })
    .await
    .unwrap();

    let _ = match res {
        Ok(it) => {
            println!("testing modify variable {:?}", it);
            it;
        }
        Err(_) => {
            let empty_vec: Vec<String> = Vec::new();
            empty_vec;
        }
    };

    "Hello, World!"
}

async fn create_user(
    // this argument tells axum to parse the request body
    // as JSON into a `CreateUser` type
    Json(payload): Json<CreateUser>,
) -> (StatusCode, Json<User>) {
    // insert your application logic here
    let user = User {
        id: 1337,
        username: payload.username,
    };

    // this will be converted into a JSON response
    // with a status code of `201 Created`
    (StatusCode::CREATED, Json(user))
}

// the input to our `create_user` handler
#[derive(Deserialize)]
struct CreateUser {
    username: String,
}

// the output to our `create_user` handler
#[derive(Serialize)]
struct User {
    id: u64,
    username: String,
}

Enter fullscreen mode Exit fullscreen mode

Maybe someone can just suggest me, or correct me.

Iteration 2

This is next iteration.

the sqlite db is come from use memos db. one of the best open source note app

use axum::{
    Json, Router,
    extract::State,
    http::StatusCode,
    routing::{get, post},
};
use rusqlite::Connection;
use serde::{Deserialize, Serialize};
use std::{
    sync::{Arc, Mutex},
    vec,
};
// use std::thread;
use tokio::task;

// Ref: how to use app state: https://users.rust-lang.org/t/how-to-pass-app-state-in-axum/104458/3

// define local module. the file at app_infra/mod.rs
mod app_infra;

#[derive(Debug, Clone)]
struct AppState {
    // Ref: how to make un-clonable object in external mod implementing Clone traits
    // here the link: https://itsallaboutthebit.com/arc-mutex/
    // Arc: will let the data / object shared between threads
    // Mutex: ensure only 1 thread at a time can access the data / object
    db: Arc<Mutex<Connection>>,
}

#[tokio::main]
async fn main() {
    // initialize tracing
    tracing_subscriber::fmt::init();

    let db = match app_infra::create_db_connection() {
        Ok(conn) => conn,
        Err(err) => panic!("error to connect to db {}", err.to_string()),
    };
    let cloneable_db = Arc::new(Mutex::new(db));

    let app_state = AppState { db: cloneable_db };

    // build our application with a route
    let app = Router::new()
        // `GET /` goes to `root`
        .route("/", get(root))
        .route("/memos", get(get_memo_list))
        .route("/basic-json", get(get_with_response_json))
        // `POST /users` goes to `create_user`
        .route("/users", post(create_user))
        .with_state(app_state);

    // run our app with hyper, listening globally on port 3000
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

// basic handler that responds with a static string
async fn root(State(AppState { db }): State<AppState>) -> &'static str {
    let outside_int = 213123i32;

    // let mut all_item: Vec<String> = Vec::new();
    // ref: use tokio:task instead: https://stackoverflow.com/a/75840352
    // ref: spawn vs spawn_blocking https://stackoverflow.com/a/74547875
    // we use spawn_blocking here because it related to IO files like sqlite db.
    let res = task::spawn_blocking(move || -> Result<Vec<String>, rusqlite::Error> {
        let mut all_item: Vec<String> = Vec::new();
        // ref: https://itsallaboutthebit.com/arc-mutex/
        // this is how to lock our mutex object
        let locked_db = db.lock().unwrap();
        let mut stmt = locked_db.prepare("select * from memo limit 1")?;
        let mut rows = stmt.query([])?;
        let _row = match rows.next() {
            Ok(it) => {
                println!("{:?}", it);
            }
            Err(_) => (),
        };

        println!("testing outsider variable {:?}", outside_int);
        all_item.push("Mantab".to_string());
        // after locked_user goes out of scope, mutex will be unlocked again,
        // but you can also explicitly unlock it with:
        // drop(locked_user);
        Ok(all_item)
    })
    .await
    .unwrap();

    let _ = match res {
        Ok(it) => {
            println!("testing modify variable {:?}", it);
            drop(it);
        }
        Err(_) => {
            let empty_vec: Vec<String> = Vec::new();
            drop(empty_vec);
        }
    };

    "Hello, World!"
}

async fn get_with_response_json() -> (StatusCode, Json<Vec<Memo>>) {
    let mut memo_list: Vec<Memo> = Vec::new();
    let mut tag_list: Vec<String> = Vec::new();
    tag_list.push("tag".to_string());

    let memo = Memo {
        id: 1,
        created_ts: 1,
        updated_ts: 1,
        content: "content".to_string(),
        payload: "".to_string(),
    };

    memo_list.push(memo);
    (StatusCode::OK, Json(memo_list))
}

async fn get_memo_list(State(AppState { db }): State<AppState>) -> (StatusCode, Json<Vec<Memo>>) {
    // let mut all_item: Vec<String> = Vec::new();
    // ref: use tokio:task instead: https://stackoverflow.com/a/75840352
    // ref: spawn vs spawn_blocking https://stackoverflow.com/a/74547875
    // we use spawn_blocking here because it related to IO files like sqlite db.
    let res = task::spawn_blocking(move || -> Result<Vec<Memo>, rusqlite::Error> {
        let mut all_item: Vec<Memo> = Vec::new();
        // ref: https://itsallaboutthebit.com/arc-mutex/
        // this is how to lock our mutex object
        let locked_db = db.lock().unwrap();
        let mut stmt = locked_db.prepare("select * from memo limit 1")?;
        let mut rows = stmt.query([])?;

        // copied form smt.query docs. just click it.
        while let Some(row) = rows.next()? {
            println!("{:?}", row);
            let memo_item = Memo {
                id: match row.get::<&str, i64>("id") {
                    Ok(it) => it,
                    Err(_) => 0i64,
                },
                created_ts: match row.get::<&str, i64>("created_ts") {
                    Ok(it) => it,
                    Err(_) => 0i64,
                },
                updated_ts: match row.get::<&str, i64>("updated_ts") {
                    Ok(it) => it,
                    Err(_) => 0i64,
                },
                content: match row.get::<&str, String>("content") {
                    Ok(it) => it,
                    Err(_) => "".to_string(),
                },
                payload: match row.get::<&str, String>("payload") {
                    Ok(it) => it,
                    Err(_) => "".to_string(),
                },
            };
            all_item.push(memo_item);
        }
        // after locked_user goes out of scope, mutex will be unlocked again,
        // but you can also explicitly unlock it with:
        // drop(locked_user);
        Ok(all_item)
    })
    .await
    .unwrap();

    match res {
        Ok(it) => {
            return (StatusCode::OK, Json(it));
        }
        Err(_) => {
            let memo_list: Vec<Memo> = Vec::new();
            return (StatusCode::INTERNAL_SERVER_ERROR, Json(memo_list));
        }
    };
}

async fn create_user(
    // this argument tells axum to parse the request body
    // as JSON into a `CreateUser` type
    Json(payload): Json<CreateUser>,
) -> (StatusCode, Json<User>) {
    // insert your application logic here
    let user = User {
        id: 1337,
        username: payload.username,
    };

    // this will be converted into a JSON response
    // with a status code of `201 Created`
    (StatusCode::CREATED, Json(user))
}

// the output to our `memo`
#[derive(Debug, Serialize)]
struct Memo {
    id: i64,
    created_ts: i64,
    updated_ts: i64,
    content: String,
    payload: String,
}

// the input to our `create_user` handler
#[derive(Deserialize)]
struct CreateUser {
    username: String,
}

// the output to our `create_user` handler
#[derive(Serialize)]
struct User {
    id: u64,
    username: String,
}
Enter fullscreen mode Exit fullscreen mode

I forget to put Cargo.toml. here the Cargo.toml

[package]
name = "bosnotes"
version = "0.1.0"
edition = "2024"

[dependencies]
time = "0.3.44"
# `bundled` causes us to automatically compile and link in an up to date
# version of SQLite for you. This avoids many common build issues, and
# avoids depending on the version of SQLite on the users system (or your
# system), which may be old or missing. It's the right choice for most
# programs that control their own SQLite databases.
#
# That said, it's not ideal for all scenarios and in particular, generic
# libraries built around `rusqlite` should probably not enable it, which
# is why it is not a default feature -- it could become hard to disable.
rusqlite = { version = "0.38.0", features = ["bundled"] }
axum = { version = "0.8.8" }
# ref: https://stackoverflow.com/a/79605363
tokio = { version = "1.49.0", features = ["full"] }
# ref: https://users.rust-lang.org/t/serde-cannot-find-derive-macro-deserialize-serialize-in-this-scope/79402
serde = { version = "1.0.228", features = ["std", "derive", "serde_derive"] }
tracing-subscriber = "0.3.22"
tower = "0.5.2"


[dependencies.uuid]
version = "1.19.0"
# Lets you generate random UUIDs
features = [
    "v4",
]
Enter fullscreen mode Exit fullscreen mode

and when I try to find how much the resource usages, here the result on idle state:

Top comments (0)