Coding Guides
Introduction
We present coding guides for the reliable message bus (RMB).
This document will always be
work in progress
. Read the official docs for updates.
Module Structure
In Rust there are multiple ways to create a (sub)module in your crate
<module>.rs
A single file module. can be imported inmain.rs
orlib.rs
with the keywordmod
<module>/mod.rs
A directory module. Uses mod.rs as the moduleentrypoint
always. Other sub-modules can be created next to mod.rs and can be made available by using themod
keyword again in themod.rs
file.
We will agree to use the 2nd way (directory) but with the following restrictions:
mod.rs
will have alltraits
andconcrete types
used by the traits.<implementation>.rs
file next tomod.rs
that can include implementation for the module trait. z
Example
Following is an example of animal
module.
animal/
mod.rs
dog.rs
cat.rs
File names are always in
snake_case
but avoid the_
as much as possible because they basically look ugly in file tree. For example we prefer the namedog.rs
overdog_animal.rs
because we already can tell from the module name that it's adog
animal. Hence in the identity module for example the nameed25519.rs
is preferred overed25519_identity.rs
because that's already inferred from the module name.
The mod.rs
file then can contain
#![allow(unused)] fn main() { pub mod dog; pub mod cat; pub use dog::Dog; pub trait Food { fn calories(&self) -> u32; } pub trait Animal<F> where F: Food, { fn feed(&mut self, food: F); } }
The dog.rs
file then can contain
#![allow(unused)] fn main() { use super::{Animal, Food}; pub struct DogFood {} impl Food for DogFood { fn calories(&self) -> u32 { 1000 } } pub struct Dog {} impl Animal<DogFood> for Dog { fn feed(&mut self, food: DogFood) { println!("yum yum yum {} calories", food.calories()); } } }
A user of the module now can do
use animal::dog::{Dog, DogFood};
For common implementation that are usually used in your modules, a pub use
can be added in mod.rs
to make it easier to import your type. For example
#![allow(unused)] fn main() { // dog is brought directly from animal crate use animal::Dog; // cat i need to get from the sub-module use animal::cat::Cat; }
Naming Conventions
Following the rust guide lines for name
file names
are short snake case. avoid_
if otherwise name will not be descriptive. Check note about file names above.trait
,struct
,enum
names are allCamelCase
fn
,variables
names are snake case
Note, names of functions and variables need to be descriptive
but short at the same time. Also avoid the _
until absolutely necessary. A variable with a single word
name is better if it doesn't cause confusion with other variables in the same context.
The name of the variable should never include the type
.
error
Handling
We agreed to use anyhow
crate in this project. Please read the docs for anyhow
To unify the practice by default we import both Result
and Context
from anyhow
Others can be imported as well if needed.
#![allow(unused)] fn main() { use anyhow::{Result, Context}; fn might_fail() -> Result<()> { // context adds a `context` to the error. so if another_call fails. I can tell exactly failed when i was doing what another_call().context("failed to do something")?; // <- we use ? to propagate the error unless you need to handle the error differently Ok(()) // we use Ok from std no need to import anyhow::Ok although it's probably the same. } fn might_fail2() -> Result<()> { if fail { // use the bail macro fom anyhow to exit with an error. bail!("failed because fail with set to true"); } } > NOTE: all error messages starts with lowercase. for example it's `failed to ...` not `Failed to ...` }
logs
logging is important to trace the errors that cannot be propagated and also for debug messages that can help spotting a problem. We always gonna use log
crate. as
#![allow(unused)] fn main() { log::debug!(); // for debug messages log::info!(); // info messages }
Note only errors
that can NOT be propagated are logged.
NOTE: All log messages start with lowercase.
Function Signatures
For function inputs (arguments) generic
types are preferred if available over concrete types. This most obvious with string
types. depending on the function behavior
Examples
This is bad:
#![allow(unused)] fn main() { fn call1(key: String); fn call2(key: &str); }
It is preferred to use:
#![allow(unused)] fn main() { // in case function will need to take ownership of the string. fn call1<k: Into<String>>(k: K); // inc ase function will just need to use a reference to the string. fn call2<K: AsRef<str>>(k: K); // this will allow both functions to be callable with `&str`, `String`. }