Rust Standards & Tips

We use rust for a few projects right now, including both well supported Odyssey projects and lesser used experimental firmware projects. This page is split into 3 sections → general notes, linux/multithreaded notes, and firmware notes. Make sure to read the applicable sections before developing in rust at NER.

 

IDE Setup

Use rust-analyzer extension and you should be all set for both embedded and applications context.

Helpful cargo commands

  • cargo run to test your code

  • cargo run --release to test your code’s performance

  • cargo clippy to check your code passes linter

  • cargo fmt to format your code

  • cargo test to use your tests on the code

General Notes

  • Do not use (import) modules in lib.rs!! This could global import things. Instead use full paths.

  • Use Result and Option. These structs are not just for libraries, use them for your own return results.

    • Use Result for things that could return an error object.

    • Use Option for things that could return null or may not “find” anything.

  • NEVER unwrap. Alternatives

    • expect if an error is a critical failure and rare

    • unwrap_or_default if you do not care about the value and will no-op if the value is empty, 0, etc.

    • match the some/none or ok/err if you need to transform the error and/or break out/return from a function

    • unwrap_or_else if you need to provide a default value and want to perform other operations

    • unwrap_or to provide a const-context default

  • For functions with alot of parameters, use an options struct and pass that struct in.

  • For struct initialization, use a new function which returns Self

  • Printing

    • Debug print something (implement with derive(Debug) on the struct): {:?}

    • Display value of something (implement with derive(Display) on the struct): {}

  • If you use a library, do not use its dependencies directly. Instead add them to Cargo.toml.

    • Example: dont use prisma_client_rust::chrono , instead use chrono as then if you remove prisma_client_rust chrono will still work

  •  

Applications notes

Entrypoint/settings

  • Use clap and clap_derive for all settings that should be changed at runtime. As rust is a compiled language, making a setting that may require changing involves rebuiliding and deploying a sometimes hefty application. Use clap_derive (as seen here) to expose your setting to the program. Use the environment variables as the example shows

  • Dockerization should use environment variables for all settings (see above)

Tokio/Threading

  • Use threads conservatively. If you need two+ things to happen on a loop, use a tokio::select in order to time multiple things that require access to the same variables.

  • Use print levels (trace, debug, info, warn) when you want a print to be included in the final product

 

Firmware notes

  • Use the embassy version of stuff. Embassy provides alternatives of normal functions for most stuff, use them. Do not consider adding a new library unless you are sure an embassy proejct doesn’t already include it.

  • Do all initialization of hardware using the peripherals variable in main.rs , and then once a new object is procured initialize that in the task.

  • Ensure you arent copying things into functions. Make their context static or give a lifetime, do not copy a variable, especially a String.

  • Use DMA/await when possible. No matter how much work, avoid the xxx_blocking function available, use the await async functions. This allows better parallelism within code leading to optimal performance.