Skip to content

Latest commit

 

History

History
163 lines (119 loc) · 7.73 KB

File metadata and controls

163 lines (119 loc) · 7.73 KB

RF-A Code Style Guide

Ideally, we will have automated means of checking as much of this guide as possible, and will support these guidelines with helpful APIs, by using the type system, and so on. We will continually reduce the manual effort necessary to adhere to these guidelines.

This document, and any supporting code, are subject to change. Additionally, there is likely to be code and documentation that doesn’t meet these guidelines. If you see a way to get closer to the ideal this document describes, would like to propose a policy change, or have an idea for an automated check, please go right ahead and push a change to Gerrit.

How To Use unsafe

As part of meeting the RF-A threat model, we aim to have as little unsafe code as possible. Keep unsafe blocks as short as possible, and always write a safety comment explaining how the code upholds Rust’s safety guarantees. (The undocumented_unsafe_blocks and missing_safety_doc Clippy lints are set to “deny”.)

Safety comments should make specific and verifiable claims about the validity of pointers, the block's (non-)impingement on memory safety and type safety, and how the block meets the # Safety requirements of the unsafe functions it calls.

During code review, get an unsafe expert to review your unsafe blocks and their safety comments.

Third-party Dependencies

As part of meeting the RF-A threat model, we aim to minimize third-party crate dependencies. Only import a third-party crate if it’s absolutely necessary, and only use the minimum necessary crate features.

For [build-dependencies], convenience and clarity can be acceptable reasons to import a 3P crate (for example, anyhow). Even then, parsimony remains a goal.

Use cargo vet

All dependencies (direct and transitive) must be audited with cargo vet. This is enforced by CI.

The crate’s vetter and the crate’s author must not be the same person.

Coding Guidelines

Using The Primitive Integral Types

usize and isize are the Rust types for values that will become pointers, sequence indices, and memory object counts.

u32, i32, u64, and i64 are the right types for other register values, bit fields and bit masks, and other integer values.

Inevitably, we will need to cast these types to each other and to pointers. We must do so carefully to avoid unintended sign extension, truncation, erasure of pointer provenance, and unportability. A bare cast from (for example) usize to u64 using as risks creating those problems — while we currently target 64-bit machines only, that is not guaranteed to always be true. The safest and most future-proof approach for the integer types is to use foo.try_into().unwrap(), which will either fail immediately upon use or (very likely) be optimized away.

TODO: Develop a standard, ergonomic way to safely convert between these types, and recommend them here.

Converting between integer types and pointers is dangerous. In addition to the risks of (for example) incorrect truncation or sign extension, such conversions also risk the elision of pointer provenance information. Where applicable, prefer the strict provenance and exposed provenance APIs instead of bare casts with (for example) as.

In the future, we might start using the lossy_provenance_casts lint to help us catch such cases.

assert And debug_assert

Use assert and debug_assert freely to check and enforce your assumptions. asserts run in debug and release builds, while debug_asserts run only in debug builds.

In general, prefer assert unless it causes RF-A to miss some critical efficiency goal (latency, object code size, or other). Consider debug_assert to be, in effect, an optimization: the optimization is safe only if we are certain that the condition the assert tests can only fail due to (statically determinable) programmer error. When the condition may be affected by run-time conditions or by likely code churn — RF-A is a work in progress — assert is the way to go.

cargo clippy

The RF-A project has the eventual goal of defining a set of clippy lints and then staying clean of those lints, as a way of ensuring a baseline of code quality and readability.

Being mechanically generated, lints don’t always directly describe the problem; sometimes they only detect a symptom (possibly even a 2nd-order symptom). For each lint, think carefully about the ultimate cause and seek to solve that. Don’t quiet lints just for the sake of quieting them.

Ordering of mod and use statements

The Rust Style Guide offers a few rules about the order in which use statements should appear in a Rust source file but it leaves some of it unspecified. For example, it does not recommend any particular grouping scheme for imports (like one group for Rust standard library imports, one group for other types of imports), and it does not mention whether they should come before or after mod statements.

The RF-A project tries to stick to the following convention:

  1. Module declarations first.
  2. An empty line.
  3. One single group of imports, ordered as recommended in the Rust Style Guide.

In practice, this looks like this:

                                    For example:
module declarations                 mod foo;
<empty line>
Imports from parent module          use super::x;
Imports from the crate root         use crate::y;
Other Imports
  (standard library crates, external crates or the module's children crates all clubbed together,
  ordered as per the Rust Style Guide)

cargo fmt will enforce the correct ordering of imports, so long as the "one single group of imports" rule is followed.

Documentation

Document all pub interfaces (functions, types, methods, etc.) with Rustdoc comments (/// ... and //! ...). (The missing_docs lint is set to “deny” in Cargo.toml.)

Use Markdown to indicate structure where necessary, especially for code identifiers but also for emphasis, hyperlinks (including links to related code), and so on.

It’s always worth spending time on punctuation, capitalization (including proper nouns, Arm architecture technical terms, and acronyms), and spelling. TODO: See if Arm has a documentation style guide we can follow.

The primary language for comments and naming must be International English. In cases where there is a conflict between the American English and British English spellings of a word, use the American English spelling. However, for proper nouns, such as the names of companies, use the existing spelling.

Rustdoc comments for functions and methods should begin with a summary fragment starting with a verb. This should be phrased such that it could be preceded by "This function". For example:

/// Computes the sum of the given numbers.
fn sum(numbers: &[u32]) -> u32 {
    //...
}

Copyright and License

At the top of each source code file, put a header in this format:

Copyright The Rusted Firmware-A Contributors.

SPDX-License-Identifier: BSD-3-Clause

The SPDX tag replaces the full license text and enables machine processing of license information based on the SPDX License Identifiers that are available here.