Rust cookbook

Some findings and notes about using rust on some real-world stuff

Handling JSON

A small example how to dump your struct to a file and read it again.

Cargo.toml:

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
use serde::{Serialize, Deserialize};
use serde_json;
use std::io::BufWriter;
use std::io::BufReader;
use std::fs::File;

#[derive(Serialize, Deserialize, Debug)]
enum CarClass {
    Race,
    Offroad,
    Hover
}

#[derive(Serialize, Deserialize, Debug)]
struct Car {
    maxspeed: f64,
    weight: f64,
    class: CarClass
}

fn main() {
    let new_car = Car {
        maxspeed: 120.0,
        weight: 4000.0,
        class: CarClass::Offroad
        };
    
    // write out the file
    let writer = BufWriter::new(File::create("car.json").unwrap());
    serde_json::to_writer_pretty(writer, &new_car).unwrap();

    // read it back again
    let reader = BufReader::new(File::open("car.json").unwrap());
    let loaded_car: Car = serde_json::from_reader(reader).unwrap();
    println!("{:?}", loaded_car);
}

Safely reading JSON

Let's revisit the above example and see how we can ditch the unwrap()s.

let's look at this line:

let loaded_car: Car = serde_json::from_reader(reader).unwrap();

we could break this up of course. The challenge is that we need to tell serde exaclty what to expect so it can deserialize the data properly. All we really know is that we get a result, and there should be a Car in it. So let's just put the _ as a placeholder in there (a.k.a. I don't care, please figure it out for me)

let loaded_car: Result<Car, _> = serde_json::from_reader(BufReader::new(&open_file));

Nice! But what if we want to go more functional and not store any intermediate variables?

We'll just apply what we learned using the turbofish (https://techblog.tonsser.com/posts/what-is-rusts-turbofish):

match serde_json::from_reader::<_, Car>(BufReader::new(open_file)) {...}

See the example in action:

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
use std::io::BufWriter;
use std::io::BufReader;
use std::fs::File;

#[derive(Serialize, Deserialize, Debug)]
enum CarClass {
    Race,
    Offroad,
    Hover
}

#[derive(Serialize, Deserialize, Debug)]
struct Car {
    maxspeed: f64,
    weight: f64,
    class: CarClass
}

fn main() {

    match File::open("car.json") {
        Ok(open_file) => {
            match serde_json::from_reader::<_, Car>(BufReader::new(open_file)) {
                Ok(car) => println!("{:?}", car),
                Err(e) => println!("{:?}",e)
            }
        },
        Err(e) => println!("{:?}",e)
    }


Iterate folder:

https://doc.rust-lang.org/std/fs/fn.read_dir.html

use std::io;
use std::fs::{self, DirEntry};
use std::path::Path;


fn iterate_path(dir: &Path, cb: &Fn(&DirEntry)) -> io::Result<()> {
    if dir.is_dir() {
        for entry in fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.is_dir() {
                visit_dirs(&path, cb)?;
            } else {
                cb(&entry);
            }
        }
    }
    Ok(())
}

or walkdir:

[dependencies]
walkdir = "2"
extern crate walkdir
use walkdir::WalkDir;

for entry in WalkDir::new("foo") {
    dbg!(entry);
}

Argument parsing with clap

[dependencies]
clap = "2"
extern crate clap;
use clap::{Arg, App, SubCommand};

fn main() {
    let matches = App::new("myapp")
                          .version("0.1")
                          .arg(Arg::with_name("dir")
                               .value_name("DIRECTORY")
                               .help("Sets a starting directory")
                               .takes_value(true)
                               .required(true)
                          )
                          .get_matches();
    
    let dir  = matches.value_of("dir").unwrap();
    println!("Value for root: {}", dir);

}
cargo run -- somedir

XML

use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
use treexml::Document;

   
match File::open("yo.xml") {
    Ok(f) => {
        let mut buf_reader = BufReader::new(f);
        let doc = Document::parse(buf_reader).unwrap();
    },
    Err(_e) => ()
}


Run system command

use std::process::Command;

let output = 
Command::new("ls")
        .args(&["-l", "-R"])
        .output()
        .expect("failed to execute process");
let res = output.stdout;
println!("{:?}", res);

Gracefully handling Options and Results in iterators

fn main() {
    let animals = vec![Some("Bear"), Some("Dog"), None, Some("Cat")];
    let valid_animals = animals.iter().flat_map(|x| x).collect::<Vec<_>>()
}

Parse string to type

let i = String::from("42").parse::<i32>().unwrap();
// or let the compiler figure that out from annotating the variable
let i: i32 = String::from("42").parse().unwrap();

Defaults for structs

Derive from Default is the easiest:

#[derive(Default)]
struct Animal {
    hitpoints: i32,
    speed: f64,
    weight: f64
}
fn main() {
    // use all defaults
    let snake = Animal::default();
    //just specify one, use Default for everything else
    let birdy = Animal {
        hitpoints: 500,
        ..
        Default::default()
    };
}

If you want to have hand-crafted defaults, you need to impl your own Default:

struct Animal {
    hitpoints: i32,
    speed: f64,
    weight: f64
}

impl Default for Animal {
    fn default () -> Animal {
        Animal{hitpoints: 500, speed: 3.141, weight: 55.5}
    }
}

fn main() {
    let snake = Animal::default();
}

Building gotchas and Cargo stuff

OSX @rpath

you can use install_name_tool to add a relative library search path to your executable like that:
install_name_tool -add_rpath @executable_path/. your_binary
in case you are using a gui library like libui you can then bundle the libui.A.dylib with your app bundle.
cargo bundle is recommended for packaging the actual app.

create multiple binaries

Let's say you want a GUI app and one for the command line that has minimal size and dependencies. All you need to do is the following in your Cargo.toml:

[[bin]]
name = "cli"
path = "src/cli.rs"

[[gui]]
name = "cli"
path = "src/gui.rs"

cargo build will then build both. Run the binary of your choice with cargo run --bin cli.

Windows programs without a console window

#![windows_subsystem = "windows"]

If you use this in your crate root, no console window will be shown if you run the resulting executable.

Git dependencies

[dependencies]
rand = { git = "https://github.com/rust-lang-nursery/rand" }

But what if your repo is private? Relative url without a base?
First: if you are cloning with ssh, use the appropriate protocol and use an absolute-style url - github will accept replacing : with a /:

[dependencies]
rand = { git = "ssh://git@github.myorg.com/myuser/myrepo.git" }

In order for this to work, .cargo/config needs to specify

[net]
git-fetch-with-cli = true

This file can be in your ~ home dir or in the project itself which makes sense if other people want to build your project.