Create a project
Let’s write some Rust! 🦀
Create
Let’s create a new rust project named diary:
Cargo creates this simple directory structure:
1
2
3
4
|
.
├── Cargo.toml
└── src
└── main.rs
|
Open main.rs
1
2
3
|
fn main() {
println!("Let's eat some fruit!");
}
|
Optimize a bit
- check its size with
du -sh target/debug/diary (3.8M)
- build with
--release flag, i.e. cargo build --release
- check the size now:
du -sh target/release/diary (428K)
…to automaticlally strip symbols from binary (strip=true) and enable link time optimization which removes dead code and may therefore reduce binary size (lto=true)
1
2
3
|
[profile.release]
strip = true # Automatically strip symbols from the binary.
lto = true # see https://llvm.org/docs/LinkTimeOptimization.html
|
Write something useful!
Let’s assume we have a diary file which stores important anniversary dates and descriptions. Its format is very simple:
1
2
3
4
|
1953-03-16 Richard Stallman's birthday
1960-12-28 Linus Torvalds' birthday
1956-01-31 Guido van Rossum's birthday
1961-07-04 Brendan Eich birthday
|
We’d like to print how many days there is left for each of those dates:
1
2
3
4
5
|
Today (2026-02-13):
31 days till #73 anniversary of [Richard Stallman's birthday] (1953-03-16)
318 days till #66 anniversary of [Linus Torvalds' birthday] (1960-12-28)
352 days till #71 anniversary of [Guido van Rossum's birthday] (1956-01-31)
141 days till #65 anniversary of [Brendan Eich birthday] (1961-07-04)
|
Let’s parse an anniversary
With this simple struct we can parse a line that contains date and description.
We will be using chrono library.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#[derive(Debug)]
struct Anniversary {
date: NaiveDate,
description: String,
}
impl Anniversary {
// (code...)
/// Allows to define an anniversary with given date and description
fn from(d: NaiveDate, s: String) -> Anniversary {
Anniversary {
date: d,
description: s,
}
}
}
|
Parsing is non-trivial. We want to be able to parse &str into an Anniversary, so
we will first implement TryFrom<&str> trait (parsing data may fail).
Let’s see how the initial implementation looks like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
impl TryFrom<&str> for Anniversary {
type Error = DiaryErr;
fn try_from(value: &str) -> Result<Anniversary> {
let split_val = value.split_once(" ");
println!("try_From for {value} is {split_val:#?}");
match split_val {
None => Err(DiaryErr("No space separator")),
Some((date, desc)) if date.trim().is_empty() => Err(DiaryErr("Empty date")),
Some((date, desc)) if desc.trim().is_empty() => Err(DiaryErr("Empty description")),
Some((date, desc)) => NaiveDate::parse_from_str(date, Anniversary::FORMAT)
.map(|d| Anniversary::from(d, desc.to_owned()))
.map_err(|e| e.into()),
}
}
}
|
Error - simple struct wrapping static string
…where we are using a simple struct-based error type DiaryErr which holds a description of error reason.
As we want to use this error when we encounter NaiveDate parsing error, we also implement From<chrono::NaiveDate::ParsingError>:
1
2
3
4
5
6
7
8
9
10
11
12
|
#[derive(Debug, Clone)]
struct DiaryErr(&'static str);
impl fmt::Display for DiaryErr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Invalid anniversary definition: {}", self.0)
}
}
impl From<ParseError> for DiaryErr {
fn from(_: ParseError) -> Self {
DiaryErr("parse error")
}
}
|
Representing time difference
If we have an Anniversary parsed from input, we’d like to check which anniversary is ahead of us (we are always interested in the next anniversary) and how many days must pass starting from now.
This time difference data is expressed with AnnoDIff struct:
1
2
3
4
5
|
#[derive(Debug, PartialEq)]
pub struct AnnoDiff {
pub which_anni: u32,
pub days_till: u32,
}
|
and our goal is to implement:
1
|
fn anni_diff(&self, current: NaiveDate) -> Option<AnnoDiff>
|
To be hosnest, the logic of determining AnnoDiff for a given current date is more complex than I expected. Let’s see how the initial implementation looks like. It is clumsy, but hey, I’m learning :)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
pub fn anni_diff(&self, current: NaiveDate) -> Option<AnnoDiff> {
if current < self.date {
return None;
};
let next_anni_date = self.date.with_year(current.year())?;
let which_anni =
current.years_since(self.date)? + (if next_anni_date == current { 0 } else { 1 });
let next_anni_date = if next_anni_date < current {
next_anni_date.checked_add_months(Months::new(12))?
} else {
next_anni_date
};
let days_till = next_anni_date.signed_duration_since(current).num_days();
Some(AnnoDiff {
which_anni,
days_till: days_till as u32,
})
}
|
Main
Finally, we just need to write the main: parse stdin lines and for each line print anniversary information in a nice way:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
fn main() {
let today = today_date();
println!("Today ({today}): ");
for line in io::stdin().lock().lines() {
println!("{}", transform(line.unwrap().as_str(), &today));
}
}
fn today_date() -> NaiveDate {
Local::now().date_naive()
}
fn transform(s: &str, today: &NaiveDate) -> String {
match Anniversary::try_from(s) {
Ok(anni) => match anni.anni_diff(*today) {
Some(diff) => format!(
"{} days till #{} anniversary of [{}] ({})",
diff.days_till, diff.which_anni, anni.description, anni.date
),
None => format!("Could not find time delta for {s}"),
},
Err(DiaryErr(info)) => format!("Error: {info} when parsing {s}"),
}
}
|
Ok, this way I know I still have one month left to think about a gift for Mr Richard Stallman.
You, dear reader, could also use a system (or write yourself some rust) to remember about:
- birthdays of your spouse, kids, parents
- important events in your life
- anniversaries of projects’ completions, achievements etc
It would be quite useful to allow enter dates in the future and display how many dats is left - for example, for tracking of your goals, boxed projects or obligations/deadlines.
Or you can just use org-mode :)
That’s all for today.
Happy hacking!
Code is available on GitHub
Resources