teloxide/CODE_STYLE.md
2022-09-29 19:01:03 +04:00

5.1 KiB

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.

Generics

All trait bounds should be written in where

// GOOD
pub fn new<N, T, P, E>(user_id: i32, name: N, title: T, png_sticker: P, emojis: E) -> Self
where
    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 { ... }
// GOOD
impl<T> Trait for Wrap<T>
where
    T: Trait
{ ... }

// BAD
impl<T: Trait> Trait for Wrap<T> { ... }

Rationale: where clauses are easier to read when there are a lot of bounds, uniformity.

Documentation comments

  1. Documentation must describe what your code does and mustn't describe how your code does it and bla-bla-bla.
  2. Be sure that your comments follow the grammar, including punctuation, the first capital letter and so on.
    // GOOD
    /// This function makes a request to Telegram.
    pub fn make_request(url: &str) -> String { ... }
    
    // 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).
    <!-- GOOD -->
    - Handle different kinds of Update
    - Pass dependencies to handlers
    - Disable a default Ctrl-C handling
    
    <!-- BAD -->
    - Handle different kinds of Update.
    - Pass dependencies to handlers.
    - Disable a default Ctrl-C handling.
    
    <!-- BAD -->
    - Handle different kinds of Update;
    - Pass dependencies to handlers;
    - Disable a default Ctrl-C handling;
    
  4. Link resources in your comments when possible, for example:
    /// 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
    

Use Self where possible

When referring to the type for which block is implemented, prefer using Self, rather than the name of the type.

impl ErrorKind {
    // GOOD
    fn print(&self) {
        Self::Io => println!("Io"),
        Self::Network => println!("Network"),
        Self::Json => println!("Json"),
    }

    // BAD
    fn print(&self) {
        ErrorKind::Io => println!("Io"),
        ErrorKind::Network => println!("Network"),
        ErrorKind::Json => println!("Json"),
    }
}
impl<'a> AnswerCallbackQuery<'a> {
    // GOOD
    fn new<C>(bot: &'a Bot, callback_query_id: C) -> Self
    where
        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

struct Message {
    // GOOD
    #[serde(rename = "message_id")]
    id: MessageId,

    // BAD
    message_id: MessageId,
}

Rationale: duplication is unnecessary

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 a functions as #[must_use] if they don't have side-effects and the only reason to call them is to get the result.

impl User {
    // GOOD
    #[must_use]
    fn full_name(&self) -> String {
        format!("{} {}", user.first_name, user.last_name)
    }
}

Rationale: users will get warnings if they forgot to do something with the result, potentially preventing bugs.

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>!(...).

// GOOD
log::warn!("Everything is on fire");

// BAD
use log::warn;

warn!("Everything is on fire");

Rationale: uniformity, it's clearer which log crate is used.