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