Add CONTRIBUTING.md with styling rules

This commit is contained in:
Riccardo Zaglia 2022-09-27 00:52:55 +02:00
parent 86e2c543a8
commit 2334450b62
1 changed files with 74 additions and 0 deletions

74
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,74 @@
# Style
Checklist for code style. This is on top of common Rust styling rules. These rules are not mandatory but I might point them out if not respected in PRs :) -zarik
## Naming
- Respect Rust naming conventions (not respecting this will cause a warning).
- Add useful information in the name, with the exception of indices for iterating.
- Do not put type or scope information in the name.
- Avoid abbreviations.
- Avoid prefixes and suffixes.
- if necessary prefer suffixes rather than prefixes.
- `_ref` or `_mut` suffixes are accepted when you want to put emphasis on that the variable is a mutable reference, and so assigning values has side effects, even if not consumed later. Suffixes are not needed if the variable is only mutable or instead a immutable reference.
- Use `maybe_` prefix if the variable is an `Option` or `Result`. Omit if it's clear from the context. Never use it for parameter or field definitions.
- Shadowing is encouraged.
- If shadowing cannot be used and two variables have similar meaning but different types, suffix the variable with the least useful or least specialized type with its type. Example:
```rust
let myfile_string = "./file.txt";
let myfile = Path::new(my_file_string);
```
- If both directory and file paths are used in the same context, suffix directories with `_dir` and files with `_path`. Suffix file names with `_fname`.
## Top level definitions
- For each file, define in order: private imports, public imports, ffi bindings import, private constants, public constants, private structs, public structs, private top level functions, public top level functions.
### Imports
- Do not leave spaces between imports, only between the private and public import blocks.
- Define imports in alphabetical order (use cargo fmt).
- Group imports using braces when there are common parts of the path.
### Structs
- Prefer adding trait bounds to the impl type parameters instead of struct type parameters
- Define in order: struct, Default impl, custom impl, Drop impl, all in the same module. Do not split the custom impl.
## Spacing
Smartly use empty newlines between blocks of code to improve legibility
- Rust recommends not to specify the return keyword when returning at the end of a function. To put emphasis on the return expression, isolate it with a empty new line just before.
- Inside each block (function, if/else/while/for etc or just braces) make so that there are roughly 2 to 6 blocks of code separated by empty lines for the amount of code that fits in a single screen (long functions that span multiple screens can have way more than 6 block). Check existing codebase for an example.
- Spaces between groups of statements should be done so the groups are similar is size and that each achieves a specific purpose. You should be able to easily describe what the group does in few words, even if you don't comment it (because the meaning should be self evident).
- Do not define variables at the start of the function/block, but do define them at the start of the functional group delimited by spaces.
## Comments
- It's important to use comments when the meaning or inner workings of a piece of code is not clear from the context. Well-named symbols (variables and functions) are often enough. In doubt do use comments.
- Do not add comments about how certain parts of the language/std library work, unless it's about quirks of features.
## Panicking and error handling
- Use of `panic!()` is discouraged
- When matching exhaustively, prefer `unreachable!()` over `panic!()` for certain branches.
- `unwrap()` is discouraged, but prefer `unwrap()` over `expect()` (bubble up the error instead).
- Prefer `.get()` to index a collection rather than `[]`, unless extremely certain it will never index out of bounds.
- Add a `// # Safety` comment before a statement that contains a `unwrap()` or raw indexing, explaining why it should never crash.
- Use `todo!()` to mark unfinished code (it returns `!` and so it helps with the missing return statement).
## Code repetition and maintainability
- Lean towards the DRY rule, without overdoing it.
- Extract a piece of code (in a function or lambda) only when it is used two or more times and it doesn't depend on many parameters.
- Always extract constants for "arbitrary" values (literals) which are chosen with no absolute rule and makes sense to change in the future. Example: time interval between resending discovery packet. Opposite example: number of eyes on a human head (it's always going to be 2, no need to use a constant to change its value in the future :) ).
- Prefer defining constants at the start of the file, even if used locally in a single function.
- Prefer using "complex" types for constants, if the std library allows it. Example: prefer using `Duration` instead of an integer type if the constant represents a time duration. Same with `Path` vs string.
## Structural soundness
- Try to avoid invalid states in the data, using Rust enums. Example: do not use `resumed` + `streaming` boolean variables if the state `resumed == false` + `streaming == true` is invalid. Instead use `enum State { Paused, Resumed, Streaming }`.
- Make full use of pattern matching with `if let` and `while let`, this reduces the use of `unwrap()`.