esp-hal/documentation/API-GUIDELINES.md
Sergio Gasquez Arcos b5f0246129
Reordered RX-TX pairs to be consistent (#2074)
* feat: Update rx-tx order in i2s

* feat: Update rx-tx order in dma macros

* feat: Update rx-tx order in spi

* feat: Update rx-tx order in aes

* feat: Update rx-tx order in mem2mem

* feat: Update rx-tx order in twai and split methods

* feat: Update rx-tx order in twai

* feat: Update rx-tx order in twai and uart docs

* docs: Add sentence about order

* docs: Update changelog

* feat: Update rx-tx order in embassy_interrupt_spi_dma tests

* style: Rustfmt

* docs: Migrating guide

* fix: Typo

Co-authored-by: Dániel Buga <bugadani@gmail.com>

* fix: Diff

Co-authored-by: Dániel Buga <bugadani@gmail.com>

* fix: Tests rx-tx order

* fix: Update new_with_default_pins order

* feat: Update rx/tx order in hil_test::common_test_pins!

* feat: Update dma_extmem2mem example

* fix: Revert deleted input arg

* style: rustfmt

* feat: Disable test_asymmetric_dma_transfer for S2

---------

Co-authored-by: Dániel Buga <bugadani@gmail.com>
2024-09-06 09:56:10 +00:00

5.7 KiB

esp-rs API Guidelines

About

This is a living document - make sure to check the latest version of this document.

Note

Not all of the currently existing code follows this guideline, yet.

In general, the Rust API Guidelines apply to all projects in the ESP-RS GitHub organization where possible. (C-RW-VALUE and C-SERDE do not apply)

Especially for public API but if possible also for internal APIs.

The following paragraphs contain additional recommendations.

Construction and Destruction of Drivers

  • Drivers take peripherals and pins via the PeripheralRef pattern - they don't consume peripherals/pins.
  • Consider adding a Drop implementation resetting the peripheral to idle state.
  • Consider using a builder-like pattern for configuration which must be done during initialization.

Interoperability

  • cfg gated defmt derives and impls are added to new structs and enums.
    • see this example
    • e.g. #[cfg_attr(feature = "defmt", derive(defmt::Format))]
  • Don't use log::XXX! macros directly - use the wrappers in fmt.rs (e.g. just info! instead of log::info! or importing log::*)!

API Surface

  • Add #[deny(missing_docs)] to new modules or when reworking a larger part of a module. In the end we will require this for whole crates.
  • API documentation shouldn't be an afterthought
  • Private details shouldn't leak into the public API, and should be made private where technically possible.
  • Implementation details that need to be public should be marked with #[doc(hidden)] and a comment as to why it needs to be public.
  • Functions which technically need to be public but shouldn't be callable by the user need to be sealed.
  • Any public traits, that must not be implemented downstream need to be Sealed
  • Prefer compile-time checks over runtime checks where possible, prefer a fallible API over panics.
  • Follow naming conventions in order to be consistent across drivers - take inspiration from existing drivers
  • Design APIs in a way that they are easy to use.
  • Driver API decisions should be assessed individually, don't not just follow embedded-hal or other ecosystem trait crates. Expose the capabilities of the hardware. (Ecosystem traits are implemented on top of the inherent API)
  • Avoid type states and extraneous generics whenever possible
    • These often lead to usability problems, and tend to just complicate things needlessly - sometimes it can be a good tradeoff to make a type not ZST
    • Common cases of useless type info is storing pin information - this is usually not required after configuring the pins and will bloat the complexity of the type massively. When following the PeripheralRef pattern it's not needed in order to keep users from re-using the pin while in use
  • Avoiding &mut self when &self is safe to use. &self is generally easier to use as an API. Typical applications of this are where the methods just do writes to registers which don't have side effects.
    • For example starting a timer is fine for &self, worst case a timer will be started twice if two parts of the program call it. You can see a real example of this here
  • Maintain order consistency in the API, such as in the case of pairs like RX/TX.

Maintainability

  • Avoid excessive use of macros unless there is no other option; modification of the PAC crates should be considered before resorting to macros.
  • Every line of code is a liability. Take some time to see if your implementation can be simplified before opening a PR.
  • If you are porting code from ESP-IDF (or anything else), please include a link WITH the commit hash in it, and please highlight the relevant line(s) of code
  • If necessary provide further context as comments (consider linking to code, PRs, TRM - make sure to use permanent links, e.g. include the hash when linking to a Git repository, include the revision, page number etc. when linking to TRMs)
  • Generally, follow common "good practices" and idiomatic Rust style
  • All Future objects (public or private) must be marked with #[must_use = "futures do nothing unless you `.await` or poll them"].
  • Prefer cfg_if! over multiple exclusive #[cfg] attributes. cfg_if! visually divides the options, often results in simpler conditions and simplifies adding new branches in the future.

Modules Documentation

Modules should have the following documentation format:

//! # Peripheral Name (Peripheral Acronym)
//!
//! ## Overview
//! Small description of the peripheral, see ESP-IDF docs or TRM
//!
//! ## Configuration
//! Explain how can the peripheral be configured, and which parameters can be configured
//!
//! ## Usage
//! Explain if we implement any external traits
//!
//! ## Examples
//!
//! ### Name of the Example
//! Small description of the example if needed
//! ```rust, no_run
//! ...
//! ```
//!
//! ## Implementation State
//! List unsupported features
  • If any of the headers is empty, remove it
  • When possible, use ESP-IDF docs and TRM as references and include links if possible.
    • In case of referencing an ESP-IDF link make it chip-specific, for example:
      #![doc = concat!("[ESP-IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/", crate::soc::chip!(), "/api-reference/peripherals/etm.html)")]
      
  • Documentation examples should be short and basic, for more complex scenarios, create an example.