software/rust
Rust
Resources
- http://xion.io/post/code/rust-iter-patterns.html
- https://deterministic.space/rust-cli-tips.htm
- https://deterministic.space/elegant-apis-in-rust.html
- https://manishearth.github.io/blog/2018/01/10/whats-tokio-and-async-io-all-about/
- https://saghm.github.io/five-rust-things/
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/