These guidelines define IOTA Foundation rules and recommendations for Rust development.
The following rules should always be followed to the best of your ability.
All usages of unsafe
, .expect()
, panic!()
, unreachable!()
, or other functions that can explicitly panic under
defined conditions should be justified using a comment, or in some cases in the error message.
Unwrapping should be done using .expect()
with an appropriate error message. Messages should follow
these guidelines.
Whenever possible, audited libraries should be used to perform unsafe behaviors, rather than implementing something manually which could be incorrect.
Functions which might panic should document this behavior clearly in its rustdoc
comments within a Panics
section
(see here for an example).
Use variable names between 1-4 words long that adequately describe the purpose of the variable. Avoid acronyms and abbreviations except when the variable is extremely long.
It is acceptable to use short, non-descriptive variables in some situations. For example:
- Indexes in loops (
i
,j
,k
,idx
, etc) - Single-line mapping closures
Use crate
-level imports. Do not use super
to specify relative import paths, as they tend to break more easily during
refactors than crate
imports.
NOTE: Relative import paths are allowed in test modules.
Do not use the import-all wildcard (use something::*;
). All dependencies should be explicitly imported.
NOTE: The wildcard is allowed for public re-exports.
Error messages should generally be short and to the point. Avoid multiple sentences and periods, instead use commas or semicolons to divide message content. When writing error messages, think about what information would be most helpful in a debugging scenario to quickly understand what the cause of the error was.
Error messages should be lowercase and conform to Common Message Styles.
Errors which are expected to be handled by library users should be defined in such a way that they can be responded to
appropriately, usually by matching on enum variants or an error code. Avoid trait objects and convenience crates such as
anyhow
.
Types should be defined as privately as possible, so that updating them does not necessitate breaking changes.
When changes are made to a public API, the changes should be additive if possible.
Public API methods that are due to be deleted should be marked with #[deprecated]
with a reason and version, so that
they may be safely deleted after the next breaking version release.
Structs, enums, and variants that are expected to grow (such as error types) should be marked with #[non_exhaustive]
so that additive changes are non-breaking.
Dependencies that are defined in the workspace manifest should follow these rules:
version
set to the most fine-grained version that is non-breaking*default-features = false
features
should only contain features that are common across all child crates
Dependencies that are defined in crates should follow these rules:
- If using a workspace dependency, set
workspace = true
; otherwise setversion
to the most fine-grained version that is non-breaking* features
contains additional features that are needed for the crate
* SemVer specifies that breaking changes may occur on Major releases for stable crates (version >= 1.0.0
), and
Minor releases for crates below version 1.0.0
.
New dependencies should be well justified.
Unmaintained dependencies should never be added. Replacements should be found for existing unmaintained dependencies if possible.
Never release a version of crates with vulnerabilities reported by auditing. If there is an audit, update immediately when a new version is published.
Critical dependencies should be well vetted. A dependency is critical if replacing it would present a major development effort.
Crates that are commonly used by the community can be considered well-vetted. Lesser-known crates should be thoroughly scrutinized.
Use .unwrap()
in tests rather than returning a result, so that the stack trace is printed in the output if the line
fails.
Unit tests should be defined in a tests module as locally as possible to the tested code.
Avoid making non-public APIs public if they will only be used by tests. Instead, either define the tests locally or add
test-specific public APIs that are gated by the #[cfg(test)]
attribute.
Test functions should be descriptive and should not be prefixed with test_
.
The following conventions are strongly recommended, but may not always apply. Breaking these conventions generally demands an explanation.
In addition to the conventions listed here, follow the Rust API Guidelines.
Generally speaking, mod.rs
files should contain little or no code.
NOTE: In situations where a module folder should logically contain functionality, create a file with the same
name as the containing folder and re-export it’s members in mod.rs
.
something
├ mod.rs <-- pub use something::*;
⎩ something.rs
Prefer small file sizes with only local struct implementations and strongly applicable code.
When an API is highly reliant on traits to provide common functionality, they should be bundled in a prelude-style public re-export for convenience.
Detailed context should be provided on errors whenever possible.
When using thiserror
to define errors, prefer #[source]
over #[from]
when defining wrapping errors. Errors that
are converted automatically by thiserror
contain only the context provided by the wrapped error, which is often from a
3rd party library. This is frequently insufficient to discern the nature of the error when debugging, particularly if a
back trace is not available. Using #[source]
forces the call site to manually map error types and allows a opportunity
to add helpful context to errors that may otherwise contain none.
When generics and lifetimes are presented as part of a public API, they should be descriptively named. In particular,
lifetimes should describe what they constrain. Consider using multi-letter names for type parameters (e.g. Doc
instead
of just D
) as well if it helps clarity and readability, in particular when a type has multiple type parameters.
When exposing a new public API, write accompanying examples and tests that use it.
Documentation about high-level library usage should be located in the top-level lib.rs
. This documentation should be
extensive and cover most topics that a user needs to know in order to use the API.
Code examples in rustdoc
comments should be used sparingly in code that needs usage examples due to complexity or
non-obvious behaviors.
New features should be strongly justified, and other options should be considered first.