Rust API Guidelines

repository·master·Indexed 23 days ago

https://github.com/rust-lang/api-guidelines

A collection of best practices and recommendations for designing, implementing, and presenting APIs in the Rust programming language. Authored by the Rust library team, these guidelines cover naming conventions, ecosystem interoperability, macro design, documentation standards, API predictability, type safety, and dependability to help developers build idiomatic and interoperable libraries.

Tokens
13.6K
Snippets
34
Records
71
Agent score
79%

What's inside Rust API Guidelines

  1. Understand the purpose of the Rust API Guidelines

    master

    The Rust API Guidelines are a set of recommendations for designing and presenting APIs in the Rust programming language. They are authored by the Rust library team based on their experience with the standard library and the broader ecosystem.

    Key considerations for developers:

    • Not a mandate: These are guidelines, not strict rules. Use them as a set of considerations to help build idiomatic and interoperable libraries.
    • Ecosystem integration: While not mandatory, following these guidelines helps your crate integrate more smoothly with the existing Rust ecosystem.
    • Structure: The guidelines are organized into a concise checklist for quick scanning and topical chapters for detailed explanations.
  2. Access the Rust API guidelines

    master

    The Rust API guidelines provide a set of recommendations for designing and presenting APIs in Rust. These guidelines are authored by the Rust library team and are based on the experience of building the Rust standard library and other ecosystem crates. You can read the full set of guidelines at the official hosted site.

    https://rust-lang.github.io/api-guidelines
  3. Use `iter`, `iter_mut`, and `into_iter` for collections (C-ITER)

    master

    For homogeneous collections, iterator methods should follow this pattern to represent different ownership/borrowing models:

    • fn iter(&self) -> Iter (returns items as &U)
    • fn iter_mut(&mut self) -> IterMut (returns items as &mut U)
    • fn into_iter(self) -> IntoIter (returns items as U)

    Exceptions

    • This applies to methods, not functions. Functions that return iterators (like percent_encode) do not need to follow this convention.
    • Non-homogeneous collections (like str) use specific names instead of this group, such as str::bytes or str::chars.
    fn iter(&self) -> Iter             // Iter implements Iterator<Item = &U>
    fn iter_mut(&mut self) -> IterMut  // IterMut implements Iterator<Item = &mut U>
    fn into_iter(self) -> IntoIter     // IntoIter implements Iterator<Item = U>
  4. Convey meaning through custom types instead of bool or Option (C-CUSTOM-TYPE)

    master
    Avoid using core types like bool, u8, or Option for function arguments when they have multiple possible interpretations. Instead, use a deliberate type (an enum, struct, or tuple) to convey meaning and invariants. This makes the API more suggestive and easier to extend (e.g., adding a new variant to an enum) without breaking existing logic.
  5. Match iterator type names to their producing methods (C-ITER-TY)

    master

    When designing an API, ensure that the name of the type returned by an iterator-producing method matches the method name. This provides clarity and predictability for users. For example, a method named into_iter() should return a type named IntoIter. This convention applies to both methods and standalone functions.

    Standard library examples:

    • Vec::iter returns Iter
    • Vec::iter_mut returns IterMut
    • Vec::into_iter returns IntoIter
    • BTreeMap::keys returns Keys
    • BTreeMap::values returns Values
  6. Ensure destructors never fail (C-DTOR-FAIL)

    master

    Destructors (Drop implementation) are executed during panics. If a destructor fails during a panic, the program will abort.

    To avoid this:

    1. Do not return errors from a destructor.
    2. Provide a separate method (e.g., close()) that returns a Result to allow the caller to handle teardown errors explicitly.
    3. If the close() method is not called, the Drop implementation should perform the teardown and either ignore or log/trace any errors produced.
  7. Use `as_`, `to_`, and `into_` for ad-hoc conversions (C-CONV)

    master

    When providing conversion methods, use prefixes to indicate the cost and ownership changes involved:

    PrefixCostOwnership
    as_Freeborrowed $\rightarrow$ borrowed
    to_Expensiveborrowed $\rightarrow$ borrowed, borrowed $\rightarrow$ owned (non-Copy), or owned $\rightarrow$ owned (Copy)
    into_Variableowned $\rightarrow$ owned (non-Copy)

    Mental Model

    • as_ and into_ typically decrease abstraction: as_ exposes a view into the underlying representation, while into_ deconstructs data into its underlying representation.
    • to_ typically stays at the same level of abstraction but performs work to change representations.
    • Use into_inner() for wrappers that associate a single value with higher-level semantics (e.g., buffering like BufReader, encoding like GzDecoder, or atomic access like AtomicBool).

    Mutability in Names

    If the mut qualifier is part of the return type, include it in the name as it appears in the type. For example, as_mut_slice is preferred over as_slice_mut because it returns a &mut [T].

    // Return type is a mut slice.
    fn as_mut_slice(&mut self) -> &mut [T];
  8. Design object-safe traits (C-OBJECT)

    master

    When designing a trait, decide if it should be used as a trait object (dynamic dispatch) or as a generic bound (static dispatch).

    Trait objects have limitations: methods cannot be generic and cannot use Self (except in the receiver position). To make a trait object-safe while still allowing some generic methods, use a where Self: Sized clause on those specific methods. This excludes them from the trait object while keeping the rest of the trait usable as an object.

  9. Ensure operator overloads are unsurprising (C-OVERLOAD)

    master
    When implementing traits from std::ops to provide operator syntax (like *, |, etc.), ensure the implementation matches the strong expectations of that operator. For example, Mul should only be implemented for operations that resemble multiplication and share its expected mathematical properties (like associativity).
  10. Place conversions on the most specific type involved (C-CONV-SPECIFIC)

    master

    When implementing conversions between two types, place the conversion methods on the more 'specific' type (the one providing additional invariants or interpretations). This prevents polluting simpler, more general types with excessive conversion methods.

    For example, str is more specific than &[u8] because it guarantees UTF-8 encoding. Therefore, str provides both as_bytes and from_utf8 rather than &[u8] providing methods to convert to str.

  11. Pass mutable references to generic Read and Write functions

    master

    When a function accepts a generic parameter R: Read or W: Write by value, you can pass a mutable reference (&mut R) instead of the value itself. This is because the standard library provides implementations of Read and Write for mutable references:

    impl<'a, R: Read + ?Sized> Read for &'a mut R
    impl<'a, W: Write + ?Sized> Write for &'a mut W

    This pattern is essential when a user needs to perform multiple read or write operations on the same underlying resource, as passing the value directly would consume it after the first call.

  12. Keep struct fields private (C-STRUCT-PRIVATE)

    master

    Avoid making struct fields public unless the struct is a passive, compound data structure (similar to a C struct). Making a field public pins down a specific representation and prevents the type from enforcing invariants or performing validation on that data.

    For most types, prefer hiding fields and providing getter and setter methods instead.