Rusty Projects — Chapter 02
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 theserialize
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
This is the result of version 0.29
This is the result before editing rusqlite
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