mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
Reorg style
This commit is contained in:
parent
2aa46034c2
commit
fdf2f6226b
1 changed files with 186 additions and 181 deletions
|
@ -6,7 +6,9 @@ Our approach to "clean code" is two-fold:
|
||||||
It is explicitly OK for a reviewer to flag only some nits in the PR, and then send a follow-up cleanup PR for things which are easier to explain by example, cc-ing the original author.
|
It is explicitly OK for a reviewer to flag only some nits in the PR, and then send a follow-up cleanup PR for things which are easier to explain by example, cc-ing the original author.
|
||||||
Sending small cleanup PRs (like renaming a single local variable) is encouraged.
|
Sending small cleanup PRs (like renaming a single local variable) is encouraged.
|
||||||
|
|
||||||
# Scale of Changes
|
# General
|
||||||
|
|
||||||
|
## Scale of Changes
|
||||||
|
|
||||||
Everyone knows that it's better to send small & focused pull requests.
|
Everyone knows that it's better to send small & focused pull requests.
|
||||||
The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs.
|
The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs.
|
||||||
|
@ -45,13 +47,35 @@ That said, adding an innocent-looking `pub use` is a very simple way to break en
|
||||||
Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate
|
Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate
|
||||||
https://www.tedinski.com/2018/02/06/system-boundaries.html
|
https://www.tedinski.com/2018/02/06/system-boundaries.html
|
||||||
|
|
||||||
# Crates.io Dependencies
|
## Crates.io Dependencies
|
||||||
|
|
||||||
We try to be very conservative with usage of crates.io dependencies.
|
We try to be very conservative with usage of crates.io dependencies.
|
||||||
Don't use small "helper" crates (exception: `itertools` is allowed).
|
Don't use small "helper" crates (exception: `itertools` is allowed).
|
||||||
If there's some general reusable bit of code you need, consider adding it to the `stdx` crate.
|
If there's some general reusable bit of code you need, consider adding it to the `stdx` crate.
|
||||||
|
|
||||||
# Minimal Tests
|
## Commit Style
|
||||||
|
|
||||||
|
We don't have specific rules around git history hygiene.
|
||||||
|
Maintaining clean git history is strongly encouraged, but not enforced.
|
||||||
|
Use rebase workflow, it's OK to rewrite history during PR review process.
|
||||||
|
After you are happy with the state of the code, please use [interactive rebase](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) to squash fixup commits.
|
||||||
|
|
||||||
|
Avoid @mentioning people in commit messages and pull request descriptions(they are added to commit message by bors).
|
||||||
|
Such messages create a lot of duplicate notification traffic during rebases.
|
||||||
|
|
||||||
|
## Clippy
|
||||||
|
|
||||||
|
We don't enforce Clippy.
|
||||||
|
A number of default lints have high false positive rate.
|
||||||
|
Selectively patching false-positives with `allow(clippy)` is considered worse than not using Clippy at all.
|
||||||
|
There's `cargo xtask lint` command which runs a subset of low-FPR lints.
|
||||||
|
Careful tweaking of `xtask lint` is welcome.
|
||||||
|
See also [rust-lang/clippy#5537](https://github.com/rust-lang/rust-clippy/issues/5537).
|
||||||
|
Of course, applying Clippy suggestions is welcome as long as they indeed improve the code.
|
||||||
|
|
||||||
|
# Code
|
||||||
|
|
||||||
|
## Minimal Tests
|
||||||
|
|
||||||
Most tests in rust-analyzer start with a snippet of Rust code.
|
Most tests in rust-analyzer start with a snippet of Rust code.
|
||||||
This snippets should be minimal -- if you copy-paste a snippet of real code into the tests, make sure to remove everything which could be removed.
|
This snippets should be minimal -- if you copy-paste a snippet of real code into the tests, make sure to remove everything which could be removed.
|
||||||
|
@ -65,119 +89,7 @@ There are many benefits to this:
|
||||||
It also makes sense to format snippets more compactly (for example, by placing enum definitions like `enum E { Foo, Bar }` on a single line),
|
It also makes sense to format snippets more compactly (for example, by placing enum definitions like `enum E { Foo, Bar }` on a single line),
|
||||||
as long as they are still readable.
|
as long as they are still readable.
|
||||||
|
|
||||||
# Order of Imports
|
## Preconditions
|
||||||
|
|
||||||
Separate import groups with blank lines.
|
|
||||||
Use one `use` per crate.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
mod x;
|
|
||||||
mod y;
|
|
||||||
|
|
||||||
// First std.
|
|
||||||
use std::{ ... }
|
|
||||||
|
|
||||||
// Second, external crates (both crates.io crates and other rust-analyzer crates).
|
|
||||||
use crate_foo::{ ... }
|
|
||||||
use crate_bar::{ ... }
|
|
||||||
|
|
||||||
// Then current crate.
|
|
||||||
use crate::{}
|
|
||||||
|
|
||||||
// Finally, parent and child modules, but prefer `use crate::`.
|
|
||||||
use super::{}
|
|
||||||
```
|
|
||||||
|
|
||||||
Module declarations come before the imports.
|
|
||||||
Order them in "suggested reading order" for a person new to the code base.
|
|
||||||
|
|
||||||
# Import Style
|
|
||||||
|
|
||||||
Qualify items from `hir` and `ast`.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Good
|
|
||||||
use syntax::ast;
|
|
||||||
|
|
||||||
fn frobnicate(func: hir::Function, strukt: ast::StructDef) {}
|
|
||||||
|
|
||||||
// Not as good
|
|
||||||
use hir::Function;
|
|
||||||
use syntax::ast::StructDef;
|
|
||||||
|
|
||||||
fn frobnicate(func: Function, strukt: StructDef) {}
|
|
||||||
```
|
|
||||||
|
|
||||||
Avoid local `use MyEnum::*` imports.
|
|
||||||
|
|
||||||
Prefer `use crate::foo::bar` to `use super::bar`.
|
|
||||||
|
|
||||||
When implementing `Debug` or `Display`, import `std::fmt`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Good
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
impl fmt::Display for RenameError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { .. }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not as good
|
|
||||||
impl std::fmt::Display for RenameError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { .. }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Order of Items
|
|
||||||
|
|
||||||
Optimize for the reader who sees the file for the first time, and wants to get a general idea about what's going on.
|
|
||||||
People read things from top to bottom, so place most important things first.
|
|
||||||
|
|
||||||
Specifically, if all items except one are private, always put the non-private item on top.
|
|
||||||
|
|
||||||
Put `struct`s and `enum`s first, functions and impls last.
|
|
||||||
|
|
||||||
Do
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Good
|
|
||||||
struct Foo {
|
|
||||||
bars: Vec<Bar>
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Bar;
|
|
||||||
```
|
|
||||||
|
|
||||||
rather than
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Not as good
|
|
||||||
struct Bar;
|
|
||||||
|
|
||||||
struct Foo {
|
|
||||||
bars: Vec<Bar>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Variable Naming
|
|
||||||
|
|
||||||
Use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)).
|
|
||||||
The default name is a lowercased name of the type: `global_state: GlobalState`.
|
|
||||||
Avoid ad-hoc acronyms and contractions, but use the ones that exist consistently (`db`, `ctx`, `acc`).
|
|
||||||
|
|
||||||
Default names:
|
|
||||||
|
|
||||||
* `res` -- "result of the function" local variable
|
|
||||||
* `it` -- I don't really care about the name
|
|
||||||
* `n_foo` -- number of foos
|
|
||||||
* `foo_idx` -- index of `foo`
|
|
||||||
|
|
||||||
# Collection types
|
|
||||||
|
|
||||||
Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`.
|
|
||||||
They use a hasher that's slightly faster and using them consistently will reduce code size by some small amount.
|
|
||||||
|
|
||||||
# Preconditions
|
|
||||||
|
|
||||||
Express function preconditions in types and force the caller to provide them (rather than checking in callee):
|
Express function preconditions in types and force the caller to provide them (rather than checking in callee):
|
||||||
|
|
||||||
|
@ -199,7 +111,6 @@ fn frobnicate(walrus: Option<Walrus>) {
|
||||||
|
|
||||||
Avoid preconditions that span across function boundaries:
|
Avoid preconditions that span across function boundaries:
|
||||||
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Good
|
// Good
|
||||||
fn string_literal_contents(s: &str) -> Option<&str> {
|
fn string_literal_contents(s: &str) -> Option<&str> {
|
||||||
|
@ -233,31 +144,7 @@ fn foo() {
|
||||||
In the "Not as good" version, the precondition that `1` is a valid char boundary is checked in `is_string_literal` and used in `foo`.
|
In the "Not as good" version, the precondition that `1` is a valid char boundary is checked in `is_string_literal` and used in `foo`.
|
||||||
In the "Good" version, the precondition check and usage are checked in the same block, and then encoded in the types.
|
In the "Good" version, the precondition check and usage are checked in the same block, and then encoded in the types.
|
||||||
|
|
||||||
# Early Returns
|
## Getters & Setters
|
||||||
|
|
||||||
Do use early returns
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Good
|
|
||||||
fn foo() -> Option<Bar> {
|
|
||||||
if !condition() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not as good
|
|
||||||
fn foo() -> Option<Bar> {
|
|
||||||
if condition() {
|
|
||||||
Some(...)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
# Getters & Setters
|
|
||||||
|
|
||||||
If a field can have any value without breaking invariants, make the field public.
|
If a field can have any value without breaking invariants, make the field public.
|
||||||
Conversely, if there is an invariant, document it, enforce it in the "constructor" function, make the field private, and provide a getter.
|
Conversely, if there is an invariant, document it, enforce it in the "constructor" function, make the field private, and provide a getter.
|
||||||
|
@ -285,6 +172,40 @@ impl Person {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Avoid Monomorphization
|
||||||
|
|
||||||
|
Rust uses monomorphization to compile generic code, meaning that for each instantiation of a generic functions with concrete types, the function is compiled afresh, *per crate*.
|
||||||
|
This allows for exceptionally good performance, but leads to increased compile times.
|
||||||
|
Runtime performance obeys 80%/20% rule -- only a small fraction of code is hot.
|
||||||
|
Compile time **does not** obey this rule -- all code has to be compiled.
|
||||||
|
For this reason, avoid making a lot of code type parametric, *especially* on the boundaries between crates.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Good
|
||||||
|
fn frbonicate(f: impl FnMut()) {
|
||||||
|
frobnicate_impl(&mut f)
|
||||||
|
}
|
||||||
|
fn frobnicate_impl(f: &mut dyn FnMut()) {
|
||||||
|
// lots of code
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not as good
|
||||||
|
fn frbonicate(f: impl FnMut()) {
|
||||||
|
// lots of code
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Avoid `AsRef` polymorphism, it pays back only for widely used libraries:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Good
|
||||||
|
fn frbonicate(f: &Path) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not as good
|
||||||
|
fn frbonicate(f: impl AsRef<Path>) {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# Premature Pessimization
|
# Premature Pessimization
|
||||||
|
|
||||||
|
@ -322,62 +243,146 @@ fn frobnicate(s: &str) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
# Avoid Monomorphization
|
## Collection types
|
||||||
|
|
||||||
Rust uses monomorphization to compile generic code, meaning that for each instantiation of a generic functions with concrete types, the function is compiled afresh, *per crate*.
|
Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`.
|
||||||
This allows for exceptionally good performance, but leads to increased compile times.
|
They use a hasher that's slightly faster and using them consistently will reduce code size by some small amount.
|
||||||
Runtime performance obeys 80%/20% rule -- only a small fraction of code is hot.
|
|
||||||
Compile time **does not** obey this rule -- all code has to be compiled.
|
# Style
|
||||||
For this reason, avoid making a lot of code type parametric, *especially* on the boundaries between crates.
|
|
||||||
|
## Order of Imports
|
||||||
|
|
||||||
|
Separate import groups with blank lines.
|
||||||
|
Use one `use` per crate.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
mod x;
|
||||||
|
mod y;
|
||||||
|
|
||||||
|
// First std.
|
||||||
|
use std::{ ... }
|
||||||
|
|
||||||
|
// Second, external crates (both crates.io crates and other rust-analyzer crates).
|
||||||
|
use crate_foo::{ ... }
|
||||||
|
use crate_bar::{ ... }
|
||||||
|
|
||||||
|
// Then current crate.
|
||||||
|
use crate::{}
|
||||||
|
|
||||||
|
// Finally, parent and child modules, but prefer `use crate::`.
|
||||||
|
use super::{}
|
||||||
|
```
|
||||||
|
|
||||||
|
Module declarations come before the imports.
|
||||||
|
Order them in "suggested reading order" for a person new to the code base.
|
||||||
|
|
||||||
|
## Import Style
|
||||||
|
|
||||||
|
Qualify items from `hir` and `ast`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Good
|
// Good
|
||||||
fn frbonicate(f: impl FnMut()) {
|
use syntax::ast;
|
||||||
frobnicate_impl(&mut f)
|
|
||||||
}
|
fn frobnicate(func: hir::Function, strukt: ast::StructDef) {}
|
||||||
fn frobnicate_impl(f: &mut dyn FnMut()) {
|
|
||||||
// lots of code
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not as good
|
// Not as good
|
||||||
fn frbonicate(f: impl FnMut()) {
|
use hir::Function;
|
||||||
// lots of code
|
use syntax::ast::StructDef;
|
||||||
}
|
|
||||||
|
fn frobnicate(func: Function, strukt: StructDef) {}
|
||||||
```
|
```
|
||||||
|
|
||||||
Avoid `AsRef` polymorphism, it pays back only for widely used libraries:
|
Avoid local `use MyEnum::*` imports.
|
||||||
|
|
||||||
|
Prefer `use crate::foo::bar` to `use super::bar`.
|
||||||
|
|
||||||
|
When implementing `Debug` or `Display`, import `std::fmt`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Good
|
// Good
|
||||||
fn frbonicate(f: &Path) {
|
use std::fmt;
|
||||||
|
|
||||||
|
impl fmt::Display for RenameError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { .. }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not as good
|
// Not as good
|
||||||
fn frbonicate(f: impl AsRef<Path>) {
|
impl std::fmt::Display for RenameError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { .. }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
# Documentation
|
## Order of Items
|
||||||
|
|
||||||
|
Optimize for the reader who sees the file for the first time, and wants to get a general idea about what's going on.
|
||||||
|
People read things from top to bottom, so place most important things first.
|
||||||
|
|
||||||
|
Specifically, if all items except one are private, always put the non-private item on top.
|
||||||
|
|
||||||
|
Put `struct`s and `enum`s first, functions and impls last.
|
||||||
|
|
||||||
|
Do
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Good
|
||||||
|
struct Foo {
|
||||||
|
bars: Vec<Bar>
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Bar;
|
||||||
|
```
|
||||||
|
|
||||||
|
rather than
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Not as good
|
||||||
|
struct Bar;
|
||||||
|
|
||||||
|
struct Foo {
|
||||||
|
bars: Vec<Bar>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variable Naming
|
||||||
|
|
||||||
|
Use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)).
|
||||||
|
The default name is a lowercased name of the type: `global_state: GlobalState`.
|
||||||
|
Avoid ad-hoc acronyms and contractions, but use the ones that exist consistently (`db`, `ctx`, `acc`).
|
||||||
|
|
||||||
|
Default names:
|
||||||
|
|
||||||
|
* `res` -- "result of the function" local variable
|
||||||
|
* `it` -- I don't really care about the name
|
||||||
|
* `n_foo` -- number of foos
|
||||||
|
* `foo_idx` -- index of `foo`
|
||||||
|
|
||||||
|
|
||||||
|
## Early Returns
|
||||||
|
|
||||||
|
Do use early returns
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Good
|
||||||
|
fn foo() -> Option<Bar> {
|
||||||
|
if !condition() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not as good
|
||||||
|
fn foo() -> Option<Bar> {
|
||||||
|
if condition() {
|
||||||
|
Some(...)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines.
|
For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines.
|
||||||
If the line is too long, you want to split the sentence in two :-)
|
If the line is too long, you want to split the sentence in two :-)
|
||||||
|
|
||||||
# Commit Style
|
|
||||||
|
|
||||||
We don't have specific rules around git history hygiene.
|
|
||||||
Maintaining clean git history is strongly encouraged, but not enforced.
|
|
||||||
Use rebase workflow, it's OK to rewrite history during PR review process.
|
|
||||||
After you are happy with the state of the code, please use [interactive rebase](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) to squash fixup commits.
|
|
||||||
|
|
||||||
Avoid @mentioning people in commit messages and pull request descriptions(they are added to commit message by bors).
|
|
||||||
Such messages create a lot of duplicate notification traffic during rebases.
|
|
||||||
|
|
||||||
# Clippy
|
|
||||||
|
|
||||||
We don't enforce Clippy.
|
|
||||||
A number of default lints have high false positive rate.
|
|
||||||
Selectively patching false-positives with `allow(clippy)` is considered worse than not using Clippy at all.
|
|
||||||
There's `cargo xtask lint` command which runs a subset of low-FPR lints.
|
|
||||||
Careful tweaking of `xtask lint` is welcome.
|
|
||||||
See also [rust-lang/clippy#5537](https://github.com/rust-lang/rust-clippy/issues/5537).
|
|
||||||
Of course, applying Clippy suggestions is welcome as long as they indeed improve the code.
|
|
||||||
|
|
Loading…
Reference in a new issue