The Grass is Sometimes Greener - Success with Rust

Published

I'm not a member of Rust community sites as I don't really feel like I can contribute anything meaningful, but I occasionally browse /r/rust and Hackernews. A few days ago I was surprised when a friend had pointed out to me that the article I wrote made it to Hackernews, and I was even more surprised when I saw it was generating a lot of really good conversation. It was hard to find anything that didn't feel constructive or was mean (though... not impossible!).

Here's a couple great quotes from the discussion:

One thing I've found with rust is that you struggle struggle struggle trying to do a simple task, and then finally someone says "Oh, all you need to do is this". Rust has already reached the point where it leaves the world behind. Only the people who have been there since the early days really understand it, and getting into rust gets harder and harder as time goes on. -- kstenerud

This was a pretty good point on the current top comment. I think this overstates the problem a little bit, but I do think there's an enormous amount of value in detailing your hardships with the language so that the community doesn't dissipate into a very niche group of people that think Rust can do no wrong. In the end Rust is trying to cook up a really clever language the design that does what other languages haven't - be fast without cost. It makes sense that it'll take a few passes to get there.

The big question is would the Python script crash or handle the error when obvious problems like not valid JSON or file not found happen? -- dep_b

That's actually fair. There were at least a few places where the Python code would potentially throw an unhandled exception for sure, making a 1 to 1 comparison somewhat unfair.

I do think people focused a bit too much on the "I'm Porting something from Python" aspect and less on the "It's really unclear how to do idiomatic things in Rust." I was not expecting to write a 20 liner in Rust. Good for the people that understand Rust readily and posted their ports of my Python code, but my experiences are not unique.

That Said, I Got Stuff to Work

Happily chugging along, I put the toy Jenkins project on ice and put what I had learned into another project. This time, I wanted to query Rancher (a Docker orchestration system) for a bunch of running containers, ping them all, and then send that data to InfluxDB. This was definitely more of an elaborate project than the initial one, but it was much closer to a real project and thus had more value to me. Plus I had my coffee and was stoked.

Racer and Rust Language Server are Game Changers

Nothing helps learning something more than having a clear development loop. The faster you can go from hitting Ctrl+S to knowing if your code worked, the better. I developed the Rancher ping code with VS Code with the Rust extension, which prompted me to install Racer, Rust Language Server and Rustfmt.

This instantly turned VS Code into a very productive Rust workstation. I quickly was able to know when to add mut, which objects would not live long enough to do what I want with them, and other little helpful hints.

Diving In with Error Handling

First thing I wanted to tackle was the error handling, since it was the focal pain point I encountered last time. I regret not really digging into error_chain, the Rust community package that makes error creation more straightforward. I went the route of making one big Error enum for my project.

#[derive(Debug)]
pub enum RancherHealthCheckError {
    IoError,
    FormatError,
    JsonError,
    ConnectionTimeoutError,
    ConnectionResetError,
    ReqwestError(reqwest::Error),
    UrlParseError,
    BadConfigError
}

impl From<io::Error> for RancherHealthCheckError {
    fn from(_: io::Error) -> RancherHealthCheckError {
        RancherHealthCheckError::IoError
    }
}

impl fmt::Display for RancherHealthCheckError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            RancherHealthCheckError::IoError             => write!(f, "IO Error"),
            RancherHealthCheckError::ReqwestError(ref e) => write!(f, "ReqwestError {}", e),
            _                                            => write!(f, "Unknown RancherHealthCheckError"),
        }
    }
}

// ... and so forth ...

/// Display to make Rancher errors printable.
impl fmt::Display for RancherHealthCheckError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            RancherHealthCheckError::IoError             => write!(f, "IO Error"),
            RancherHealthCheckError::ReqwestError(ref e) => write!(f, "ReqwestError {}", e),
            RancherHealthCheckError::JsonError           => write!(f, "Serde JSON Error"),
            RancherHealthCheckError::BadConfigError      => write!(f, "Bad configuration error"),
            _                                            => write!(f, "Unknown RancherHealthCheckError"),
        }
    }
}

This isn't bad, and a lot of the issues with getting the details right is significantly helped out by having VS Code set up to give me instant feedback.

Everything Else was Pretty Easy

Once I had the error handling set up in a way that would give me good feedback early on in the dev process, the rest kind of came together. Using the Rust Oping library was extremely straightforward, largely due to the great examples that the author provided:

let mut ping = oping::Ping::new();

ping.set_timeout(5.0).unwrap();

// Add every host container host to Ping

for container in running_containers {
    match container.ip_address {
        None => {
            println!("Skipping {}, no ip.", container.name);
        },
        Some(ip_address) => {
            ping.add_host(ip_address.as_str()).unwrap();
        }
    }
}

// Ping everything!

match ping.send() {

The InfluxDB package was a similar story: it worked. Reqwest also had totally clear and easy examples that drove fast development. These Rust libraries have sub 1.0.0 version numbers, but using them still feels pretty stable. Figuring out how to use strings vs &str's properly took a few passes, but again having fast feedback helped tremendously.

On Learning

I learned a lot about my approach to learning things recently. I think what I have a tendency to do is see something that I like, know what I want to use it for, and then try to use it while still having a mental model about how it ought to work in my mind. I think I need to spend more time to learn something, taking notes and writing practical examples, before I attempt to use it for something that's even a toy project. This is probably just a good long term approach for anything cool in the software world.

This was a good learning experience for me. I think it's valuable for people to write up their difficulties with using something, because they're not as alone as they think, but it's also good to take a step back and really try to learn something in a structured way.