|
| 1 | +# Rerun code style |
| 2 | + |
| 3 | +## See also |
| 4 | +* [`ARCHITECTURE.md`](ARCHITECTURE.md) |
| 5 | +* [`BUILD.md`](BUILD.md) |
| 6 | +* [`CONTRIBUTING.md`](CONTRIBUTING.md) |
| 7 | +* [`RELEASES.md`](RELEASES.md) |
| 8 | + |
| 9 | +## Rust code |
| 10 | + |
| 11 | +### Error handling and logging |
| 12 | +We log problems using our own `re_log` crate (which is currently a wrapper around [`tracing`](https://crates.io/crates/tracing/)). |
| 13 | + |
| 14 | +* An error should never happen in silence. |
| 15 | +* Validate code invariants using `assert!` or `debug_assert!`. |
| 16 | +* Validate user data and return errors using [`thiserror`](https://crates.io/crates/thiserror). |
| 17 | +* Attach context to errors as they bubble up the stack using [`anyhow`](https://crates.io/crates/anyhow). |
| 18 | +* Log errors using `re_log::error!` or `re_log::error_once!`. |
| 19 | +* If a problem is recoverable, use `re_log::warn!` or `re_log::warn_once!`. |
| 20 | +* If an event is of interest to the user, log it using `re_log::info!` or `re_log::info_once!`. |
| 21 | +* The code should only panic if there is a bug in the code. |
| 22 | +* Never ignore an error: either pass it on, or log it. |
| 23 | +* Handle each error exactly once. If you log it, don't pass it on. If you pass it on, don't log it. |
| 24 | + |
| 25 | +### Log levels |
| 26 | + |
| 27 | +The log is for several distinct users: |
| 28 | +* The application user |
| 29 | +* The application programmer |
| 30 | +* The library user |
| 31 | +* The library programmer |
| 32 | + |
| 33 | +We are all sharing the same log stream, so we must cooperate carefully. |
| 34 | + |
| 35 | +#### `ERROR` |
| 36 | +This is for _unrecoverable_ problems. The application or library couldn't complete an operation. |
| 37 | + |
| 38 | +Libraries should ideally not log `ERROR`, but instead return `Err` in a `Result`, but there are rare cases where returning a `Result` isn't possible (e.g. then doing an operation in a background task). |
| 39 | + |
| 40 | +Application can "handle" `Err`ors by logging them as `ERROR` (perhaps in addition to showing a popup, if this is a GUI app). |
| 41 | + |
| 42 | +#### `WARNING` |
| 43 | +This is for _recoverable_ problems. The operation completed, but couldn't do exactly what it was instructed to do. |
| 44 | + |
| 45 | +Sometimes an `Err` is handled by logging it as `WARNING` and then running some fallback code. |
| 46 | + |
| 47 | +#### `INFO` |
| 48 | +This is the default verbosity level. This should mostly be used _only by application code_ to write interesting and rare things to the application user. For instance, you may perhaps log that a file was saved to specific path, or where the default configuration was read from. These things lets application users understand what the application is doing, and debug their use of the application. |
| 49 | + |
| 50 | +#### `DEBUG` |
| 51 | +This is a level you opt-in to to debug either an application or a library. These are logged when high-level operations are performed (e.g. texture creation). If it is likely going to be logged each frame, move it to `TRACE` instead. |
| 52 | + |
| 53 | +#### `TRACE` |
| 54 | +This is the last-resort log level, and mostly for debugging libraries or the use of libraries. Here any and all spam goes, logging low-level operations. |
| 55 | + |
| 56 | +The distinction between `DEBUG` and `TRACE` is the least clear. Here we use a rule of thumb: if it generates a lot of continuous logging (e.g. each frame), it should go to `TRACE`. |
| 57 | + |
| 58 | + |
| 59 | +### Libraries |
| 60 | +We use [`thiserror`](https://crates.io/crates/thiserror) for errors in our libraries, and [`anyhow`](https://crates.io/crates/anyhow) for type-erased errors in applications. |
| 61 | + |
| 62 | +For faster hashing, we use [`ahash`](https://crates.io/crates/ahash) (`ahash::HashMap`, …). |
| 63 | + |
| 64 | +When the hashmap key is high-entropy we use [`nohash-hasher`](https://crates.io/crates/nohash-hasher) (`nohash_hasher::IntMap`). |
| 65 | + |
| 66 | +### Style |
| 67 | +We follow the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/about.html). |
| 68 | + |
| 69 | +We use `rust fmt` with default settings. |
| 70 | + |
| 71 | +We have blank lines before functions, types, `impl` blocks, and docstrings. |
| 72 | + |
| 73 | +We format comments `// Like this`, and `//not like this`. |
| 74 | + |
| 75 | +When importing a `trait` to use it's trait methods, do this: `use Trait as _;`. That lets the reader know why you imported it, even though it seems unused. |
| 76 | + |
| 77 | +When intentionally ignoring a `Result`, prefer `foo().ok();` over `let _ = foo();`. The former shows what is happening, and will fail to compile if `foo`:s return type ever changes. |
| 78 | + |
| 79 | +### `TODO`:s |
| 80 | +When you must remember to do something before merging a PR, write `TODO` or `FIXME` in any file. The CI will not be green until you either remove them or rewrite them as `TODO(yourname)`. |
| 81 | + |
| 82 | +You can also use the `todo()!` macro during development, but again it won't pass CI until you rewrite it as `todo!("more details")`. Of course, we should try to avoid `todo!` macros in our code. |
| 83 | + |
| 84 | + |
| 85 | +### Misc |
| 86 | +Use debug-formatting (`{:?}`) when logging strings in logs and error messages. This will surround the string with quotes and escape newlines, tabs, etc. For instance: `re_log::warn!("Unknown key: {key:?}");`. |
| 87 | + |
| 88 | +Use `re_error::format(err)` when displaying an error. |
| 89 | + |
| 90 | +### Naming |
| 91 | +When in doubt, be explicit. BAD: `id`. GOOD: `msg_id`. |
| 92 | + |
| 93 | +Be terse when it doesn't hurt readability. BAD: `message_identifier`. GOOD: `msg_id`. |
| 94 | + |
| 95 | +Avoid negations in names. A lot of people struggle with double negations, so things like `non_blocking = false` and `if !non_blocking { … }` can become a source of confusion and will slow down most readers. So prefer `connected` over `disconnected`, `initialized` over `uninitialized` etc. |
| 96 | + |
| 97 | +For UI functions (functions taking an `&mut egui::Ui` argument), we use the name `ui` or `_ui` suffix, e.g. `blueprint_ui(…)` or `blueprint.ui(…)`. |
| 98 | + |
| 99 | +#### Spaces |
| 100 | +Points, vectors, rays etc all live in different _spaces_. Whenever there is room for ambiguity, we explicitly state which space something is in, e.g. with `ray_in_world`. |
| 101 | + |
| 102 | +Here are some of our standard spaces: |
| 103 | + |
| 104 | +* `ui`: coordinate system used by `egui`, measured in logical pixels ("points"), with origin in the top left |
| 105 | +* `image`: image pixel coordinates, possibly with an added `z=depth` |
| 106 | +* `space`: a user-defined space where they log stuff into |
| 107 | +* `world`: the common coordinate system of a 3D scene, usually same as `space` |
| 108 | +* `view`: X=right, Y=down, Z=back, origin = center of screen |
| 109 | + |
| 110 | +#### Matrices |
| 111 | +We use column vectors, which means matrix multiplication is done as `M * v`, i.e. we read all matrix/vector operations right-to-left. We therefore name all transform matrices as `foo_from_bar`, for instance: |
| 112 | + |
| 113 | +```rust |
| 114 | +let point_in_world = world_from_view * point_in_view; |
| 115 | +``` |
| 116 | + |
| 117 | +This means the name of the space matches up nicely, e.g.: |
| 118 | + |
| 119 | +```rust |
| 120 | +let projection_from_object = projection_from_view * view_from_world * world_from_object; |
| 121 | +``` |
| 122 | + |
| 123 | +See <https://www.sebastiansylvan.com/post/matrix_naming_convention/> for motivation. |
| 124 | + |
| 125 | +For consistency, we use the same naming convention for other non-matrix transforms too. For instance, functions: `let screen = screen_from_world(world);`. |
| 126 | + |
| 127 | +#### Vectors vs points |
| 128 | +Vectors are directions with magnitudes. Points are positions. |
0 commit comments