David Harks bio photo

David Harks

There are few things in life more satisfying than solving a great problem.

Email Twitter LinkedIn Github Stackoverflow

Lately I’ve been playing around with Rust, and there are some fantastic things to love about it: it’s fast, forces you to think through safety risks up front, and provides some of the best features of functional languages (pattern matching! Closures! Expression-oriented syntax!). Since the best way to learn a language is to use it, I decided to write a simple program to summarize what CPU is in my system via reading /proc/cpuinfo.

Features

  • Read /proc/cpuinfo and parse its format
  • Summarize the following fields:
    • Vendor
    • Model Name
    • # of physical cores
    • # of virtual cores (“threads”)
    • MHz
  • Output the results in a nice, compact format.
  • Use idiomatic Rust and the Rust Standard Library
  • Use the daily builds of Rust – track the compiler

try! (and facepalm)

Of course, I picked a time when the language is settling down but not 100% stable to jump in to learning it, so the first thing that happened is I let myself get confused by writing the following code in main:

let f = try!(File::open("/proc/cpuinfo"));

…which didn’t compile. It said:

<std macros>:5:8: 6:51 error: mismatched types:
 expected `()`,
	found `core::result::Result<_, _>`
(expected (),
	found enum `core::result::Result`) [E0308]
<std macros>:5 return $ crate:: result:: Result:: Err (
<std macros>:6 $ crate:: error:: FromError:: from_error ( err ) ) } } )
<std macros>:1:1: 6:57 note: in expansion of try!
src/main.rs:14:13: 14:47 note: expansion site
error: aborting due to previous error

I thought this maybe had to do with recent changes in the standard library (which, in hindsight, was silly – of course Rustaceans wouldn’t miss something this simple!), so I spent some time bisecting what the expectations were of code in std::old_io vs std::io, but they were so very similar that it just didn’t make sense.

Thankfully, I found Rust by Example, which gives runnable examples of all the major features of Rust. Fantastic stuff! There, I saw that using match {} was the way to manage possible failures when opening a file:

let mut cpuinfo = match File::open("/proc/cpuinfo") {
        Ok(f) => f,
        Err(e) => panic!("{}", e)
    };

Fair enough, the return type is ioResult<T> (which is really a typedef for Result<T, ioError>), so you have to destructure it. But it was bugging me that you’d have a try! macro that supposedly does that destructuring yet doesn’t compile. And how do I interpret that compiler error?

I finally came back to reading the documentation on try! here. Aha! See, my C++/Python/Java roots bit me; I assumed that try! was similar to a try..catch expression, but no – it’s really intended to make handling a series of operations more elegant – you still ultimately use a match to destructure the Result<>. With try!, you can group together multiple operations that could return an error into a function that returns the Result of the entire group; if any of the steps returns Err, you early-return and end up in the error branch.

Okay, so what did that compiler error tell me? Well, it told me what it knew: that main had no return value defined (thus, ()), yet my use of try! was giving a Result<>. Expression-oriented syntax: the value of the last expression in the function is the return value of the function. So boom: I was being dumb.

So – I learned that Rust has no exceptions, but with a way to group a series of operations and handle failures gracefully. I dig. Certainly avoids the pitfalls of the entire program being a gigantic try..catch with a single, generic exception handler!

I also learned that the compiler is way smarter than I am.

I also re-learned what Mr. Eugene Lewis Fordsworthe said: Assumption is the mother of all mistakes.

Next time, I’ll actually do something with this file I’ve managed to properly open.