Hello rust
I finally checked out Rust and solved day one of last year’s advent of code.
tl;dr:
use std::fs::File;
use std::io::{self, prelude::*, BufReader};
// https://stackoverflow.com/a/45882510
fn main() -> io::Result<()> {
// https://doc.rust-lang.org/std/vec/struct.Vec.html
let mut calories = Vec::new();
let mut sum: i32 = 0;
// https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#propagating-errors
let file = match File::open("input"){
Err(e) => return Err(e),
Ok(file) => file,
};
for line in BufReader::new(file).lines() {
let data = match line {
Err(e) => return Err(e),
Ok(data) => data,
};
if data == "" {
calories.push(sum);
sum = 0;
continue
}
// https://doc.rust-lang.org/std/primitive.str.html#method.parse
sum += data.parse::<i32>().unwrap();
}
calories.push(sum);
calories.sort();
println!("part1: {}", calories.iter().rev().take(1).sum::<i32>());
println!("part2: {}", calories.iter().rev().take(3).sum::<i32>());
Ok(())
}
Contents:
- Motivation
- Starting over
- SO vs. official docs
- Investigate
- Expand the shortcuts
- Reaching for the second star
- Thoughts
Motivation
As a C fanboy, I’m interested in languages that have a similar low level scope. Since 2019, Go is my language of choice where I made myself comfortable.
The claims and news brought me to Rust and today my curiousity about them took over:
Starting over
To dive right into the language, I skipped all tutorials and exampls and started with day 1 of the last year’s advent of code right away. Rust is already installed, so I started over by asking the internet for Rust line by line and Rust max value, and here you are:
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
use std::cmp;
// based on https://doc.rust-lang.org/stable/rust-by-example/std_misc/file/read_lines.html
fn main() {
let mut max: i32 = 0;
let mut sum: i32 = 0;
if let Ok(lines) = read_lines("input") {
for line in lines {
if let Ok(data) = line {
if data == "" {
max = cmp::max(max, sum);
sum = 0;
continue
}
sum += data.parse::<i32>().unwrap();
}
}
}
max = cmp::max(max, sum);
println!("{}", max);
}
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where P: AsRef<Path>, {
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
Looking more closely over the copy-pasta code, some constructs are standing out1:
let mut max: i32 = 0;
if let Ok(lines) = read_lines("input") {
data.parse::<i32>().unwrap()
println!("{}, max)
Don’t forget the elephant in the room at the end. The last block seemed too complicated for simply reading a file line-by-line. Let’s address this one first, as it’s the greatest question mark for me.
SO vs. official docs
Well, rather than addressing it, I asked the internet again as the code from the codes looked too optimized. This SO post was more reasonable than the read_lines
section from the official documentation:
use std::fs::File;
use std::io::{self, prelude::*, BufReader};
use std::cmp;
// based on https://doc.rust-lang.org/stable/rust-by-example/std_misc/file/read_lines.html
fn main() -> io::Result<()> {
let mut max: i32 = 0;
let mut sum: i32 = 0;
let file = File::open("input")?;
let reader = BufReader::new(file);
for line in reader.lines() {
if let Ok(data) = line {
if data == "" {
max = cmp::max(max, sum);
sum = 0;
continue
}
sum += data.parse::<i32>().unwrap();
}
}
max = cmp::max(max, sum);
println!("{}", max);
Ok(())
}
Investigate
Let’s get into all the remaining findings from starting over.
Immutable first
Variables are immutable/constant by default. A classical variables needs to be declared with mut
for beeing mutable:
let mut max: i32 = 0;
Error handling
Error handling works by using Results
containing the wanted data on success or an error. They can be assumed via Ok
and Err
, providing the wanted variable if it applies. In case of an error, the condition is false and never enters the branch:
if let Ok(lines) = read_lines("input") {
Alternatively by using ?
aka the question mark operator, the error can implicitly be returned from
the current function:
let file = File::open("input")?;
This can be converted into a more explicit approach using match
where the success and failure case has its own arm.
An arm is kind of a lambda <pattern> => <expression>
. The first pattern match gets selected and the corresponding expression executed. The Err
and Ok
do some kind of type-assertion including declaration of variables e
and file
:
let file = match File::open("input"){
Err(e) => return Err(e),
Ok(file) => file,
};
String to int
Converting a string into an integer needs some parsing. The ::<>
aka turbofish operator is used to tell parse
about the target type:
data.parse::<i32>().unwrap()
Expand the shortcuts
After expanding the error handling shortcuts, the code gets more transparent:
use std::fs::File;
use std::io::{self, prelude::*, BufReader};
use std::cmp;
// https://stackoverflow.com/a/45882510
fn main() -> io::Result<()> {
let mut max: i32 = 0;
let mut sum: i32 = 0;
// https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#propagating-errors
let file = match File::open("input"){
Err(e) => return Err(e),
Ok(file) => file,
};
let reader = BufReader::new(file);
for line in reader.lines() {
let data = match line {
Err(e) => return Err(e),
Ok(data) => data,
};
if data == "" {
max = cmp::max(max, sum);
sum = 0;
continue
}
// https://doc.rust-lang.org/std/primitive.str.html#method.parse
sum += data.parse::<i32>().unwrap();
}
max = cmp::max(max, sum);
println!("{}", max);
Ok(())
}
Reaching for the second star
Finally I used a vector to solve the second aoc part:
use std::fs::File;
use std::io::{self, prelude::*, BufReader};
// https://stackoverflow.com/a/45882510
fn main() -> io::Result<()> {
// https://doc.rust-lang.org/std/vec/struct.Vec.html
let mut calories = Vec::new();
let mut sum: i32 = 0;
// https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#propagating-errors
let file = match File::open("input"){
Err(e) => return Err(e),
Ok(file) => file,
};
for line in BufReader::new(file).lines() {
let data = match line {
Err(e) => return Err(e),
Ok(data) => data,
};
if data == "" {
calories.push(sum);
sum = 0;
continue
}
// https://doc.rust-lang.org/std/primitive.str.html#method.parse
sum += data.parse::<i32>().unwrap();
}
calories.push(sum);
calories.sort();
println!("part1: {}", calories.iter().rev().take(1).sum::<i32>());
println!("part2: {}", calories.iter().rev().take(3).sum::<i32>());
Ok(())
}
While the error handling takes a few lines, summing up the n-th last items of the vector are pretty ruby-alike short:
calories.iter().rev().take(1).sum::<i32>());
Thoughts
It was refreshing to checkout a new language including its resources and toolchain. The error handling with the Ok
and Err
construct is a mixture of the two error handling concepts I saw in other languages so far: try-catch and error value.
Although this looks promising for writing readable and maintainable code, I’m far from judging.
The rustc compiler is colorful and includes some hints which supports my learning-by-doing approach.
Finally I’m hyped and want to learn more about the language. Let’s do some more days!
- In context to Golang and C.