diff options
Diffstat (limited to 'docs/dev/style.md')
-rw-r--r-- | docs/dev/style.md | 154 |
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: | |||
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. | 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. | 7 | Sending small cleanup PRs (like renaming a single local variable) is encouraged. |
8 | 8 | ||
9 | When reviewing pull requests prefer extending this document to leaving | ||
10 | non-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 | |||
38 | The new API needs to be right (or at least easy to change later). | 41 | The new API needs to be right (or at least easy to change later). |
39 | The actual implementation doesn't matter that much. | 42 | The actual implementation doesn't matter that much. |
40 | It's very important to minimize the amount of changed lines of code for changes of the second kind. | 43 | It's very important to minimize the amount of changed lines of code for changes of the second kind. |
41 | 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. | 44 | Often, you start doing a change of the first kind, only to realize that you need to elevate to a change of the second kind. |
42 | In this case, we'll probably ask you to split API changes into a separate PR. | 45 | In this case, we'll probably ask you to split API changes into a separate PR. |
43 | 46 | ||
44 | Changes of the third group should be pretty rare, so we don't specify any specific process for them. | 47 | Changes 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 | ||
101 | Most tests in rust-analyzer start with a snippet of Rust code. | 104 | Most tests in rust-analyzer start with a snippet of Rust code. |
102 | 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. | 105 | These 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 | ||
104 | It also makes sense to format snippets more compactly (for example, by placing enum definitions like `enum E { Foo, Bar }` on a single line), | 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), |
105 | as long as they are still readable. | 108 | as long as they are still readable. |
@@ -139,13 +142,24 @@ There are many benefits to this: | |||
139 | 142 | ||
140 | Formatting ensures that you can use your editor's "number of selected characters" feature to correlate offsets with test's source code. | 143 | Formatting 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 | |||
147 | Use | ||
148 | [`mark::hit! / mark::check!`](https://github.com/rust-analyzer/rust-analyzer/blob/71fe719dd5247ed8615641d9303d7ca1aa201c2f/crates/test_utils/src/mark.rs) | ||
149 | when testing specific conditions. | ||
150 | Do not place several marks into a single test or condition. | ||
151 | Do not reuse marks between several tests. | ||
152 | |||
153 | **Rationale:** marks provide an easy way to find the canonical test for each bit of code. | ||
154 | This makes it much easier to understand. | ||
155 | |||
142 | ## Function Preconditions | 156 | ## Function Preconditions |
143 | 157 | ||
144 | Express function preconditions in types and force the caller to provide them (rather than checking in callee): | 158 | Express 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 |
148 | fn frbonicate(walrus: Walrus) { | 162 | fn 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 | ||
220 | Assert liberally. | 234 | Assert liberally. |
221 | Prefer `stdx::assert_never!` to standard `assert!`. | 235 | Prefer `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. | |||
253 | Borrowed own data discloses irrelevant details about origin of data. | 267 | Borrowed own data discloses irrelevant details about origin of data. |
254 | Irrelevant (neither right nor wrong) things obscure correctness. | 268 | Irrelevant (neither right nor wrong) things obscure correctness. |
255 | 269 | ||
270 | ## Useless Types | ||
271 | |||
272 | More generally, always prefer types on the left | ||
273 | |||
274 | ```rust | ||
275 | // GOOD BAD | ||
276 | &[T] &Vec<T> | ||
277 | &str &String | ||
278 | Option<&T> &Option<T> | ||
279 | ``` | ||
280 | |||
281 | **Rationale:** types on the left are strictly more general. | ||
282 | Even when generality is not required, consistency is important. | ||
283 | |||
256 | ## Constructors | 284 | ## Constructors |
257 | 285 | ||
258 | Prefer `Default` to zero-argument `new` function | 286 | Prefer `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 | ||
311 | Use `Vec::new` rather than `vec![]`. | ||
312 | |||
313 | **Rationale:** uniformity, strength reduction. | ||
314 | |||
283 | ## Functions Over Objects | 315 | ## Functions Over Objects |
284 | 316 | ||
285 | Avoid creating "doer" objects. | 317 | Avoid 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 | |||
373 | Avoid creating functions with many optional or boolean parameters. | ||
374 | Introduce a `Config` struct instead. | ||
375 | |||
376 | ```rust | ||
377 | // GOOD | ||
378 | pub struct AnnotationConfig { | ||
379 | pub binary_target: bool, | ||
380 | pub annotate_runnables: bool, | ||
381 | pub annotate_impls: bool, | ||
382 | } | ||
383 | |||
384 | pub fn annotations( | ||
385 | db: &RootDatabase, | ||
386 | file_id: FileId, | ||
387 | config: AnnotationConfig | ||
388 | ) -> Vec<Annotation> { | ||
389 | ... | ||
390 | } | ||
391 | |||
392 | // BAD | ||
393 | pub 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. | ||
405 | If the function has many parameters, they most likely change frequently. | ||
406 | By packing them into a struct we protect all intermediary functions from changes. | ||
407 | |||
408 | Do not implement `Default` for the `Config` struct, the caller has more context to determine better defaults. | ||
409 | Do not store `Config` as a part of the `state`, pass it explicitly. | ||
410 | This gives more flexibility for the caller. | ||
411 | |||
412 | If 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 | ||
416 | pub struct Query { | ||
417 | pub name: String, | ||
418 | pub case_sensitive: bool, | ||
419 | } | ||
420 | |||
421 | impl Query { | ||
422 | pub fn all(self) -> Vec<Item> { ... } | ||
423 | pub fn first(self) -> Option<Item> { ... } | ||
424 | } | ||
425 | |||
426 | // MAYBE BAD | ||
427 | fn query_all(name: String, case_sensitive: bool) -> Vec<Item> { ... } | ||
428 | fn query_first(name: String, case_sensitive: bool) -> Option<Item> { ... } | ||
429 | ``` | ||
430 | |||
339 | ## Avoid Monomorphization | 431 | ## Avoid Monomorphization |
340 | 432 | ||
341 | Avoid making a lot of code type parametric, *especially* on the boundaries between crates. | 433 | Avoid making a lot of code type parametric, *especially* on the boundaries between crates. |
342 | 434 | ||
343 | ```rust | 435 | ```rust |
344 | // GOOD | 436 | // GOOD |
345 | fn frbonicate(f: impl FnMut()) { | 437 | fn frobnicate(f: impl FnMut()) { |
346 | frobnicate_impl(&mut f) | 438 | frobnicate_impl(&mut f) |
347 | } | 439 | } |
348 | fn frobnicate_impl(f: &mut dyn FnMut()) { | 440 | fn 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 |
353 | fn frbonicate(f: impl FnMut()) { | 445 | fn 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 |
362 | fn frbonicate(f: &Path) { | 454 | fn frobnicate(f: &Path) { |
363 | } | 455 | } |
364 | 456 | ||
365 | // BAD | 457 | // BAD |
366 | fn frbonicate(f: impl AsRef<Path>) { | 458 | fn 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 | |||
372 | Runtime performance obeys 80%/20% rule -- only a small fraction of code is hot. | 464 | Runtime performance obeys 80%/20% rule -- only a small fraction of code is hot. |
373 | Compile time **does not** obey this rule -- all code has to be compiled. | 465 | Compile time **does not** obey this rule -- all code has to be compiled. |
374 | 466 | ||
467 | ## Appropriate String Types | ||
468 | |||
469 | When 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 | |||
472 | Use `AbsPathBuf` and `AbsPath` over `std::Path`. | ||
473 | **Rationale:** rust-analyzer is a long-lived process which handles several projects at the same time. | ||
474 | It 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. |
419 | It is also more efficient when the caller already owns the allocation. | 519 | It is also more efficient when the caller already owns the allocation. |
420 | 520 | ||
421 | ## Collection types | 521 | ## Collection Types |
422 | 522 | ||
423 | Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. | 523 | Prefer `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 | |||
529 | When writing a recursive function to compute a sets of things, use an accumulator parameter instead of returning a fresh collection. | ||
530 | Accumulator goes first in the list of arguments. | ||
531 | |||
532 | ```rust | ||
533 | // GOOD | ||
534 | pub fn reachable_nodes(node: Node) -> FxHashSet<Node> { | ||
535 | let mut res = FxHashSet::default(); | ||
536 | go(&mut res, node); | ||
537 | res | ||
538 | } | ||
539 | fn 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 | ||
547 | pub 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 | ||