Merge pull request #718 from teloxide/code_style

Update code style

Former-commit-id: 13a10110d4
This commit is contained in:
Waffle Maybe 2022-10-04 05:30:42 +04:00 committed by GitHub
commit c7aa76ad39

View file

@ -1,100 +1,103 @@
# Code style # Code style
This is a description of a coding style that every contributor must follow. Please, read the whole document before you start pushing code.
This is a description of a coding style that every contributor must follow.
Please, read the whole document before you start pushing code.
## Generics ## Generics
Generics are always written with `where`.
Bad: All trait bounds should be written in `where`:
```rust ```rust
pub fn new<N: Into<String>, // GOOD
T: Into<String>, pub fn new<N, T, P, E>(user_id: i32, name: N, title: T, png_sticker: P, emojis: E) -> Self
P: Into<InputFile>, where
E: Into<String>> N: Into<String>,
T: Into<String>,
P: Into<InputFile>,
E: Into<String>,
{ ... }
// BAD
pub fn new<N: Into<String>,
T: Into<String>,
P: Into<InputFile>,
E: Into<String>>
(user_id: i32, name: N, title: T, png_sticker: P, emojis: E) -> Self { ... } (user_id: i32, name: N, title: T, png_sticker: P, emojis: E) -> Self { ... }
``` ```
Good:
```rust ```rust
pub fn new<N, T, P, E>(user_id: i32, name: N, title: T, png_sticker: P, emojis: E) -> Self // GOOD
where impl<T> Trait for Wrap<T>
N: Into<String>, where
T: Into<String>, T: Trait
P: Into<InputFile>, { ... }
E: Into<String> { ... }
// BAD
impl<T: Trait> Trait for Wrap<T> { ... }
``` ```
## Comments **Rationale:**
1. Comments must describe what your code does and mustn't describe how your code does it and bla-bla-bla. Be sure that your comments follow the grammar, including punctuation, the first capital letter and so on. - `where` clauses are easier to read when there are a lot of bounds
- uniformity
Bad: ## Documentation comments
```rust 1. Documentation must describe _what_ your code does and mustn't describe _how_ your code does it and bla-bla-bla.
/// this function make request to telegram 2. Be sure that your comments follow the grammar, including punctuation, the first capital letter and so on:
pub fn make_request(url: &str) -> String { ... } ```rust
``` // GOOD
/// This function makes a request to Telegram.
pub fn make_request(url: &str) -> String { ... }
Good: // BAD
/// this function make request to telegram
pub fn make_request(url: &str) -> String { ... }
```
3. Do not use ending punctuation in short list items (usually containing just one phrase or sentence):
```md
<!-- GOOD -->
- Handle different kinds of Update
- Pass dependencies to handlers
- Disable a default Ctrl-C handling
```rust <!-- BAD -->
/// This function makes a request to Telegram. - Handle different kinds of Update.
pub fn make_request(url: &str) -> String { ... } - Pass dependencies to handlers.
``` - Disable a default Ctrl-C handling.
2. Do not use ending punctuation in short list items (usually containing just one phrase or sentence). Bad: <!-- BAD -->
- Handle different kinds of Update;
- Pass dependencies to handlers;
- Disable a default Ctrl-C handling;
```
3. Link resources in your comments when possible:
```rust
/// Download a file from Telegram.
///
/// `path` can be obtained from the [`Bot::get_file`].
///
/// To download into [`AsyncWrite`] (e.g. [`tokio::fs::File`]), see
/// [`Bot::download_file`].
///
/// [`Bot::get_file`]: crate::bot::Bot::get_file
/// [`AsyncWrite`]: tokio::io::AsyncWrite
/// [`tokio::fs::File`]: tokio::fs::File
/// [`Bot::download_file`]: crate::Bot::download_file
```
```md ## Use `Self` where possible
- Handle different kinds of Update.
- Pass dependencies to handlers.
- Disable a default Ctrl-C handling.
```
Bad: When referring to the type for which block is implemented, prefer using `Self`, rather than the name of the type:
```md
- Handle different kinds of Update;
- Pass dependencies to handlers;
- Disable a default Ctrl-C handling.
```
Good:
```md
- Handle different kinds of Update
- Pass dependencies to handlers
- Disable a default Ctrl-C handling
```
3. Link resources in your comments when possible:
```rust
/// Download a file from Telegram.
///
/// `path` can be obtained from the [`Bot::get_file`].
///
/// To download into [`AsyncWrite`] (e.g. [`tokio::fs::File`]), see
/// [`Bot::download_file`].
///
/// [`Bot::get_file`]: crate::bot::Bot::get_file
/// [`AsyncWrite`]: tokio::io::AsyncWrite
/// [`tokio::fs::File`]: tokio::fs::File
/// [`Bot::download_file`]: crate::Bot::download_file
#[cfg(feature = "unstable-stream")]
pub async fn download_file_stream(
&self,
path: &str,
) -> Result<impl Stream<Item = Result<Bytes, reqwest::Error>>, reqwest::Error>
{
download_file_stream(&self.client, &self.token, path).await
}
```
## Use Self where possible
Bad:
```rust ```rust
impl ErrorKind { impl ErrorKind {
// GOOD
fn print(&self) {
Self::Io => println!("Io"),
Self::Network => println!("Network"),
Self::Json => println!("Json"),
}
// BAD
fn print(&self) { fn print(&self) {
ErrorKind::Io => println!("Io"), ErrorKind::Io => println!("Io"),
ErrorKind::Network => println!("Network"), ErrorKind::Network => println!("Network"),
@ -102,50 +105,303 @@ impl ErrorKind {
} }
} }
``` ```
Good:
```rust ```rust
impl ErrorKind { impl<'a> AnswerCallbackQuery<'a> {
fn print(&self) { // GOOD
Self::Io => println!("Io"), fn new<C>(bot: &'a Bot, callback_query_id: C) -> Self
Self::Network => println!("Network"), where
Self::Json => println!("Json"), C: Into<String>,
{ ... }
// BAD
fn new<C>(bot: &'a Bot, callback_query_id: C) -> AnswerCallbackQuery<'a>
where
C: Into<String>,
{ ... }
}
```
**Rationale:** `Self` is generally shorter and it's easier to copy-paste code or rename the type.
## Avoid duplication in fields names
```rust
struct Message {
// GOOD
#[serde(rename = "message_id")]
id: MessageId,
// BAD
message_id: MessageId,
}
```
**Rationale:** duplication blurs the focus of code, making it unnecessarily longer.
## Conventional generic names
Use a generic parameter name `S` for streams, `Fut` for futures, `F` for functions (where possible).
**Rationale:** uniformity.
## Deriving traits
Derive `Copy`, `Clone`, `Eq`, `PartialEq`, `Hash` and `Debug` for public types when possible.
**Rationale:** these traits can be useful for users and can be implemented for most types.
Derive `Default` when there is a reasonable default value for the type.
**Rationale:** `Default` plays nicely with generic code (for example, `mem::take`).
## `Into`-polymorphism
Use `T: Into<Ty>` when this can simplify user code.
I.e. when there are types that implement `Into<Ty>` that are likely to be passed to this function.
**Rationale:** conversions unnecessarily complicate caller code and can be confusing for beginners.
## `must_use`
Always mark functions as `#[must_use]` if they don't have side effects and the only reason to call them is to get the result:
```rust
impl User {
// GOOD
#[must_use]
fn full_name(&self) -> String {
format!("{} {}", user.first_name, user.last_name)
} }
} }
``` ```
<details> **Rationale:** users will get warnings if they forgot to do something with the result, potentially preventing bugs.
<summary>More examples</summary>
Bad: ## Creating boxed futures
Prefer `Box::pin(async { ... })` instead of `async { ... }.boxed()`.
**Rationale:** the former is generally formatted better by rustfmt.
## Full paths for logging
Always write `log::<op>!(...)` instead of importing `use log::<op>;` and invoking `<op>!(...)`.
```rust ```rust
impl<'a> AnswerCallbackQuery<'a> { // GOOD
pub(crate) fn new<C>(bot: &'a Bot, callback_query_id: C) -> AnswerCallbackQuery<'a> log::warn!("Everything is on fire");
where
C: Into<String>, { ... } // BAD
use log::warn;
warn!("Everything is on fire");
``` ```
Good: **Rationale:**
- Less polluted import blocks
- Uniformity
## `&str` -> `String` conversion
Prefer using `.to_owned()`, rather than `.to_string()`, `.into()`, `String::from`, etc.
**Rationale:** uniformity, intent clarity.
## Order of imports
Separate import groups with blank lines. Use one use per crate.
Module declarations come before the imports.
Order them in "suggested reading order" for a person new to the code base.
```rust ```rust
impl<'a> AnswerCallbackQuery<'a> { mod x;
pub(crate) fn new<C>(bot: &'a Bot, callback_query_id: C) -> Self mod y;
where
C: Into<String>, { ... } // 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::{}
// Re-exports are treated as item definitions rather than imports, so they go
// after imports and modules. Use them sparingly.
pub use crate::x::Z;
``` ```
</details>
## Naming **Rationale:**
1. Avoid unnecessary duplication (`Message::message_id` -> `Message::id` using `#[serde(rename = "message_id")]`). - Reading order is important for new contributors
2. Use a generic parameter name `S` for streams, `Fut` for futures, `F` for functions (where possible). - Grouping by crate allows spotting unwanted dependencies easier
- Consistency
## Deriving ## Import Style
1. Derive `Copy`, `Eq`, `Hash`, `PartialEq`, `Clone`, `Debug` for public types when possible (note: if the default `Debug` implementation is weird, you should manually implement it by yourself).
2. Derive `Default` when there is an algorithm to get a default value for your type.
## Misc When implementing traits from `std::fmt` import the module:
1. Use `Into<...>` only where there exists at least one conversion **and** it will be logically to use.
2. Always mark a function as `#[must_use]` if its return value **must** be used. ```rust
3. `Box::pin(async [move] { ... })` instead of `async [move] { ... }.boxed()`. // GOOD
4. Always write `log::<op>!(...)` instead of importing `use log::<op>;` and invoking `<op>!(...)`. For example, write `log::info!("blah")`. use std::fmt;
impl fmt::Display for RenameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { .. }
}
// BAD
impl std::fmt::Display for RenameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { .. }
}
```
**Rationale:**
- Makes it clear that a trait is implemented, rather than used
- Less typing
Prefer `use crate::foo::bar` to `use super::bar` or `use self::bar::baz`. **Rationale:**
- Works in all cases
- Consistency
## 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:
```rust
// GOOD
pub(crate) fn frobnicate() {
Helper::act()
}
#[derive(Default)]
struct Helper { stuff: i32 }
impl Helper {
fn act(&self) {
}
}
// BAD
#[derive(Default)]
struct Helper { stuff: i32 }
pub(crate) fn frobnicate() {
Helper::act()
}
impl Helper {
fn act(&self) {
}
}
```
If there's a mixture of private and public items, put public items first.
Put structs and enums first, functions and impls last. Order type declarations in a top-down manner:
```rust
// GOOD
struct Parent {
children: Vec<Child>
}
struct Child;
impl Parent {
}
impl Child {
}
// BAD
struct Child;
impl Child {
}
struct Parent {
children: Vec<Child>
}
impl Parent {
}
```
**Rationale:**
- Easier to get a sense of the API by visually scanning the file
- If function bodies are folded in the editor, the source code should be read as documentation for the public API
## Early Returns
Do use early returns:
```rust
// GOOD
fn foo() -> Option<Bar> {
if !condition() {
return None;
}
Some(...)
}
// BAD
fn foo() -> Option<Bar> {
if condition() {
Some(...)
} else {
None
}
}
```
**Rationale:** reduce cognitive stack usage.
## If-let
Avoid the `if let ... { } else { }` construct, use `match` instead:
```rust
// GOOD
match ctx.expected_type.as_ref() {
Some(expected_type) => completion_ty == expected_type && !expected_type.is_unit(),
None => false,
}
// BAD
if let Some(expected_type) = ctx.expected_type.as_ref() {
completion_ty == expected_type && !expected_type.is_unit()
} else {
false
}
```
**Rationale:**
- `match` is almost always more compact
- The `else` branch can get a more precise pattern: `None` or `Err(_)` instead of `_`
## Empty Match Arms
Use `=> (),` when a match arm is intentionally empty:
```rust
// GOOD
match result {
Ok(_) => (),
Err(err) => error!("{}", err),
}
// BAD
match result {
Ok(_) => {}
Err(err) => error!("{}", err),
}
```
**Rationale:** consistency.