summaryrefslogtreecommitdiffstats
path: root/software/rust.page
blob: dc900c8fce389be389f53c76ef49f891893cda13 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
Rust
==================

## Resources

- <http://xion.io/post/code/rust-iter-patterns.html>
- <https://deterministic.space/rust-cli-tips.htm>
- <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](https://doc.rust-lang.org/book/ch15-06-reference-cycles.html#preventing-reference-cycles-turning-an-rct-into-a-weakt)
(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/>