aboutsummaryrefslogtreecommitdiff
path: root/docs/dev
diff options
context:
space:
mode:
Diffstat (limited to 'docs/dev')
-rw-r--r--docs/dev/README.md2
-rw-r--r--docs/dev/style.md216
2 files changed, 153 insertions, 65 deletions
diff --git a/docs/dev/README.md b/docs/dev/README.md
index 55527bab0..dd2bfc493 100644
--- a/docs/dev/README.md
+++ b/docs/dev/README.md
@@ -77,7 +77,7 @@ Notably, this uses the usual `rust-analyzer` binary from `PATH`.
77For this, it is important to have the following in your `settings.json` file: 77For this, it is important to have the following in your `settings.json` file:
78```json 78```json
79{ 79{
80 "rust-analyzer.serverPath": "rust-analyzer" 80 "rust-analyzer.server.path": "rust-analyzer"
81} 81}
82``` 82```
83After I am done with the fix, I use `cargo xtask install --client` to try the new extension for real. 83After I am done with the fix, I use `cargo xtask install --client` to try the new extension for real.
diff --git a/docs/dev/style.md b/docs/dev/style.md
index 58b309379..21330948b 100644
--- a/docs/dev/style.md
+++ b/docs/dev/style.md
@@ -53,6 +53,9 @@ We try to be very conservative with usage of crates.io dependencies.
53Don't use small "helper" crates (exception: `itertools` is allowed). 53Don't use small "helper" crates (exception: `itertools` is allowed).
54If there's some general reusable bit of code you need, consider adding it to the `stdx` crate. 54If there's some general reusable bit of code you need, consider adding it to the `stdx` crate.
55 55
56**Rationale:** keep compile times low, create ecosystem pressure for faster
57compiles, reduce the number of things which might break.
58
56## Commit Style 59## Commit Style
57 60
58We don't have specific rules around git history hygiene. 61We don't have specific rules around git history hygiene.
@@ -66,15 +69,20 @@ Such messages create a lot of duplicate notification traffic during rebases.
66If possible, write commit messages from user's perspective: 69If possible, write commit messages from user's perspective:
67 70
68``` 71```
69# Good 72# GOOD
70Goto definition works inside macros 73Goto definition works inside macros
71 74
72# Not as good 75# BAD
73Use original span for FileId 76Use original span for FileId
74``` 77```
75 78
76This makes it easier to prepare a changelog. 79This makes it easier to prepare a changelog.
77 80
81If 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.
82
83**Rationale:** clean history is potentially useful, but rarely used.
84But many users read changelogs.
85
78## Clippy 86## Clippy
79 87
80We don't enforce Clippy. 88We don't enforce Clippy.
@@ -82,21 +90,16 @@ A number of default lints have high false positive rate.
82Selectively patching false-positives with `allow(clippy)` is considered worse than not using Clippy at all. 90Selectively patching false-positives with `allow(clippy)` is considered worse than not using Clippy at all.
83There's `cargo xtask lint` command which runs a subset of low-FPR lints. 91There's `cargo xtask lint` command which runs a subset of low-FPR lints.
84Careful tweaking of `xtask lint` is welcome. 92Careful tweaking of `xtask lint` is welcome.
85See also [rust-lang/clippy#5537](https://github.com/rust-lang/rust-clippy/issues/5537).
86Of course, applying Clippy suggestions is welcome as long as they indeed improve the code. 93Of course, applying Clippy suggestions is welcome as long as they indeed improve the code.
87 94
95**Rationale:** see [rust-lang/clippy#5537](https://github.com/rust-lang/rust-clippy/issues/5537).
96
88# Code 97# Code
89 98
90## Minimal Tests 99## Minimal Tests
91 100
92Most tests in rust-analyzer start with a snippet of Rust code. 101Most tests in rust-analyzer start with a snippet of Rust code.
93This 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. 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.
94There are many benefits to this:
95
96* less to read or to scroll past
97* easier to understand what exactly is tested
98* less stuff printed during printf-debugging
99* less time to run test
100 103
101It also makes sense to format snippets more compactly (for example, by placing enum definitions like `enum E { Foo, Bar }` on a single line), 104It also makes sense to format snippets more compactly (for example, by placing enum definitions like `enum E { Foo, Bar }` on a single line),
102as long as they are still readable. 105as long as they are still readable.
@@ -111,7 +114,7 @@ When using multiline fixtures, use unindented raw string literals:
111 r#" 114 r#"
112struct S { foo: i32} 115struct S { foo: i32}
113fn main() { 116fn main() {
114 let <|>foo = 92; 117 let $0foo = 92;
115 S { foo } 118 S { foo }
116} 119}
117"#, 120"#,
@@ -125,19 +128,28 @@ fn main() {
125 } 128 }
126``` 129```
127 130
128That way, you can use your editor's "number of selected characters" feature to correlate offsets with test's source code. 131**Rationale:**
132
133There are many benefits to this:
134
135* less to read or to scroll past
136* easier to understand what exactly is tested
137* less stuff printed during printf-debugging
138* less time to run test
139
140Formatting ensures that you can use your editor's "number of selected characters" feature to correlate offsets with test's source code.
129 141
130## Preconditions 142## Function Preconditions
131 143
132Express function preconditions in types and force the caller to provide them (rather than checking in callee): 144Express function preconditions in types and force the caller to provide them (rather than checking in callee):
133 145
134```rust 146```rust
135// Good 147// GOOD
136fn frbonicate(walrus: Walrus) { 148fn frbonicate(walrus: Walrus) {
137 ... 149 ...
138} 150}
139 151
140// Not as good 152// BAD
141fn frobnicate(walrus: Option<Walrus>) { 153fn frobnicate(walrus: Option<Walrus>) {
142 let walrus = match walrus { 154 let walrus = match walrus {
143 Some(it) => it, 155 Some(it) => it,
@@ -147,10 +159,13 @@ fn frobnicate(walrus: Option<Walrus>) {
147} 159}
148``` 160```
149 161
150Avoid preconditions that span across function boundaries: 162**Rationale:** this makes control flow explicit at the call site.
163Call-site has more context, it often happens that the precondition falls out naturally or can be bubbled up higher in the stack.
164
165Avoid splitting precondition check and precondition use across functions:
151 166
152```rust 167```rust
153// Good 168// GOOD
154fn main() { 169fn main() {
155 let s: &str = ...; 170 let s: &str = ...;
156 if let Some(contents) = string_literal_contents(s) { 171 if let Some(contents) = string_literal_contents(s) {
@@ -166,7 +181,7 @@ fn string_literal_contents(s: &str) -> Option<&str> {
166 } 181 }
167} 182}
168 183
169// Not as good 184// BAD
170fn main() { 185fn main() {
171 let s: &str = ...; 186 let s: &str = ...;
172 if is_string_literal(s) { 187 if is_string_literal(s) {
@@ -182,20 +197,29 @@ fn is_string_literal(s: &str) -> bool {
182In the "Not as good" version, the precondition that `1` is a valid char boundary is checked in `is_string_literal` and used in `foo`. 197In the "Not as good" version, the precondition that `1` is a valid char boundary is checked in `is_string_literal` and used in `foo`.
183In the "Good" version, the precondition check and usage are checked in the same block, and then encoded in the types. 198In the "Good" version, the precondition check and usage are checked in the same block, and then encoded in the types.
184 199
200**Rationale:** non-local code properties degrade under change.
201
185When checking a boolean precondition, prefer `if !invariant` to `if negated_invariant`: 202When checking a boolean precondition, prefer `if !invariant` to `if negated_invariant`:
186 203
187```rust 204```rust
188// Good 205// GOOD
189if !(idx < len) { 206if !(idx < len) {
190 return None; 207 return None;
191} 208}
192 209
193// Not as good 210// BAD
194if idx >= len { 211if idx >= len {
195 return None; 212 return None;
196} 213}
197``` 214```
198 215
216**Rationale:** its useful to see the invariant relied upon by the rest of the function clearly spelled out.
217
218## Assertions
219
220Assert liberally.
221Prefer `stdx::assert_never!` to standard `assert!`.
222
199## Getters & Setters 223## Getters & Setters
200 224
201If a field can have any value without breaking invariants, make the field public. 225If a field can have any value without breaking invariants, make the field public.
@@ -211,31 +235,36 @@ struct Person {
211 middle_name: Option<String> 235 middle_name: Option<String>
212} 236}
213 237
214// Good 238// GOOD
215impl Person { 239impl Person {
216 fn first_name(&self) -> &str { self.first_name.as_str() } 240 fn first_name(&self) -> &str { self.first_name.as_str() }
217 fn middle_name(&self) -> Option<&str> { self.middle_name.as_ref() } 241 fn middle_name(&self) -> Option<&str> { self.middle_name.as_ref() }
218} 242}
219 243
220// Not as good 244// BAD
221impl Person { 245impl Person {
222 fn first_name(&self) -> String { self.first_name.clone() } 246 fn first_name(&self) -> String { self.first_name.clone() }
223 fn middle_name(&self) -> &Option<String> { &self.middle_name } 247 fn middle_name(&self) -> &Option<String> { &self.middle_name }
224} 248}
225``` 249```
226 250
251**Rationale:** we don't provide public API, it's cheaper to refactor than to pay getters rent.
252Non-local code properties degrade under change, privacy makes invariant local.
253Borrowed own data discloses irrelevant details about origin of data.
254Irrelevant (neither right nor wrong) things obscure correctness.
255
227## Constructors 256## Constructors
228 257
229Prefer `Default` to zero-argument `new` function 258Prefer `Default` to zero-argument `new` function
230 259
231```rust 260```rust
232// Good 261// GOOD
233#[derive(Default)] 262#[derive(Default)]
234struct Foo { 263struct Foo {
235 bar: Option<Bar> 264 bar: Option<Bar>
236} 265}
237 266
238// Not as good 267// BAD
239struct Foo { 268struct Foo {
240 bar: Option<Bar> 269 bar: Option<Bar>
241} 270}
@@ -249,16 +278,18 @@ impl Foo {
249 278
250Prefer `Default` even it has to be implemented manually. 279Prefer `Default` even it has to be implemented manually.
251 280
281**Rationale:** less typing in the common case, uniformity.
282
252## Functions Over Objects 283## Functions Over Objects
253 284
254Avoid creating "doer" objects. 285Avoid creating "doer" objects.
255That is, objects which are created only to execute a single action. 286That is, objects which are created only to execute a single action.
256 287
257```rust 288```rust
258// Good 289// GOOD
259do_thing(arg1, arg2); 290do_thing(arg1, arg2);
260 291
261// Not as good 292// BAD
262ThingDoer::new(arg1, arg2).do(); 293ThingDoer::new(arg1, arg2).do();
263``` 294```
264 295
@@ -303,16 +334,14 @@ impl ThingDoer {
303} 334}
304``` 335```
305 336
337**Rationale:** not bothering the caller with irrelevant details, not mixing user API with implementor API.
338
306## Avoid Monomorphization 339## Avoid Monomorphization
307 340
308Rust 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*. 341Avoid making a lot of code type parametric, *especially* on the boundaries between crates.
309This allows for exceptionally good performance, but leads to increased compile times.
310Runtime performance obeys 80%/20% rule -- only a small fraction of code is hot.
311Compile time **does not** obey this rule -- all code has to be compiled.
312For this reason, avoid making a lot of code type parametric, *especially* on the boundaries between crates.
313 342
314```rust 343```rust
315// Good 344// GOOD
316fn frbonicate(f: impl FnMut()) { 345fn frbonicate(f: impl FnMut()) {
317 frobnicate_impl(&mut f) 346 frobnicate_impl(&mut f)
318} 347}
@@ -320,7 +349,7 @@ fn frobnicate_impl(f: &mut dyn FnMut()) {
320 // lots of code 349 // lots of code
321} 350}
322 351
323// Not as good 352// BAD
324fn frbonicate(f: impl FnMut()) { 353fn frbonicate(f: impl FnMut()) {
325 // lots of code 354 // lots of code
326} 355}
@@ -329,15 +358,21 @@ fn frbonicate(f: impl FnMut()) {
329Avoid `AsRef` polymorphism, it pays back only for widely used libraries: 358Avoid `AsRef` polymorphism, it pays back only for widely used libraries:
330 359
331```rust 360```rust
332// Good 361// GOOD
333fn frbonicate(f: &Path) { 362fn frbonicate(f: &Path) {
334} 363}
335 364
336// Not as good 365// BAD
337fn frbonicate(f: impl AsRef<Path>) { 366fn frbonicate(f: impl AsRef<Path>) {
338} 367}
339``` 368```
340 369
370**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*.
371This allows for exceptionally good performance, but leads to increased compile times.
372Runtime 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.
374
375
341# Premature Pessimization 376# Premature Pessimization
342 377
343## Avoid Allocations 378## Avoid Allocations
@@ -346,7 +381,7 @@ Avoid writing code which is slower than it needs to be.
346Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly. 381Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly.
347 382
348```rust 383```rust
349// Good 384// GOOD
350use itertools::Itertools; 385use itertools::Itertools;
351 386
352let (first_word, second_word) = match text.split_ascii_whitespace().collect_tuple() { 387let (first_word, second_word) = match text.split_ascii_whitespace().collect_tuple() {
@@ -354,37 +389,40 @@ let (first_word, second_word) = match text.split_ascii_whitespace().collect_tupl
354 None => return, 389 None => return,
355} 390}
356 391
357// Not as good 392// BAD
358let words = text.split_ascii_whitespace().collect::<Vec<_>>(); 393let words = text.split_ascii_whitespace().collect::<Vec<_>>();
359if words.len() != 2 { 394if words.len() != 2 {
360 return 395 return
361} 396}
362``` 397```
363 398
399**Rationale:** not allocating is almost often faster.
400
364## Push Allocations to the Call Site 401## Push Allocations to the Call Site
365 402
366If allocation is inevitable, let the caller allocate the resource: 403If allocation is inevitable, let the caller allocate the resource:
367 404
368```rust 405```rust
369// Good 406// GOOD
370fn frobnicate(s: String) { 407fn frobnicate(s: String) {
371 ... 408 ...
372} 409}
373 410
374// Not as good 411// BAD
375fn frobnicate(s: &str) { 412fn frobnicate(s: &str) {
376 let s = s.to_string(); 413 let s = s.to_string();
377 ... 414 ...
378} 415}
379``` 416```
380 417
381This is better because it reveals the costs. 418**Rationale:** reveals the costs.
382It is also more efficient when the caller already owns the allocation. 419It is also more efficient when the caller already owns the allocation.
383 420
384## Collection types 421## Collection types
385 422
386Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. 423Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`.
387They use a hasher that's slightly faster and using them consistently will reduce code size by some small amount. 424
425**Rationale:** they use a hasher that's significantly faster and using them consistently will reduce code size by some small amount.
388 426
389# Style 427# Style
390 428
@@ -393,6 +431,9 @@ They use a hasher that's slightly faster and using them consistently will reduce
393Separate import groups with blank lines. 431Separate import groups with blank lines.
394Use one `use` per crate. 432Use one `use` per crate.
395 433
434Module declarations come before the imports.
435Order them in "suggested reading order" for a person new to the code base.
436
396```rust 437```rust
397mod x; 438mod x;
398mod y; 439mod y;
@@ -411,46 +452,62 @@ use crate::{}
411use super::{} 452use super::{}
412``` 453```
413 454
414Module declarations come before the imports. 455**Rationale:** consistency.
415Order them in "suggested reading order" for a person new to the code base. 456Reading order is important for new contributors.
457Grouping by crate allows to spot unwanted dependencies easier.
416 458
417## Import Style 459## Import Style
418 460
419Qualify items from `hir` and `ast`. 461Qualify items from `hir` and `ast`.
420 462
421```rust 463```rust
422// Good 464// GOOD
423use syntax::ast; 465use syntax::ast;
424 466
425fn frobnicate(func: hir::Function, strukt: ast::StructDef) {} 467fn frobnicate(func: hir::Function, strukt: ast::Struct) {}
426 468
427// Not as good 469// BAD
428use hir::Function; 470use hir::Function;
429use syntax::ast::StructDef; 471use syntax::ast::Struct;
430 472
431fn frobnicate(func: Function, strukt: StructDef) {} 473fn frobnicate(func: Function, strukt: Struct) {}
432``` 474```
433 475
434Avoid local `use MyEnum::*` imports. 476**Rationale:** avoids name clashes, makes the layer clear at a glance.
435
436Prefer `use crate::foo::bar` to `use super::bar`.
437 477
438When implementing `Debug` or `Display`, import `std::fmt`: 478When implementing traits from `std::fmt` or `std::ops`, import the module:
439 479
440```rust 480```rust
441// Good 481// GOOD
442use std::fmt; 482use std::fmt;
443 483
444impl fmt::Display for RenameError { 484impl fmt::Display for RenameError {
445 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { .. } 485 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { .. }
446} 486}
447 487
448// Not as good 488// BAD
449impl std::fmt::Display for RenameError { 489impl std::fmt::Display for RenameError {
450 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { .. } 490 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { .. }
451} 491}
492
493// BAD
494use std::ops::Deref;
495
496impl Deref for Widget {
497 type Target = str;
498 fn deref(&self) -> &str { .. }
499}
452``` 500```
453 501
502**Rationale:** overall, less typing.
503Makes it clear that a trait is implemented, rather than used.
504
505Avoid local `use MyEnum::*` imports.
506**Rationale:** consistency.
507
508Prefer `use crate::foo::bar` to `use super::bar` or `use self::bar::baz`.
509**Rationale:** consistency, this is the style which works in all cases.
510
454## Order of Items 511## Order of Items
455 512
456Optimize for the reader who sees the file for the first time, and wants to get a general idea about what's going on. 513Optimize for the reader who sees the file for the first time, and wants to get a general idea about what's going on.
@@ -459,7 +516,7 @@ People read things from top to bottom, so place most important things first.
459Specifically, if all items except one are private, always put the non-private item on top. 516Specifically, if all items except one are private, always put the non-private item on top.
460 517
461```rust 518```rust
462// Good 519// GOOD
463pub(crate) fn frobnicate() { 520pub(crate) fn frobnicate() {
464 Helper::act() 521 Helper::act()
465} 522}
@@ -473,7 +530,7 @@ impl Helper {
473 } 530 }
474} 531}
475 532
476// Not as good 533// BAD
477#[derive(Default)] 534#[derive(Default)]
478struct Helper { stuff: i32 } 535struct Helper { stuff: i32 }
479 536
@@ -489,12 +546,11 @@ impl Helper {
489``` 546```
490 547
491If there's a mixture of private and public items, put public items first. 548If there's a mixture of private and public items, put public items first.
492If function bodies are folded in the editor, the source code should read as documentation for the public API.
493 549
494Put `struct`s and `enum`s first, functions and impls last. Order types declarations in top-down manner. 550Put `struct`s and `enum`s first, functions and impls last. Order type declarations in top-down manner.
495 551
496```rust 552```rust
497// Good 553// GOOD
498struct Parent { 554struct Parent {
499 children: Vec<Child> 555 children: Vec<Child>
500} 556}
@@ -507,7 +563,7 @@ impl Parent {
507impl Child { 563impl Child {
508} 564}
509 565
510// Not as good 566// BAD
511struct Child; 567struct Child;
512 568
513impl Child { 569impl Child {
@@ -521,6 +577,9 @@ impl Parent {
521} 577}
522``` 578```
523 579
580**Rationale:** easier to get the sense of the API by visually scanning the file.
581If function bodies are folded in the editor, the source code should read as documentation for the public API.
582
524## Variable Naming 583## Variable Naming
525 584
526Use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)). 585Use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)).
@@ -548,12 +607,14 @@ enum -> enum_
548mod -> module 607mod -> module
549``` 608```
550 609
610**Rationale:** consistency.
611
551## Early Returns 612## Early Returns
552 613
553Do use early returns 614Do use early returns
554 615
555```rust 616```rust
556// Good 617// GOOD
557fn foo() -> Option<Bar> { 618fn foo() -> Option<Bar> {
558 if !condition() { 619 if !condition() {
559 return None; 620 return None;
@@ -562,7 +623,7 @@ fn foo() -> Option<Bar> {
562 Some(...) 623 Some(...)
563} 624}
564 625
565// Not as good 626// BAD
566fn foo() -> Option<Bar> { 627fn foo() -> Option<Bar> {
567 if condition() { 628 if condition() {
568 Some(...) 629 Some(...)
@@ -572,20 +633,47 @@ fn foo() -> Option<Bar> {
572} 633}
573``` 634```
574 635
636**Rationale:** reduce congnitive stack usage.
637
575## Comparisons 638## Comparisons
576 639
577Use `<`/`<=`, avoid `>`/`>=`. 640Use `<`/`<=`, avoid `>`/`>=`.
578Less-then comparisons are more intuitive, they correspond spatially to [real line](https://en.wikipedia.org/wiki/Real_line)
579 641
580```rust 642```rust
581// Good 643// GOOD
582assert!(lo <= x && x <= hi); 644assert!(lo <= x && x <= hi);
583 645
584// Not as good 646// BAD
585assert!(x >= lo && x <= hi>); 647assert!(x >= lo && x <= hi>);
586``` 648```
587 649
650**Rationale:** Less-then comparisons are more intuitive, they correspond spatially to [real line](https://en.wikipedia.org/wiki/Real_line).
651
652
653## Token names
654
655Use `T![foo]` instead of `SyntaxKind::FOO_KW`.
656
657```rust
658// GOOD
659match p.current() {
660 T![true] | T![false] => true,
661 _ => false,
662}
663
664// BAD
665
666match p.current() {
667 SyntaxKind::TRUE_KW | SyntaxKind::FALSE_KW => true,
668 _ => false,
669}
670```
671
672**Rationale:** The macro uses the familiar Rust syntax, avoiding ambiguities like "is this a brace or bracket?".
673
588## Documentation 674## Documentation
589 675
590For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines. 676For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines.
591If the line is too long, you want to split the sentence in two :-) 677If the line is too long, you want to split the sentence in two :-)
678
679**Rationale:** much easier to edit the text and read the diff.