id
in an entity data structure or i
for a simple iteration.*.md
files), configs and everything else.rustfmt
: Configure IDE to run rustfmt
/cargo fmt
on file save to ensure formatting rules are maintained across any file changes. In some cases a rustfmt.toml
can be helpful to add some extra rules, but don’t change the rules that are established practices in ecosystem (like naming conventions)
Cargo.toml
and if you submit changes where new dependency is added out-of-order you're introducing unnecessary entropy and irritate maintainers.Create custom error types by defining an enumeration with variants for each possible error. Derive them with something like thiserror
for convenient implementation of std::error::Error
.
anyhow
: The anyhow
crate is helpful in application code (not in libraries!)
Except in test code, the use of .unwrap()
is strictly forbidden. Use .expect()
instead.
Appropriate use of .expect()
is when you expect the condition to always hold true.
Because of that, message you put into .expect()
must convince reader of the code why that will always be the case and NEVER panic. In rare circumstances panicking may be the preferred outcome. In this case, you must convince the reviewer. There are rare exceptions to this such as block numbers that never exceed u32
in Subspace, but even those can be avoided with wrapper data structures.
People sometimes put a message that will be printed by compiler when condition doesn't hold true, but that isn't the correct use of message in .expect()
.
This also extends to wider design considerations of generally not using .expect()
when local context (function or struct) can't guarantee invariants that are necessary for .expect()
to never fail.
If you can't guarantee that, it is likely that error must be propagated up to the level that can guarantee invariants and use .expect()
there if appropriate.
Also richer wrapper data structures that hold internal invariants often help avoiding .expect()
or error handling in the first place.
Correct usage of .expect()
:
// Statically guaranteed to be correct, error handling code will not
// even be present in the executable in this particular case
NonZeroUsize::new(1_000_000).expect("Not zero; qed")
Indexing into slices and similar data structures with []
should be avoided and replaced with .get()
or similar methods with explicit handling of cases where the element doesn't exist, otherwise this is a source of implicit panics. In most cases prefer idiomatic Rust patterns to avoid manual indexing at all.
The same goes for mathematical operations, especially in runtime logic. In most cases use explicit checked, wrapping, or saturating math. Don’t use saturating math just to make things compile if business logic doesn't expect overflow to ever happen. Use checked math with .expect()
in such cases or return an error.
Unsafe code is to be avoided except in special cases. If you can't make code compile and decide to use unsafe
to bypass the compiler, chances are you're doing it wrong and should ask for help to avoid unsafe
. When you do use unsafe
for valid reasons, just like with .expect()
, you need to provide a proof convincing the reviewer that it is safe to do so and can't be exploited with public API from safe Rust, this is done with a comment before unsafe
starting with // SAFETY:
.
Correct use of unsafe
:
#[repr(transparent)]
pub struct PieceArray([u8; Piece::SIZE]);
pub struct Piece(Box<PieceArray>);
impl Decode for Piece {
fn decode<I: Input>(input: &mut I) -> Result<Self, parity_scale_codec::Error> {
let piece = parity_scale_codec::decode_vec_with_len::<u8, _>(input, Self::SIZE)
.map_err(|error| error.chain("Could not decode `Piece.0`"))?;
let mut piece = mem::ManuallyDrop::new(piece);
// SAFETY: Original memory is not dropped and guaranteed to be allocated
let piece = unsafe { Box::from_raw(piece.as_mut_ptr() as *mut PieceArray) };
Ok(Piece(piece))
}
}
impl Piece {
/// Size of a piece (in bytes).
pub const SIZE: usize = Record::SIZE + RecordCommitment::SIZE + RecordWitness::SIZE;
}
cargo check
can be used as a faster alternative to cargo build
that produces most of the same errors, but you should prefer cargo clippy
that will do the same stuff cargo check
does + extra lintscargo clippy --all-targets
without a single warning if at all possible, use it all the time BEFORE committing code