Skip to content

Latest commit

 

History

History
190 lines (139 loc) · 4.04 KB

File metadata and controls

190 lines (139 loc) · 4.04 KB

Rust Style Guide

Formatting

Use rustfmt to maintain consistent code formatting

cargo fmt

Sort Cargo.toml dependencies alphabetically

Organize imports according to the StdExternalCrate pattern

  1. Standard library
  2. External crates
  3. Symbols from local crate & parent module
  4. Local modules definition
  5. Symbols from local modules (optional)
// Standard library
use std::collections::HashMap;
use std::sync::Arc;
use std::{mem, time};

// External crates
use serde::{Serialize, Deserialize};
use tokio::sync::{oneshot, RwLock};

// Symbols from local crate & parent module
use crate::{common, Node};
use super::Shared;

// Local modules definition
mod config;
mod types;

// Symbols from local modules (optional)
use config::ContextConfig;
use types::BroadcastMessage;

Use module import granularity. This means not grouping imports from the same crate together

NOT ALLOWED:

use core::{
    future::{pending, Future},
    pin::Pin,
    str,
};
use std::{thread, time::Duration};

ALLOWED:

use core::future::{pending, Future};
use core::pin::Pin;
use core::str;
use std::thread;
use std::time::Duration;

Module Organization

We don't use the mod.rs pattern

Instead, export modules from files named according to their context.

For example:

crates/meroctl/src/cli/app.rs

Contains:

mod get;
mod install;
mod list;

And we would have these individual files:

crates/meroctl/src/cli/app/get.rs
crates/meroctl/src/cli/app/install.rs
crates/meroctl/src/cli/app/list.rs

Error Handling

We use the eyre crate extensively in our code, and import it as follows

use eyre::{Result as EyreResult};

Employ maximum caution dealing with panic points (.unwrap(), .expect(..), assert!, panic!, etc)

Only introduce them when you have maximum confidence it will NEVER panic, or if it does, it's a fatal error from which we cannot recover and aborting the node is the best thing to do. In this case, introduce a comment stating why

If unwrapping is absolutely necessary, explain why with a comment

On values that may return errors, use .map_err() to map the error into the appropriate Error type used in that crate/module

Code Efficiency

Try to limit unnecessary clones

Use short-circuiting if statements

// NOT RECOMMENDED:
if some_condition {
    // ... (lots of code)
} else {
    return Err(YourError::Something);
}
// RECOMMENDED:
if !some_condition {
    return Err(YourError::Something);
}
// Continue with main code path...

Use let..else for deep conditionals

When using if let..else with the consequent block extending beyond just a couple of lines and the alternative effectively bails, prefer the let..else syntax to reduce indentation.

// NOT RECOMMENDED:
if let Ok(val) = thing {
    func(val);
    do_ok_1;
    do_ok_2;
    do_ok_3;
} else {
    do_else_1;
    return;
}
// RECOMMENDED:
let Ok(val) = thing else {
    do_else_1;
    return;
};
func(val);
do_ok_1;
do_ok_2;
do_ok_3;

This syntax helps flatten out indentation and keeps short-circuits closer to the condition that triggered them, making code easier to read.

Code Organization

Break functions into smaller parts if they become too large

Place reusable functions in a commons.rs file or similar

Put structs needed by multiple parts of the codebase into primitives files

We try to avoid using fully qualified names, prefer using imports

// NOT RECOMMENDED:
std::fs::File::open("data.txt")

// RECOMMENDED:
use std::fs::File;
File::open("data.txt")

Naming Conventions

  • Types shall be PascalCase
  • Enum variants shall be PascalCase
  • Struct fields shall be snake_case
  • Function and method names shall be snake_case
  • Local variables shall be snake_case
  • Macro names shall be snake_case
  • Constants (consts and immutable statics) shall be SCREAMING_SNAKE_CASE

General Guidelines

Try to use functional Rust patterns when they improve code readability and maintainability