Refreshing My Rusty Rust was Quite Refreshing
@October 19, 2023
About five years ago I first heard of Rust and went through a tutorial. I was intrigued with the ownership concept. Finally a new language that is not just a “me too” copy of another language with slightly adapted syntax. I kept in touch with the release notes over the years and did a few small project patches here and there but didn’t really apply the language. Finally, I recently came across a simple task that I felt would be a great showcase to see what I remembered and if Rust had evolved and it did not disappoint.
Parsing a File and Merge Entries
My problem is that ddrescue
is a forensic disk recovery utility that produces a text log file with a header, and a bunch of simple lines in a simple line-based text format
# header
# lines
0x0000 0x0100 +
0x0100 0x0001 ?
0x0101 0x0100 +
0x0200 0x0200 ?
In my case, I had a 700Mb log file where most chunks for tiny but (almost)-consecutive and sharing the same status. In the example above, look at the two first data lines which could be merged into one by dropping the second line and adding its size to the first block. Merging my real-life file resulted in a file of only a few kilobytes. A huge difference when it comes to loading the visual representation of the process with ddrescueview
, a viewer for that log file format.
The problem does not have to be solved with rust - a simple awk, python or perl script would have done the trick. Alas, I was curious about how Rust would fare and I was not disappointed even though it took me a lot longer than the simple scripted solutions would have. Still my weekly quota on trying out new things was not used up just yet.
Great Compiler Errors and Warnings
Rust already had useful error messages years ago, but it seems the tradition went on, and help onboarding newcomers:
115 | fn reader_from_str(&String log) -> BufRead {
| --------^^^
| | |
| | expected one of `:`, `@`, or `|`
| help: declare the type after the parameter binding: `<identifier>: <type>`
Type variable_name
and suggests turning it around.A not overly obsessive linter is included. Some linters will bark at everything, Rust’s seems quite conservative but strong enough to create a sense of somewhat standardized practices in the community.
warning: unnecessary parentheses around `if` condition
--> src/main.rs:47:16
|
47 | if (skip_next) {
| ^ ^
I could add more examples but you get the idea …
A Unique and Beautiful Language
Rust is unique in quite some ways and a few very sharp minds are active in the compiler scene. Big kudos to everyone involved in making the ownership model more transparent (e.g. lifetime elision) to the casual user. It hardly ever gets in my way anymore.
I love that all possibilities have to be matched in a match
(switch) statement, pattern matching for destructuring objects in if
clauses (if let Foo(x) = myfoo { println!(x); }
), enum
with parameters, a macro language that doesn’t drive you crazy if you want to do more than small substitutions, variables are const unless marked mutable and many more. Also, IDE integration with VScode is flawless.
Easy to Add Tests
It’s worth noting how easily tests can be added to the respective files/modules.
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_merge_question_mark_entries() {
let log = "0x00000000 0x00000100 ?\n0x00000100 0x00000200 ?\n0x00000300 0x00000400 ?";
let expected_output = "0x00000000 0x00000700 ?\n";
let mut result = Vec::new();
process_ddrescue_log_file(&mut Cursor::new(log), &mut result).unwrap();
let result_str = String::from_utf8(result).unwrap();
assert_eq!(result_str, expected_output);
}
// ...
}
running 4 tests
test tests::test_unparsable_hex ... ok
test tests::test_merge_question_mark_entries ... ok
test tests::test_skip_header ... ok
test tests::test_merge_entries ... ok
cargo test
awayCargo
Cargo is the awesome companion tool to the Rust compiler for setting up the project cargo new project-name
, installing and managing modules and calling the compiler and it’s been there from the start.
Rust Delivers
Best of all, I’ve never seen a language where once a piece of code compiles, it (mostly) does what it’s supposed to. I’m definitely not as productive in Rust as in Python just yet, but the language has not disappointed and took a step forwards over the last few years. It’s also great to see Rust gaining more traction in critical environments like the Linux kernel.
Still, to get a grasp of ownership and lifetimes, it’s worth noting that knowing how lower level languages without garbage collection work and concepts of stack/heap should not be unfamiliar for Rust to be a truly lovable language.