Rust
I have a lot of experience with rust. I have dealt with it in the garden and around the house. I have also had to perform risk-based assessments and fitness-for-service assessments on it professionally as a mechanical engineer… 🤣
As a software engineer, I haven’t used Rust at all professionally but have been interested in it, especially since it has a reputation for being the most loved/admired language on Stack Overflow’s annual developer survey. Whilst I have minimal experience (C and C++) or need for a systems programming language there is always an allure to see what you are missing out on.
I have been using it to create a CLI tool for my personal use to keep track of things I have done throughout the day for work and what tasks I have to do. It has been fully inspired by my friend, Dyota’s workflow/scripts which he uses similarly. The ability to just quickly switch to the terminal and punch in a quick command and message to store a task is a huge time saver, especially since most of my day is in VSCode anyway. One day I will do a post about it, how I use it and maybe even release it as a tool for the general public.
I will have to preface my opinions below are based only on about less than 2 weeks of using it for a basic CLI hobby project. I’m sure I have not even used it to its full potential (and also not hit the bugbears people may encounter in their domains).
Things I enjoy about Rust
- The tooling is awesome!
- cargo, the package manager. A single unified system that makes it easy to pull any library that exists. Awesome.
- rustfmt, the formatter. A built-in language formatter. It’s awesome that you don’t have to worry about formatting your code and you don’t have to have decision paralysis on deciding what formatter to use.
- clippy, a linter. Not the built in linter in Rust, but basically the default. I turned on pedantic mode and it been great at pointing out things I had not considered, especially passing in values or references to functions.
- It forces you to consider every case when using
match
and handleResult
s which is a good thing.
Things I am not that fond about Rust
- Boilerplate code. Due to the use case of the language, to be honest this is not a
true gripe, it’s basically what you signed up for when you use safe Rust. But
nested
match
statements do get to you sometimes. - You can still get out of bounds errors when accessing vectors through some
access patterns.
e.g.
vector.get(10)
is safe butvector[10]
is not and there is no default warning when compiling.
An example Rust function I have written:
pub fn entries(
todo_file_path: &str,
line_number: usize,
done_file_path: &str,
) -> anyhow::Result<()> {
let mut data = get_sorted_data(todo_file_path)?;
let index = line_number - 1;
let entry_to_done_or_none = data.get_mut(line_number - 1);
let Some(entry_to_done) = entry_to_done_or_none else {
eprintln!("Line number {line_number} not found");
std::process::exit(1);
};
let readline = Confirm::new("Proceed with doing the following entry?")
.with_default(false)
.with_help_message(&entry_to_done.entry)
.prompt();
match readline {
Ok(true) => {
let tags_read = MultiSelect::new(
"Tags: ",
vec![
"Review",
"Work on",
"Start day",
"End day",
"Start lunch",
"End lunch",
"Address QA",
"Address review",
"Deploy staging",
"Deploy production",
"Meeting",
],
)
.prompt();
let tags = match tags_read {
Ok(tags) => tags,
Err(err) => {
eprintln!("Error: {err:?}");
std::process::exit(1);
}
};
// TODO: there must be a better way to do this instead of hardcoding the tags strings
let tag_assignment = DoneTagArgs {
address_qa: tags.contains(&"Address QA"),
address_review: tags.contains(&"Address review"),
deploy_staging: tags.contains(&"Deploy staging"),
deploy_production: tags.contains(&"Deploy production"),
meeting: tags.contains(&"Meeting"),
review_work: tags.contains(&"Review"),
worked_on: tags.contains(&"Work on"),
start_day: tags.contains(&"Start day"),
end_day: tags.contains(&"End day"),
start_lunch: tags.contains(&"Start lunch"),
end_lunch: tags.contains(&"End lunch"),
};
let entry_to_done = data.remove(index);
let result = write_to_done_file(done_file_path, &entry_to_done, &tag_assignment);
match result {
Ok(()) => {
let write_result = write_data(todo_file_path, &data);
match write_result {
Ok(()) => println!("Entry removed from todo successfully"),
Err(e) => eprintln!("Error removing entry from todo: {e}"),
}
}
Err(e) => eprintln!("Error writing entry to done: {e}"),
}
}
Ok(false) => {
println!("Entry not done");
std::process::exit(0);
}
Err(err) => {
eprintln!("Error: {err:?}");
std::process::exit(1);
}
}
Ok(())
}
Things I am still wondering about
The borrow checker is a bit of a mystery to me. I haven’t really had to use it, and in places I have, the clippy warnings have been enough to tell me what I should be doing. But I don’t really understand why I am doing it often. That will probably come with experience and may be a bit more reading and doing more complex things.
Conclusion
I do actually love using it, making changes to my CLI tool is so easy, I don’t have to consider many run-time errors, if it compiles, it runs (logic issues are my own 😅). My day-to-day is Python, so it is a nice sea change to use a fully compiled language that can’t lie about its types for once (jokingly shakes fist at Typescript).
I did a quick search of Rust on Seek, Australia’s most popular employment site, and I don’t think there are many positions available for Rust developers unfortunately. I should probably learn more C# to be honest if I wanted to tap into the widest local pool of opportunities. But hey, maybe it will become more popular in the future or some remote opportunities will open up.
I’m happy to dabble 😀.