]> git.lizzy.rs Git - rust.git/blob - docs/dev/style.md
Style: use the right string
[rust.git] / docs / dev / style.md
1 Our approach to "clean code" is two-fold:
2
3 * We generally don't block PRs on style changes.
4 * At the same time, all code in rust-analyzer is constantly refactored.
5
6 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.
7 Sending small cleanup PRs (like renaming a single local variable) is encouraged.
8
9 When reviewing pull requests prefer extending this document to leaving
10 non-reusable comments on the pull request itself.
11
12 # General
13
14 ## Scale of Changes
15
16 Everyone knows that it's better to send small & focused pull requests.
17 The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs.
18
19 The main things to keep an eye on are the boundaries between various components.
20 There are three kinds of changes:
21
22 1. Internals of a single component are changed.
23    Specifically, you don't change any `pub` items.
24    A good example here would be an addition of a new assist.
25
26 2. API of a component is expanded.
27    Specifically, you add a new `pub` function which wasn't there before.
28    A good example here would be expansion of assist API, for example, to implement lazy assists or assists groups.
29
30 3. A new dependency between components is introduced.
31    Specifically, you add a `pub use` reexport from another crate or you add a new line to the `[dependencies]` section of `Cargo.toml`.
32    A good example here would be adding reference search capability to the assists crates.
33
34 For the first group, the change is generally merged as long as:
35
36 * it works for the happy case,
37 * it has tests,
38 * it doesn't panic for the unhappy case.
39
40 For the second group, the change would be subjected to quite a bit of scrutiny and iteration.
41 The new API needs to be right (or at least easy to change later).
42 The actual implementation doesn't matter that much.
43 It's very important to minimize the amount of changed lines of code for changes of the second kind.
44 Often, you start doing a change of the first kind, only to realise that you need to elevate to a change of the second kind.
45 In this case, we'll probably ask you to split API changes into a separate PR.
46
47 Changes of the third group should be pretty rare, so we don't specify any specific process for them.
48 That said, adding an innocent-looking `pub use` is a very simple way to break encapsulation, keep an eye on it!
49
50 Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate
51 https://www.tedinski.com/2018/02/06/system-boundaries.html
52
53 ## Crates.io Dependencies
54
55 We try to be very conservative with usage of crates.io dependencies.
56 Don't use small "helper" crates (exception: `itertools` is allowed).
57 If there's some general reusable bit of code you need, consider adding it to the `stdx` crate.
58
59 **Rationale:** keep compile times low, create ecosystem pressure for faster
60 compiles, reduce the number of things which might break.
61
62 ## Commit Style
63
64 We don't have specific rules around git history hygiene.
65 Maintaining clean git history is strongly encouraged, but not enforced.
66 Use rebase workflow, it's OK to rewrite history during PR review process.
67 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.
68
69 Avoid @mentioning people in commit messages and pull request descriptions(they are added to commit message by bors).
70 Such messages create a lot of duplicate notification traffic during rebases.
71
72 If possible, write commit messages from user's perspective:
73
74 ```
75 # GOOD
76 Goto definition works inside macros
77
78 # BAD
79 Use original span for FileId
80 ```
81
82 This makes it easier to prepare a changelog.
83
84 If the change adds a new user-visible functionality, consider recording a GIF with [peek](https://github.com/phw/peek) and pasting it into the PR description.
85
86 **Rationale:** clean history is potentially useful, but rarely used.
87 But many users read changelogs.
88
89 ## Clippy
90
91 We don't enforce Clippy.
92 A number of default lints have high false positive rate.
93 Selectively patching false-positives with `allow(clippy)` is considered worse than not using Clippy at all.
94 There's `cargo xtask lint` command which runs a subset of low-FPR lints.
95 Careful tweaking of `xtask lint` is welcome.
96 Of course, applying Clippy suggestions is welcome as long as they indeed improve the code.
97
98 **Rationale:** see [rust-lang/clippy#5537](https://github.com/rust-lang/rust-clippy/issues/5537).
99
100 # Code
101
102 ## Minimal Tests
103
104 Most tests in rust-analyzer start with a snippet of Rust code.
105 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.
106
107 It also makes sense to format snippets more compactly (for example, by placing enum definitions like `enum E { Foo, Bar }` on a single line),
108 as long as they are still readable.
109
110 When using multiline fixtures, use unindented raw string literals:
111
112 ```rust
113     #[test]
114     fn inline_field_shorthand() {
115         check_assist(
116             inline_local_variable,
117             r#"
118 struct S { foo: i32}
119 fn main() {
120     let $0foo = 92;
121     S { foo }
122 }
123 "#,
124             r#"
125 struct S { foo: i32}
126 fn main() {
127     S { foo: 92 }
128 }
129 "#,
130         );
131     }
132 ```
133
134 **Rationale:**
135
136 There are many benefits to this:
137
138 * less to read or to scroll past
139 * easier to understand what exactly is tested
140 * less stuff printed during printf-debugging
141 * less time to run test
142
143 Formatting ensures that you can use your editor's "number of selected characters" feature to correlate offsets with test's source code.
144
145 ## Function Preconditions
146
147 Express function preconditions in types and force the caller to provide them (rather than checking in callee):
148
149 ```rust
150 // GOOD
151 fn frbonicate(walrus: Walrus) {
152     ...
153 }
154
155 // BAD
156 fn frobnicate(walrus: Option<Walrus>) {
157     let walrus = match walrus {
158         Some(it) => it,
159         None => return,
160     };
161     ...
162 }
163 ```
164
165 **Rationale:** this makes control flow explicit at the call site.
166 Call-site has more context, it often happens that the precondition falls out naturally or can be bubbled up higher in the stack.
167
168 Avoid splitting precondition check and precondition use across functions:
169
170 ```rust
171 // GOOD
172 fn main() {
173     let s: &str = ...;
174     if let Some(contents) = string_literal_contents(s) {
175
176     }
177 }
178
179 fn string_literal_contents(s: &str) -> Option<&str> {
180     if s.starts_with('"') && s.ends_with('"') {
181         Some(&s[1..s.len() - 1])
182     } else {
183         None
184     }
185 }
186
187 // BAD
188 fn main() {
189     let s: &str = ...;
190     if is_string_literal(s) {
191         let contents = &s[1..s.len() - 1];
192     }
193 }
194
195 fn is_string_literal(s: &str) -> bool {
196     s.starts_with('"') && s.ends_with('"')
197 }
198 ```
199
200 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`.
201 In the "Good" version, the precondition check and usage are checked in the same block, and then encoded in the types.
202
203 **Rationale:** non-local code properties degrade under change.
204
205 When checking a boolean precondition, prefer `if !invariant` to `if negated_invariant`:
206
207 ```rust
208 // GOOD
209 if !(idx < len) {
210     return None;
211 }
212
213 // BAD
214 if idx >= len {
215     return None;
216 }
217 ```
218
219 **Rationale:** its useful to see the invariant relied upon by the rest of the function clearly spelled out.
220
221 ## Assertions
222
223 Assert liberally.
224 Prefer `stdx::assert_never!` to standard `assert!`.
225
226 ## Getters & Setters
227
228 If a field can have any value without breaking invariants, make the field public.
229 Conversely, if there is an invariant, document it, enforce it in the "constructor" function, make the field private, and provide a getter.
230 Never provide setters.
231
232 Getters should return borrowed data:
233
234 ```rust
235 struct Person {
236     // Invariant: never empty
237     first_name: String,
238     middle_name: Option<String>
239 }
240
241 // GOOD
242 impl Person {
243     fn first_name(&self) -> &str { self.first_name.as_str() }
244     fn middle_name(&self) -> Option<&str> { self.middle_name.as_ref() }
245 }
246
247 // BAD
248 impl Person {
249     fn first_name(&self) -> String { self.first_name.clone() }
250     fn middle_name(&self) -> &Option<String> { &self.middle_name }
251 }
252 ```
253
254 **Rationale:** we don't provide public API, it's cheaper to refactor than to pay getters rent.
255 Non-local code properties degrade under change, privacy makes invariant local.
256 Borrowed own data discloses irrelevant details about origin of data.
257 Irrelevant (neither right nor wrong) things obscure correctness.
258
259 ## Constructors
260
261 Prefer `Default` to zero-argument `new` function
262
263 ```rust
264 // GOOD
265 #[derive(Default)]
266 struct Foo {
267     bar: Option<Bar>
268 }
269
270 // BAD
271 struct Foo {
272     bar: Option<Bar>
273 }
274
275 impl Foo {
276     fn new() -> Foo {
277         Foo { bar: None }
278     }
279 }
280 ```
281
282 Prefer `Default` even it has to be implemented manually.
283
284 **Rationale:** less typing in the common case, uniformity.
285
286 Use `Vec::new` rather than `vec![]`. **Rationale:** uniformity, strength
287 reduction.
288
289 ## Functions Over Objects
290
291 Avoid creating "doer" objects.
292 That is, objects which are created only to execute a single action.
293
294 ```rust
295 // GOOD
296 do_thing(arg1, arg2);
297
298 // BAD
299 ThingDoer::new(arg1, arg2).do();
300 ```
301
302 Note that this concerns only outward API.
303 When implementing `do_thing`, it might be very useful to create a context object.
304
305 ```rust
306 pub fn do_thing(arg1: Arg1, arg2: Arg2) -> Res {
307     let mut ctx = Ctx { arg1, arg2 }
308     ctx.run()
309 }
310
311 struct Ctx {
312     arg1: Arg1, arg2: Arg2
313 }
314
315 impl Ctx {
316     fn run(self) -> Res {
317         ...
318     }
319 }
320 ```
321
322 The difference is that `Ctx` is an impl detail here.
323
324 Sometimes a middle ground is acceptable if this can save some busywork:
325
326 ```rust
327 ThingDoer::do(arg1, arg2);
328
329 pub struct ThingDoer {
330     arg1: Arg1, arg2: Arg2,
331 }
332
333 impl ThingDoer {
334     pub fn do(arg1: Arg1, arg2: Arg2) -> Res {
335         ThingDoer { arg1, arg2 }.run()
336     }
337     fn run(self) -> Res {
338         ...
339     }
340 }
341 ```
342
343 **Rationale:** not bothering the caller with irrelevant details, not mixing user API with implementor API.
344
345 ## Avoid Monomorphization
346
347 Avoid making a lot of code type parametric, *especially* on the boundaries between crates.
348
349 ```rust
350 // GOOD
351 fn frbonicate(f: impl FnMut()) {
352     frobnicate_impl(&mut f)
353 }
354 fn frobnicate_impl(f: &mut dyn FnMut()) {
355     // lots of code
356 }
357
358 // BAD
359 fn frbonicate(f: impl FnMut()) {
360     // lots of code
361 }
362 ```
363
364 Avoid `AsRef` polymorphism, it pays back only for widely used libraries:
365
366 ```rust
367 // GOOD
368 fn frbonicate(f: &Path) {
369 }
370
371 // BAD
372 fn frbonicate(f: impl AsRef<Path>) {
373 }
374 ```
375
376 **Rationale:** 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*.
377 This allows for exceptionally good performance, but leads to increased compile times.
378 Runtime performance obeys 80%/20% rule -- only a small fraction of code is hot.
379 Compile time **does not** obey this rule -- all code has to be compiled.
380
381 ## Appropriate String Types
382
383 When interfacing with OS APIs, use `OsString`, even if the original source of
384 data is utf-8 encoded. **Rationale:** cleanly delineates the boundary when the
385 data goes into the OS-land.
386
387 Use `AbsPathBuf` and `AbsPath` over `std::Path`. **Rationale:** rust-analyzer is
388 a long-lived process which handles several projects at the same time. It is
389 important not to leak cwd by accident.
390
391 # Premature Pessimization
392
393 ## Avoid Allocations
394
395 Avoid writing code which is slower than it needs to be.
396 Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly.
397
398 ```rust
399 // GOOD
400 use itertools::Itertools;
401
402 let (first_word, second_word) = match text.split_ascii_whitespace().collect_tuple() {
403     Some(it) => it,
404     None => return,
405 }
406
407 // BAD
408 let words = text.split_ascii_whitespace().collect::<Vec<_>>();
409 if words.len() != 2 {
410     return
411 }
412 ```
413
414 **Rationale:** not allocating is almost often faster.
415
416 ## Push Allocations to the Call Site
417
418 If allocation is inevitable, let the caller allocate the resource:
419
420 ```rust
421 // GOOD
422 fn frobnicate(s: String) {
423     ...
424 }
425
426 // BAD
427 fn frobnicate(s: &str) {
428     let s = s.to_string();
429     ...
430 }
431 ```
432
433 **Rationale:** reveals the costs.
434 It is also more efficient when the caller already owns the allocation.
435
436 ## Collection Types
437
438 Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`.
439
440 **Rationale:** they use a hasher that's significantly faster and using them consistently will reduce code size by some small amount.
441
442 ## Avoid Intermediate Collections
443
444 When writing a recursive function to compute a sets of things, use an accumulator parameter instead of returning a fresh collection.
445 Accumulator goes first in the list of arguments.
446
447 ```rust
448 // GOOD
449 pub fn reachable_nodes(node: Node) -> FxHashSet<Node> {
450     let mut res = FxHashSet::default();
451     go(&mut res, node);
452     res
453 }
454 fn go(acc: &mut FxHashSet<Node>, node: Node) {
455     acc.insert(node);
456     for n in node.neighbors() {
457         go(acc, n);
458     }
459 }
460
461 // BAD
462 pub fn reachable_nodes(node: Node) -> FxHashSet<Node> {
463     let mut res = FxHashSet::default();
464     res.insert(node);
465     for n in node.neighbors() {
466         res.extend(reachable_nodes(n));
467     }
468     res
469 }
470 ```
471
472 **Rational:** re-use allocations, accumulator style is more concise for complex cases.
473
474 # Style
475
476 ## Order of Imports
477
478 Separate import groups with blank lines.
479 Use one `use` per crate.
480
481 Module declarations come before the imports.
482 Order them in "suggested reading order" for a person new to the code base.
483
484 ```rust
485 mod x;
486 mod y;
487
488 // First std.
489 use std::{ ... }
490
491 // Second, external crates (both crates.io crates and other rust-analyzer crates).
492 use crate_foo::{ ... }
493 use crate_bar::{ ... }
494
495 // Then current crate.
496 use crate::{}
497
498 // Finally, parent and child modules, but prefer `use crate::`.
499 use super::{}
500 ```
501
502 **Rationale:** consistency.
503 Reading order is important for new contributors.
504 Grouping by crate allows to spot unwanted dependencies easier.
505
506 ## Import Style
507
508 Qualify items from `hir` and `ast`.
509
510 ```rust
511 // GOOD
512 use syntax::ast;
513
514 fn frobnicate(func: hir::Function, strukt: ast::Struct) {}
515
516 // BAD
517 use hir::Function;
518 use syntax::ast::Struct;
519
520 fn frobnicate(func: Function, strukt: Struct) {}
521 ```
522
523 **Rationale:** avoids name clashes, makes the layer clear at a glance.
524
525 When implementing traits from `std::fmt` or `std::ops`, import the module:
526
527 ```rust
528 // GOOD
529 use std::fmt;
530
531 impl fmt::Display for RenameError {
532     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { .. }
533 }
534
535 // BAD
536 impl std::fmt::Display for RenameError {
537     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { .. }
538 }
539
540 // BAD
541 use std::ops::Deref;
542
543 impl Deref for Widget {
544     type Target = str;
545     fn deref(&self) -> &str { .. }
546 }
547 ```
548
549 **Rationale:** overall, less typing.
550 Makes it clear that a trait is implemented, rather than used.
551
552 Avoid local `use MyEnum::*` imports.
553 **Rationale:** consistency.
554
555 Prefer `use crate::foo::bar` to `use super::bar` or `use self::bar::baz`.
556 **Rationale:** consistency, this is the style which works in all cases.
557
558 ## Order of Items
559
560 Optimize for the reader who sees the file for the first time, and wants to get a general idea about what's going on.
561 People read things from top to bottom, so place most important things first.
562
563 Specifically, if all items except one are private, always put the non-private item on top.
564
565 ```rust
566 // GOOD
567 pub(crate) fn frobnicate() {
568     Helper::act()
569 }
570
571 #[derive(Default)]
572 struct Helper { stuff: i32 }
573
574 impl Helper {
575     fn act(&self) {
576
577     }
578 }
579
580 // BAD
581 #[derive(Default)]
582 struct Helper { stuff: i32 }
583
584 pub(crate) fn frobnicate() {
585     Helper::act()
586 }
587
588 impl Helper {
589     fn act(&self) {
590
591     }
592 }
593 ```
594
595 If there's a mixture of private and public items, put public items first.
596
597 Put `struct`s and `enum`s first, functions and impls last. Order type declarations in top-down manner.
598
599 ```rust
600 // GOOD
601 struct Parent {
602     children: Vec<Child>
603 }
604
605 struct Child;
606
607 impl Parent {
608 }
609
610 impl Child {
611 }
612
613 // BAD
614 struct Child;
615
616 impl Child {
617 }
618
619 struct Parent {
620     children: Vec<Child>
621 }
622
623 impl Parent {
624 }
625 ```
626
627 **Rationale:** easier to get the sense of the API by visually scanning the file.
628 If function bodies are folded in the editor, the source code should read as documentation for the public API.
629
630 ## Variable Naming
631
632 Use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)).
633 The default name is a lowercased name of the type: `global_state: GlobalState`.
634 Avoid ad-hoc acronyms and contractions, but use the ones that exist consistently (`db`, `ctx`, `acc`).
635 Prefer American spelling (color, behavior).
636
637 Default names:
638
639 * `res` -- "result of the function" local variable
640 * `it` -- I don't really care about the name
641 * `n_foo` -- number of foos
642 * `foo_idx` -- index of `foo`
643
644 Many names in rust-analyzer conflict with keywords.
645 We use mangled names instead of `r#ident` syntax:
646
647 ```
648 struct -> strukt
649 crate  -> krate
650 impl   -> imp
651 trait  -> trait_
652 fn     -> func
653 enum   -> enum_
654 mod    -> module
655 ```
656
657 **Rationale:** consistency.
658
659 ## Early Returns
660
661 Do use early returns
662
663 ```rust
664 // GOOD
665 fn foo() -> Option<Bar> {
666     if !condition() {
667         return None;
668     }
669
670     Some(...)
671 }
672
673 // BAD
674 fn foo() -> Option<Bar> {
675     if condition() {
676         Some(...)
677     } else {
678         None
679     }
680 }
681 ```
682
683 **Rationale:** reduce congnitive stack usage.
684
685 ## Comparisons
686
687 Use `<`/`<=`, avoid `>`/`>=`.
688
689 ```rust
690 // GOOD
691 assert!(lo <= x && x <= hi);
692
693 // BAD
694 assert!(x >= lo && x <= hi>);
695 ```
696
697 **Rationale:** Less-then comparisons are more intuitive, they correspond spatially to [real line](https://en.wikipedia.org/wiki/Real_line).
698
699
700 ## Token names
701
702 Use `T![foo]` instead of `SyntaxKind::FOO_KW`.
703
704 ```rust
705 // GOOD
706 match p.current() {
707     T![true] | T![false] => true,
708     _ => false,
709 }
710
711 // BAD
712
713 match p.current() {
714     SyntaxKind::TRUE_KW | SyntaxKind::FALSE_KW => true,
715     _ => false,
716 }
717 ```
718
719 **Rationale:** The macro uses the familiar Rust syntax, avoiding ambiguities like "is this a brace or bracket?".
720
721 ## Documentation
722
723 For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines.
724 If the line is too long, you want to split the sentence in two :-)
725
726 **Rationale:** much easier to edit the text and read the diff.