🏠 Home
Lyokolux's blog

A first minimal working project in Rust


I want to learn Rust since months (even years). At the same time, I want to use Neovim for it. I struggle with this IDE though: something always does not work. I finally managed to get it work properly after months, even if the Rust LSP commands are not available (again). I have color highlighting and LSP autocompletions. I tocuhed the power for Rustancean.vim but it broke again. Fine for now. As a sidenote, if something works or it works better, commit it. Anyway.

I completeed the rustlings to get a grisp on the language, learn the rust book for 2 years. I watched videos on Youtube (some of Jon Gjengset, Code to the Moon, [Let’s Get Rusty], …). I finally started some Exercism exercises. It feels boring though. I have no incentive to complete them because they feel useless. I struggle more with algorithm than the language itself.

I need to create something a bit useful.

Meanwhile, I crawl the web and my RSS feeds are always full. I discovered (also) years ago the french digital open data. The french digital services publish open data regularly.

I want a small and easy project that can evolve. A small project where I can use a bit of everything with Rust: database, API, CLI and eventually more.

The french postcodes are perfect for it. Why is it useful? Because you may not want to rely on a third-party API for this. Here is the small data set: a commune (group of village or small cities) has a code, a name, a routing label (think the official post office name) and a postcode. A first small step would be to put this JSON into a SQLite database. How to read the file? Deserialize it in a rust struct? Open a sqlite database and execute queries? Use CLI arguments to apply some rules…

After irregular weeks or months, I finally made it. Here’s the current simple “script”: (updating color syntaxing is on the todo list :)

use rusqlite::{Connection, Result};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Commune {
	#[serde(rename(deserialize = "codeCommune"))]
	code: String,
	#[serde(rename(deserialize = "nomCommune"))]
	name: String,
	#[serde(rename(deserialize = "libelleAcheminement"))]
	routing_label: String,
	#[serde(rename(deserialize = "codePostal"))]
	postcode: String,
}

fn create_db(conn: &Connection) -> Result<()> {
	conn.execute(
		"CREATE TABLE IF NOT EXISTS commune (
		code TEXT PRIMARY_KEY,
		name TEXT NOT NULL,
		routingLabel TEXT NOT NULL,
		postcode TEXT NOT NULL
		)",
		{}
	)?;
	Ok(())
}

fn seed(conn: &Connection) -> Result<()> {
	let f = std::fs::read_to_string("postcodes.json").expect("Could not open the source file.");
	let mut insert_commune = conn.prepare("INSERT INTO commune (name, code, routingLabel, postcode) VALUES (?1, ?2, ?3, ?4)")?;
	let communes: Vec<Commune> = serde_json::from_str(&f).expect("Could not read values inside the source file properly.");
	for commune in &communes {
		insert_commune.execute(
		(
			&commune.name,
			&commune.code,
			&commune.routing_label,
			&commune.postcode,
		),
		)?;
	}
	Ok(())
}

fn main() -> Result<()> {
	let db_path = "./postcodes.sqlite";
	let conn = Connection::open(db_path)?;
	
	let _ = create_db(&conn);
	seed(&conn)
}

A beauty of it is I can call this project finished because I turned a JSON into a SQLite database already. The original 4.8MB postcodes.json is 3 times smaller into the 1.6MB sqlite database.

The project can be “done”, but I can extend it:

Let’s see how this will evolve, but I am finally happy about it! To be continued.