benni.tech

My first tries with Rust

2018-06-10

The last months I have seen a lot of blog posts writing about the Rust programming language โ€” sometimes how good it is, sometimes why it is better than other languages. Then I came across Writing an OS in Rust and thought: why not give Rust a try?

Goals

Inspired by this I am trying Rust for a few hours: understanding principles and design goals, and building something simple. I will read some documentation, set up a local dev environment, develop a hello world program and then something a bit more complex.

Investigations

I started by reading:

Setting up a local dev environment

The Arch Linux rustup package from pacman didn't work for me โ€” I missed configuring a default toolchain. So I went with the official install:

curl https://sh.rustup.rs -sSf | sh
source $HOME/.cargo/env

To update later:

rustup update && cargo update

Verify versions:

$ rustc --version && cargo --version
rustc 1.26.2 (594fb253c 2018-06-01)
cargo 1.26.0 (0e7c5a931 2018-04-06)

The full installation was only 13 MB:

$ du -sh ~/.cargo
13M	/home/benni/.cargo

Hello World

Create hello.rs:

/// The main function
fn main() {
    // Print text
    println!("Hello World!");
}

Compile and run:

rustc hello.rs
./hello

A REST API with Rocket

After hello world I wanted something more serious: a simple REST API with unit tests.

Create the project:

cargo new hello-api --bin && cd hello-api

Add dependencies to Cargo.toml:

[dependencies]
rocket = "0.3.12"
rocket_codegen = "0.3.12"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"

[dependencies.rocket_contrib]
default-features = false
features = ["json"]

Add src/main.rs:

#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]

extern crate rocket;
#[macro_use] extern crate rocket_contrib;
#[macro_use] extern crate serde_derive;

#[cfg(test)] mod tests;

use rocket_contrib::{Json, Value};

#[derive(Serialize, Deserialize)]
struct HelloInput {
    name: String
}

#[get("/", format = "application/json")]
fn index() -> Json<Value> {
    Json(json!({ "status": "OK" }))
}

#[post("/", format = "application/json", data = "<json_input>")]
fn hello(json_input: Json<HelloInput>) -> Json<Value> {
    Json(json!({ "hello": json_input.name }))
}

#[error(404)]
fn not_found() -> Json<Value> {
    Json(json!({ "status": "error", "reason": "Resource was not found." }))
}

fn rocket() -> rocket::Rocket {
    rocket::ignite()
        .mount("/", routes![index])
        .mount("/hello", routes![hello])
        .catch(errors![not_found])
}

fn main() {
    rocket().launch();
}

The initial build failed because Rocket 0.3 required nightly:

rustup default nightly
rustup update && cargo update
cargo build

Run it:

$ cargo run
๐Ÿš€  Rocket has launched from http://localhost:8000

Test with curl:

$ curl http://localhost:8000/
{"status":"OK"}

$ curl --request POST \
  --header "Content-Type: application/json" \
  --data '{"name":"benni"}' \
  http://localhost:8000/hello
{"hello":"benni"}

Unit tests

Add src/tests.rs:

use rocket;
use rocket::local::Client;
use rocket::http::{Status, ContentType};

#[test]
fn test_not_found() {
    let client = Client::new(rocket()).unwrap();
    let res = client.get("/not_found").header(ContentType::JSON).dispatch();
    assert_eq!(res.status(), Status::NotFound);
}

#[test]
fn test_alive() {
    let client = Client::new(rocket()).unwrap();
    let mut res = client.get("/").header(ContentType::JSON).dispatch();
    let body = res.body_string().unwrap();
    assert_eq!(res.status(), Status::Ok);
    assert!(body.contains("OK"));
}

#[test]
fn test_post_hello() {
    let client = Client::new(rocket()).unwrap();
    let mut res = client.post("/hello")
        .header(ContentType::JSON)
        .body(r#"{ "name": "benni" }"#)
        .dispatch();
    assert_eq!(res.status(), Status::Ok);
    let body = res.body().unwrap().into_string().unwrap();
    assert!(body.contains("hello"));
}
$ cargo test
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Conclusion

My first try with Rust took about 6 hours. After that I could write simple programs with tests and work with the CLI and dependencies. Compared to Go, which I learned 1.5 years earlier, the learning curve felt similar. What I appreciated most:

The syntax felt a bit strange at first, especially macros and extern. I expect it gets easier with more use. I'm planning to invest more time in Rust to see if it fits my personal purposes.

Note: this article uses Rocket 0.3 (2018). The Rocket API has changed significantly since then.

References