Rusty Projects — Chapter 02

Yen
Rustaceans
Published in
7 min readMay 11, 2024

--

Practice Rust with several projects | Creative Projects for Rust Programmers
By Carlo Milanesi.

This is the note of code examples from the book and theimplemented results. Chapter 2 is about retrieving and storing data. I skip the part of parsing XML, Postgres and Redis, but you can find the examples in their GitHub repository.

Also, there are some new crates for us to build a projects, i.e., in this YT tutorial — SQLx is my favorite PostgreSQL driver to use with Rust (by Dreams of Code) , we can see sqlx is used for postgres.

For further explanation, you can find the details in the book Creative Projects for Rust Programmers By Carlo Milanesi. I also want to explore and build projects in Rust, this book helps me to build some and consolidate my fundamentals.

GitHub

Creative Projects for Rust Programmers | Chapter 02 | https://github.com/PacktPublishing/Creative-Projects-for-Rust-Programmers

Catalogue

  • Installation of SQLite
  • Toml Dynamic
  • Toml Static
  • Json Dynamic
  • Json Static
  • SQLite

Installation of SQLite

You can find many tutorials about it on YT/Google.

In Windows 11, you just need to

  • Download the zip file from the download page of SQLite page
  • Unzip the file
  • Copy the whole unzip folder to C:/
  • Copy the path (C:/sqlite) to environment variables > system path
  • Open PowerShell, and type sqlite3
  • If it works, it looks like this:

Toml Dynamic

main.rs

fn main() {
// 1. Define the config structure.
let config_const_values = {
// 2. Get the path of the config file from the command line.
let config_path = std::env::args().nth(1).unwrap();

// 3. Load the whole file contents into a string.
let config_text = std::fs::read_to_string(&config_path).unwrap();

// 4. Load an unmutable config structure from the string.
config_text.parse::<toml::Value>().unwrap()
};

// 5. Show the whole config structure.
println!("Original: {:#?}", config_const_values);

// 6. Get and show one config value.
println!(
"[Postgresql].Database: {}",
config_const_values
.get("postgresql")
.unwrap()
.get("database")
.unwrap()
.as_str()
.unwrap()
);
}

Cargo.toml

[package]
name = "toml_dynamic"
version = "0.1.0"
authors = []
edition = "2018"

[dependencies]
toml = "0.4"

Type this in the command line:

cargo run ../data/config.toml

You’ll notice that retrieving the value of the databases item within the postgresql section demands quite a bit of code.

The retrieval process relies on the get function, which must search for a string, introducing the possibility of failure. This inherent uncertainty comes with its own cost.

Toml Static

  • In this example, we can create several structures if we are sure of the organization of the file.

This project relies on two supplementary crates:

  • serde: Facilitating fundamental serialization and deserialization operations.
    serde_derive: Empowering custom derivation, a potent feature enabling serialization and deserialization using a struct.”

main.rs

use serde_derive::Deserialize;

#[allow(unused)]
#[derive(Deserialize)]
struct Input {
xml_file: String,
json_file: String,
}
#[allow(unused)]
#[derive(Deserialize)]
struct Redis {
host: String,
}
#[allow(unused)]
#[derive(Deserialize)]
struct Sqlite {
db_file: String,
}
#[allow(unused)]
#[derive(Deserialize)]
struct Postgresql {
username: String,
password: String,
host: String,
port: String,
database: String,
}
#[allow(unused)]
#[derive(Deserialize)]
struct Config {
input: Input,
redis: Redis,
sqlite: Sqlite,
postgresql: Postgresql,
}

fn main() {
// 1. Define the config structure.
let config_const_values: Config = {
// 2. Get the path of the config file from the command line.
let config_path = std::env::args().nth(1).unwrap();

// 3. Load the whole file contents into a string.
let config_text = std::fs::read_to_string(&config_path).unwrap();

// 4. Load an unmutable statically-typed structure from the string.
toml::from_str(&config_text).unwrap()
};

// 5. Get and show one config value.
println!(
"[postgresql].database: {}",
config_const_values.postgresql.database
);
}

Cargo.toml

[package]
name = "toml_static"
version = "0.1.0"
authors = []
edition = "2018"

[dependencies]
toml = "0.4"
serde = "1.0"
serde_derive = "1.0"

Json Dynamic

main.rs

use serde_json::{Number, Value};

fn main() {
// Get the filenames from the command line.
let input_path = std::env::args().nth(1).unwrap();
let output_path = std::env::args().nth(2).unwrap();

let mut sales_and_products = {
// Load the first file into a string.
let sales_and_products_text = std::fs::read_to_string(&input_path).unwrap();

// Parse the string into a dynamically-typed JSON structure.
serde_json::from_str::<Value>(&sales_and_products_text).unwrap()
};

// Get the field of the structure
// containing the weight of the sold oranges.
if let Value::Number(n) = &sales_and_products["sales"][1]["quantity"] {
// Increment it and store it back into the structure.
sales_and_products["sales"][1]["quantity"] =
Value::Number(Number::from_f64(n.as_f64().unwrap() + 1.5).unwrap());
}

// Save the JSON structure into the other file.
std::fs::write(
output_path,
serde_json::to_string_pretty(&sales_and_products).unwrap(),
)
.unwrap();
}

Cargo.toml

[package]
name = "json_dynamic"
version = "0.1.0"
authors = []
edition = "2018"

[dependencies]
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"

Type this in the command line:

cargo run ../data/sales.json ../data/sales2.json

There won’t be any result, but it will read the first line in the file.

Json Static

main.rs

use serde_derive::{Deserialize, Serialize};

#[derive(Deserialize, Serialize, Debug)]
struct Product {
id: u32,
category: String,
name: String,
}

#[derive(Deserialize, Serialize, Debug)]
struct Sale {
id: String,
product_id: u32,
date: i64,
quantity: f64,
unit: String,
}

#[derive(Deserialize, Serialize, Debug)]
struct SalesAndProducts {
products: Vec<Product>,
sales: Vec<Sale>,
}

fn main() -> Result<(), std::io::Error> {
let input_path = std::env::args().nth(1).unwrap();
let output_path = std::env::args().nth(2).unwrap();
let mut sales_and_products = {
let sales_and_products_text = std::fs::read_to_string(&input_path)?;

// 1. Load the sale structure from the string.
serde_json::from_str::<SalesAndProducts>(&sales_and_products_text).unwrap()
};

// Increment the weight of the sold oranges.
sales_and_products.sales[1].quantity += 1.5;

std::fs::write(
output_path,
serde_json::to_string_pretty(&sales_and_products).unwrap(),
)?;

Ok(())
}

Cargo.toml

[package]
name = "json_static"
version = "0.1.0"
authors = []
edition = "2018"

[dependencies]
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"

Summary

  • The Deserialize trait is essential for parsing (reading) JSON strings into this struct, while the serialize trait is indispensable for formatting (writing)the struct into a JSON string.
  • Additionally, the Debug trait proves useful for printing this struct during debugging traces.

SQLite

main.rs

use rusqlite::{params, Connection, Result};

#[derive(Debug)]
struct SaleWithProduct {
category: String,
name: String,
quantity: f64,
unit: String,
date: i64,
}

fn create_db() -> Result<Connection> {
let database_file = "sales.db";
let conn = Connection::open(database_file)?;
let _ = conn.execute("DROP TABLE Sales", params![]);
let _ = conn.execute("DROP TABLE Products", params![]);
conn.execute(
"CREATE TABLE Products (
id INTEGER PRIMARY KEY,
category TEXT NOT NULL,
name TEXT NOT NULL UNIQUE)",
params![],
)?;
conn.execute(
"CREATE TABLE Sales (
id TEXT PRIMARY KEY,
product_id INTEGER NOT NULL REFERENCES Products,
sale_date BIGINT NOT NULL,
quantity DOUBLE PRECISION NOT NULL,
unit TEXT NOT NULL)",
params![],
)?;
Ok(conn)
}

fn populate_db(conn: &Connection) -> Result<()> {
conn.execute(
"INSERT INTO Products (
id, category, name
) VALUES ($1, $2, $3)",
params![1, "fruit", "pears"],
)?;
conn.execute(
"INSERT INTO Sales (
id, product_id, sale_date, quantity, unit
) VALUES ($1, $2, $3, $4, $5)",
params!["2020-183", 1, 1_234_567_890_i64, 7.439, "Kg",],
)?;
Ok(())
}

fn print_db(conn: &Connection) -> Result<()> {
let mut command = conn.prepare(
"SELECT p.name, s.unit, s.quantity, s.sale_date
FROM Sales s
LEFT JOIN Products p
ON p.id = s.product_id
ORDER BY s.sale_date",
)?;
for sale_with_product in command.query_map(params![], |row| {
Ok(SaleWithProduct {
category: "".to_string(),
name: row.get(0)?,
quantity: row.get(2)?,
unit: row.get(1)?,
date: row.get(3)?,
})
})? {
if let Ok(item) = sale_with_product {
println!(
"At instant {}, {} {} of {} were sold.",
item.date, item.quantity, item.unit, item.name
);
}
}
Ok(())
}

fn main() -> Result<()> {
let conn = create_db()?;
populate_db(&conn)?;
print_db(&conn)?;
Ok(())
}

Cargo.toml

  • There is one thing worth noticed is, rusqlite in [dependencies] should be written as shown in the following codes.
[package]
name = "sqlite_example"
version = "0.1.0"
authors = []
edition = "2018"

[dependencies]
rusqlite = { version = "0.23", features = ["bundled"] }

Results

This is the result of version 0.23

Result

This is the result of version 0.29

Same as V.0.23

This is the result before editing rusqlite

A really long error message

Hey Rustaceans!

Thanks for being an awesome part of the community! Before you head off, here are a few ways to stay connected and show your love:

  • Give us a clap! Your appreciation helps us keep creating valuable content.
  • Become a contributor! ✍️ We’d love to hear your voice. Learn how to write for us.
  • Stay in the loop! Subscribe to the Rust Bytes Newsletter for the latest news and insights.
  • Support our work!Buy us a coffee.
  • Connect with us: X

--

--

Yen
Rustaceans

Programming is a mindset. Cybersecurity is the process. Focus on Python & Rust currently. More about me | https://msha.ke/monles