software/rust

Rust

Resources

Optimization: use RUSTFLAGS="-C target-cpu=native" to take advantage of CPU special features.

(via http://vfoley.xyz/rust-compilation-tip/)

For local rust/std documentation, do rustup doc.

Little tricks

Run tests with stdout output:

cargo test -- --nocapture

To run tests with logging enabled (eg, with env_logger), make sure you add env_logger::init() to the test function itself.

map() and Result Ergonomics

.collect() has some magical features! In addition to turning an iterator of Item into Vec<Item>, it will turn an iterator of Result<Item> into Result<Vec<Item>>. This makes it really useful for the end of functions.

This is particularly useful for resolving some categories of “error handling in map closures”: you can use ? in the map closure as long as you wrap the happy path with Ok() and call collect on the outside. Eg:

let list: Vec<Item> = junk
    .iter()
    .map(|thing| Ok(Item {
        a: thing.a,
        b: fixup(thing.widget)?,
    }))
    .collect::Result<Vec<Item>>()?;

What about when map over an Option? Eg:

let toy = Shiny {
    a: 123,
    b: component.map(|v| paint(v).expect("paint to succeed"),
};

Should use match in this case:

let toy = Shiny {
    a: 123,
    b: match component {
        None => None,
        Some(v) => Some(paint(v)?),
    },
};

2020-05-17 Reading

While working on fatcat-cli tool, checked the The Rust Programming Language book to read about trait objects and the dyn keyword, which I had ignored previously.

They seem like they could be used in a few places in fatcat-server rust code. We don’t particularly care about per-function-call performance there, and most entities are already allocated on the heap.

Other small syntax and thing learned:

Can copy a struct while only updating specific fields with “..” syntax. Might use this in fatcat-cli for update mutation.

This is the cleanest example of using ErrorKind that I have seen:

let f = match f {
    Ok(file) => file,
    Err(error) => match error.kind() {
        ErrorKind::NotFound => match File::create("hello.txt") {
            Ok(fc) => fc,
            Err(e) => panic!("Problem creating the file: {:?}", e),
        },
        other_error => {
            panic!("Problem opening the file: {:?}", other_error)
        }
    },
};

I didn’t realize that test code may get compiled into non-test binaries unless annotated with #[cfg(test)]. You are supposed to create a sub-module within each src/ file with unittests, like:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

This doesn’t apply to tests/ directory, which is for integration tests.

The common pattern for binary crates (vs. library crates) is to have main.rs and lib.rs, with any code that needs to be tested in lib.rs (aka, all the actual logic).

I think I knew eprintln!() (for stderr) vs. println!() (for stdout), but good to remember.

There is a description of how to avoid memory leaks with reference counting using “weak” Rc references. Probably worth reading the entire chapter on smart pointers (including Box, Rc, RefCell) again.

For the Sized trait, and Sized trait alone, can specify an ambiguous trait constraint with ? to indicate “may or may not be Sized”, which doesn’t really mean anything but does explicitly allow generic functions over non-sized traits like:

fn my_generic_func<T: ?Sized>(t: &T) {
    // --snip--
}

A trait can depend on another trait. For example, a PrettyPrint trait could rely on Display (and impl functions could call functions from Display). This is done on the trait definition line. Such a trait is called a “supertrait”.

Implementing Deref on a wrapper type allows transparent access to all the trait methods on the interior object.

Also, a new longer post on error handling: https://nick.groenen.me/posts/rust-error-handling/