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.