aboutsummaryrefslogtreecommitdiff
path: root/docs/dev/style.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/dev/style.md')
-rw-r--r--docs/dev/style.md154
1 files changed, 143 insertions, 11 deletions
diff --git a/docs/dev/style.md b/docs/dev/style.md
index 21330948b..dd71e3932 100644
--- a/docs/dev/style.md
+++ b/docs/dev/style.md
@@ -6,6 +6,9 @@ Our approach to "clean code" is two-fold:
6It 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. 6It 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.
7Sending small cleanup PRs (like renaming a single local variable) is encouraged. 7Sending small cleanup PRs (like renaming a single local variable) is encouraged.
8 8
9When reviewing pull requests prefer extending this document to leaving
10non-reusable comments on the pull request itself.
11
9# General 12# General
10 13
11## Scale of Changes 14## Scale of Changes
@@ -38,7 +41,7 @@ For the second group, the change would be subjected to quite a bit of scrutiny a
38The new API needs to be right (or at least easy to change later). 41The new API needs to be right (or at least easy to change later).
39The actual implementation doesn't matter that much. 42The actual implementation doesn't matter that much.
40It's very important to minimize the amount of changed lines of code for changes of the second kind. 43It's very important to minimize the amount of changed lines of code for changes of the second kind.
41Often, you start doing a change of the first kind, only to realise that you need to elevate to a change of the second kind. 44Often, you start doing a change of the first kind, only to realize that you need to elevate to a change of the second kind.
42In this case, we'll probably ask you to split API changes into a separate PR. 45In this case, we'll probably ask you to split API changes into a separate PR.
43 46
44Changes of the third group should be pretty rare, so we don't specify any specific process for them. 47Changes of the third group should be pretty rare, so we don't specify any specific process for them.
@@ -99,7 +102,7 @@ Of course, applying Clippy suggestions is welcome as long as they indeed improve
99## Minimal Tests 102## Minimal Tests
100 103
101Most tests in rust-analyzer start with a snippet of Rust code. 104Most tests in rust-analyzer start with a snippet of Rust code.
102This 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. 105These 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.
103 106
104It also makes sense to format snippets more compactly (for example, by placing enum definitions like `enum E { Foo, Bar }` on a single line), 107It also makes sense to format snippets more compactly (for example, by placing enum definitions like `enum E { Foo, Bar }` on a single line),
105as long as they are still readable. 108as long as they are still readable.
@@ -139,13 +142,24 @@ There are many benefits to this:
139 142
140Formatting ensures that you can use your editor's "number of selected characters" feature to correlate offsets with test's source code. 143Formatting ensures that you can use your editor's "number of selected characters" feature to correlate offsets with test's source code.
141 144
145## Marked Tests
146
147Use
148[`mark::hit! / mark::check!`](https://github.com/rust-analyzer/rust-analyzer/blob/71fe719dd5247ed8615641d9303d7ca1aa201c2f/crates/test_utils/src/mark.rs)
149when testing specific conditions.
150Do not place several marks into a single test or condition.
151Do not reuse marks between several tests.
152
153**Rationale:** marks provide an easy way to find the canonical test for each bit of code.
154This makes it much easier to understand.
155
142## Function Preconditions 156## Function Preconditions
143 157
144Express function preconditions in types and force the caller to provide them (rather than checking in callee): 158Express function preconditions in types and force the caller to provide them (rather than checking in callee):
145 159
146```rust 160```rust
147// GOOD 161// GOOD
148fn frbonicate(walrus: Walrus) { 162fn frobnicate(walrus: Walrus) {
149 ... 163 ...
150} 164}
151 165
@@ -213,12 +227,12 @@ if idx >= len {
213} 227}
214``` 228```
215 229
216**Rationale:** its useful to see the invariant relied upon by the rest of the function clearly spelled out. 230**Rationale:** it's useful to see the invariant relied upon by the rest of the function clearly spelled out.
217 231
218## Assertions 232## Assertions
219 233
220Assert liberally. 234Assert liberally.
221Prefer `stdx::assert_never!` to standard `assert!`. 235Prefer `stdx::never!` to standard `assert!`.
222 236
223## Getters & Setters 237## Getters & Setters
224 238
@@ -253,6 +267,20 @@ Non-local code properties degrade under change, privacy makes invariant local.
253Borrowed own data discloses irrelevant details about origin of data. 267Borrowed own data discloses irrelevant details about origin of data.
254Irrelevant (neither right nor wrong) things obscure correctness. 268Irrelevant (neither right nor wrong) things obscure correctness.
255 269
270## Useless Types
271
272More generally, always prefer types on the left
273
274```rust
275// GOOD BAD
276&[T] &Vec<T>
277&str &String
278Option<&T> &Option<T>
279```
280
281**Rationale:** types on the left are strictly more general.
282Even when generality is not required, consistency is important.
283
256## Constructors 284## Constructors
257 285
258Prefer `Default` to zero-argument `new` function 286Prefer `Default` to zero-argument `new` function
@@ -280,6 +308,10 @@ Prefer `Default` even it has to be implemented manually.
280 308
281**Rationale:** less typing in the common case, uniformity. 309**Rationale:** less typing in the common case, uniformity.
282 310
311Use `Vec::new` rather than `vec![]`.
312
313**Rationale:** uniformity, strength reduction.
314
283## Functions Over Objects 315## Functions Over Objects
284 316
285Avoid creating "doer" objects. 317Avoid creating "doer" objects.
@@ -336,13 +368,73 @@ impl ThingDoer {
336 368
337**Rationale:** not bothering the caller with irrelevant details, not mixing user API with implementor API. 369**Rationale:** not bothering the caller with irrelevant details, not mixing user API with implementor API.
338 370
371## Functions with many parameters
372
373Avoid creating functions with many optional or boolean parameters.
374Introduce a `Config` struct instead.
375
376```rust
377// GOOD
378pub struct AnnotationConfig {
379 pub binary_target: bool,
380 pub annotate_runnables: bool,
381 pub annotate_impls: bool,
382}
383
384pub fn annotations(
385 db: &RootDatabase,
386 file_id: FileId,
387 config: AnnotationConfig
388) -> Vec<Annotation> {
389 ...
390}
391
392// BAD
393pub fn annotations(
394 db: &RootDatabase,
395 file_id: FileId,
396 binary_target: bool,
397 annotate_runnables: bool,
398 annotate_impls: bool,
399) -> Vec<Annotation> {
400 ...
401}
402```
403
404**Rationale:** reducing churn.
405If the function has many parameters, they most likely change frequently.
406By packing them into a struct we protect all intermediary functions from changes.
407
408Do not implement `Default` for the `Config` struct, the caller has more context to determine better defaults.
409Do not store `Config` as a part of the `state`, pass it explicitly.
410This gives more flexibility for the caller.
411
412If there is variation not only in the input parameters, but in the return type as well, consider introducing a `Command` type.
413
414```rust
415// MAYBE GOOD
416pub struct Query {
417 pub name: String,
418 pub case_sensitive: bool,
419}
420
421impl Query {
422 pub fn all(self) -> Vec<Item> { ... }
423 pub fn first(self) -> Option<Item> { ... }
424}
425
426// MAYBE BAD
427fn query_all(name: String, case_sensitive: bool) -> Vec<Item> { ... }
428fn query_first(name: String, case_sensitive: bool) -> Option<Item> { ... }
429```
430
339## Avoid Monomorphization 431## Avoid Monomorphization
340 432
341Avoid making a lot of code type parametric, *especially* on the boundaries between crates. 433Avoid making a lot of code type parametric, *especially* on the boundaries between crates.
342 434
343```rust 435```rust
344// GOOD 436// GOOD
345fn frbonicate(f: impl FnMut()) { 437fn frobnicate(f: impl FnMut()) {
346 frobnicate_impl(&mut f) 438 frobnicate_impl(&mut f)
347} 439}
348fn frobnicate_impl(f: &mut dyn FnMut()) { 440fn frobnicate_impl(f: &mut dyn FnMut()) {
@@ -350,7 +442,7 @@ fn frobnicate_impl(f: &mut dyn FnMut()) {
350} 442}
351 443
352// BAD 444// BAD
353fn frbonicate(f: impl FnMut()) { 445fn frobnicate(f: impl FnMut()) {
354 // lots of code 446 // lots of code
355} 447}
356``` 448```
@@ -359,11 +451,11 @@ Avoid `AsRef` polymorphism, it pays back only for widely used libraries:
359 451
360```rust 452```rust
361// GOOD 453// GOOD
362fn frbonicate(f: &Path) { 454fn frobnicate(f: &Path) {
363} 455}
364 456
365// BAD 457// BAD
366fn frbonicate(f: impl AsRef<Path>) { 458fn frobnicate(f: impl AsRef<Path>) {
367} 459}
368``` 460```
369 461
@@ -372,6 +464,14 @@ This allows for exceptionally good performance, but leads to increased compile t
372Runtime performance obeys 80%/20% rule -- only a small fraction of code is hot. 464Runtime performance obeys 80%/20% rule -- only a small fraction of code is hot.
373Compile time **does not** obey this rule -- all code has to be compiled. 465Compile time **does not** obey this rule -- all code has to be compiled.
374 466
467## Appropriate String Types
468
469When interfacing with OS APIs, use `OsString`, even if the original source of data is utf-8 encoded.
470**Rationale:** cleanly delineates the boundary when the data goes into the OS-land.
471
472Use `AbsPathBuf` and `AbsPath` over `std::Path`.
473**Rationale:** rust-analyzer is a long-lived process which handles several projects at the same time.
474It is important not to leak cwd by accident.
375 475
376# Premature Pessimization 476# Premature Pessimization
377 477
@@ -418,12 +518,44 @@ fn frobnicate(s: &str) {
418**Rationale:** reveals the costs. 518**Rationale:** reveals the costs.
419It is also more efficient when the caller already owns the allocation. 519It is also more efficient when the caller already owns the allocation.
420 520
421## Collection types 521## Collection Types
422 522
423Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. 523Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`.
424 524
425**Rationale:** they use a hasher that's significantly faster and using them consistently will reduce code size by some small amount. 525**Rationale:** they use a hasher that's significantly faster and using them consistently will reduce code size by some small amount.
426 526
527## Avoid Intermediate Collections
528
529When writing a recursive function to compute a sets of things, use an accumulator parameter instead of returning a fresh collection.
530Accumulator goes first in the list of arguments.
531
532```rust
533// GOOD
534pub fn reachable_nodes(node: Node) -> FxHashSet<Node> {
535 let mut res = FxHashSet::default();
536 go(&mut res, node);
537 res
538}
539fn go(acc: &mut FxHashSet<Node>, node: Node) {
540 acc.insert(node);
541 for n in node.neighbors() {
542 go(acc, n);
543 }
544}
545
546// BAD
547pub fn reachable_nodes(node: Node) -> FxHashSet<Node> {
548 let mut res = FxHashSet::default();
549 res.insert(node);
550 for n in node.neighbors() {
551 res.extend(reachable_nodes(n));
552 }
553 res
554}
555```
556
557**Rationale:** re-use allocations, accumulator style is more concise for complex cases.
558
427# Style 559# Style
428 560
429## Order of Imports 561## Order of Imports
@@ -633,7 +765,7 @@ fn foo() -> Option<Bar> {
633} 765}
634``` 766```
635 767
636**Rationale:** reduce congnitive stack usage. 768**Rationale:** reduce cognitive stack usage.
637 769
638## Comparisons 770## Comparisons
639 771