This document lists conventions to follow when writing Rust in the Fuchsia Source Tree. These conventions are a combination of best practices, project preferences, and some choices made for the sake of consistency.
Guidelines
Naming
Casing conforms to Rust idioms
See C-CASE.
Ad-hoc conversions follow as_
, to_
, into_
conventions
See C-CONV.
Getter names follow Rust convention
With a few exceptions, the get_
prefix is not used for getters in Rust code.
See C-GETTER.
Methods on collections that produce iterators follow iter
, iter_mut
, into_iter
See C-ITER.
Iterator type names match the methods that produce them
See C-ITER-TY.
Names use a consistent word order
See C-WORD-ORDER.
Interoperability
Types eagerly implement common traits
Copy
, Clone
, Eq
, PartialEq
, Ord
, PartialOrd
, Hash
, Debug
, Display
, Default
should all be implemented when appropriate.
See C-COMMON-TRAITS.
Conversions use the standard traits From
, AsRef
, AsMut
See C-CONV-TRAITS.
Collections implement FromIterator
and Extend
See C-COLLECT.
Data structures implement Serde's Serialize
, Deserialize
See C-SERDE.
Types are Send
and Sync
where possible
See C-SEND-SYNC.
Error types are meaningful and well-behaved
See C-GOOD-ERR.
Binary number types provide Hex
, Octal
, Binary
formatting
See C-NUM-FMT.
Generic reader/writer functions take R: Read
and W: Write
by value
See C-RW-VALUE.
Macros
Input syntax is evocative of the output
See C-EVOCATIVE.
Macros compose well with attributes
See C-MACRO-ATTR.
Item macros work anywhere that items are allowed
See C-ANYWHERE.
Item macros support visibility specifiers
See C-MACRO-VIS.
Type fragments are flexible
See C-MACRO-TY.
Documentation
Crate level docs are thorough and include examples
See C-CRATE-DOC.
All items have a rustdoc example
See C-EXAMPLE.
Examples use ?
, not try!
, not unwrap
See C-QUESTION-MARK.
Function docs include error, panic, and safety considerations
See C-FAILURE.
Prose contains hyperlinks to relevant things
See C-LINK.
Rustdoc does not show unhelpful implementation details
See C-HIDDEN.
Every unsafe
block has an accompanying justification
Safety justifications should begin with // SAFETY:
and explain why the unsafe block is sound.
Do
// SAFETY: <why this unsafe operation's safety requirements are met>
Don't
// Safety: <...>
// [SAFETY] <...>
// <...>
// SAFETY: Trust me.
Unsafe code should explain why the unsafe block is necessary and why the code contained within the block is sound. If there are safe alternatives which may appear suitable but cannot be used, the reason why they cannot be used should be documented as well.
Do
// SAFETY: The `bytes` returned from our string builder are guaranteed to be
// valid UTF-8. We used to call `from_utf8`, but this caused performance issues
// with large inputs.
let s = unsafe { String::from_utf8_unchecked(bytes) };
Don't
// SAFETY: We shouldn't have to validate `bytes`, and the safe version is slow.
let s = unsafe { String::from_utf8_unchecked(bytes) };
Justifications should directly address the requirements for the operation. It's okay to summarize as long as all of the requirements are addressed.
Do
// SAFETY: The caller has guaranteed that `ptr` is valid for reads, properly
// aligned, and points to a properly-initialized `T`.
unsafe {
let x = ptr.read();
}
Do
// SAFETY: The caller has guaranteed that `ptr` points to a valid `T`.
unsafe {
let x = ptr.read();
}
Don't
// SAFETY: `ptr` is safe to read.
unsafe {
let x = ptr.read();
}
Safety justifications should address why an operation is justified, not just that an operation is justified.
Do
const BUFFER_LEN: usize = 1024;
fn partially_init(n: usize) -> MaybeUninit<[i32; BUFFER_LEN]> {
// These asserts ensure our safety conditions are met later on
const _: () = assert!(BUFFER_LEN <= 1024);
assert!(n < BUFFER_LEN);
let mut buffer = MaybeUninit::<[i32; BUFFER_LEN]>::uninit();
let ptr = buffer.as_mut_ptr().cast::<i32>();
for i in 0..n {
// SAFETY:
// - `ptr` points to the first `i32` of `buffer`.
// - `buffer` has space for BUFFER_LEN elements and we asserted that
// `n < BUFFER_LEN`.
// - We asserted that `BUFFER_LEN <= 1024`, so `size_of::<i32>() * i`
// is at most 4096 which is less than `isize::MAX` and `usize::MAX`.
let element = unsafe { &mut *ptr.add(i) };
*element = i as i32;
}
buffer
}
Don't
const BUFFER_LEN: usize = 1024;
fn partially_init(n: usize) -> MaybeUninit<[i32; BUFFER_LEN]> {
// Why are these asserts here?
const _: () = assert!(BUFFER_LEN <= 1024);
assert!(n < BUFFER_LEN);
let mut buffer = MaybeUninit::<[i32; BUFFER_LEN]>::uninit();
let ptr = buffer.as_mut_ptr().cast::<i32>();
for i in 0..n {
// SAFETY:
// - `ptr` is in bounds or one byte past the end of an allocated object.
// - `ptr + i` is also in bounds.
// - The computed offset, in bytes, doesn't overflow an `isize`.
// - The offset being in bounds does not rely on "wrapping around" the
// address space.
let element = unsafe { &mut*ptr.add(i) };
*element = i as i32;
}
buffer
}
Unsafe traits are documented, unsafe trait impls are justified
Unsafe traits should be documented according to the same guidelines as unsafe functions.
Unsafe trait definitions should document safety considerations (See C-FAILURE), and
unsafe trait implementations should be justified (See
"Every unsafe
block has an accompanying justification"))
Do
/// A labeler that always returns unique labels.
///
/// # Safety
///
/// Every time `create_unique_label()` is called on the same labeler, it must
/// return a distinct `u32`.
unsafe trait UniqueLabeler {
/// Returns a new unique label.
fn create_unique_label(&mut self) -> u32;
}
struct SequentialLabeler {
next: Option<u32>,
}
// SAFETY: `create_unique_label()` will always return the next sequential label
// or panic if all available labels are exhausted.
unsafe impl UniqueLabeler for SequentialLabeler {
fn create_unique_label(&mut self) -> u32 {
if let Some(next) = self.next {
self.next = (next < u32::MAX).then(|| next + 1);
next
} else {
panic!("sequential unique labels exhausted");
}
}
}
Don't
/// A labeler that always returns unique labels.
unsafe trait UniqueLabeler {
/// Returns a new unique label.
fn create_unique_label(&mut self) -> u32;
}
struct SequentialLabeler {
next: u32,
}
unsafe impl UniqueLabeler for SequentialLabeler {
fn create_unique_label(&mut self) -> u32 {
// This will have correct panicking behavior in debug builds because
// integer overflow is trapped. In a release build, this will still
// overflow but our labeler will not panic!
let result = self.next;
next += 1;
result
}
}
Unsafe operations are always in an unsafe
block
unsafe
functions are not considered unsafe contexts in Fuchsia. Unsafe operations must always be
located inside an unsafe
block, even if they are in an unsafe
function body.
Do
unsafe fn clear_slice(ptr: *mut i32, len: usize) {
assert!(len.checked_mul(mem::size_of::<i32>()).unwrap() < isize::MAX);
// SAFETY:
// - The caller has guaranteed that `ptr` points to `len` consecutive, valid
// i32s and that the data at behind `ptr` is not simultaneously accessed
// through any other pointer.
// - We asserted that the total size of the slice is less than isize::MAX.
let slice = unsafe { slice::from_raw_parts_mut(ptr, len) };
for x in slice.iter_mut() {
*x = 0;
}
}
Don't
unsafe fn clear_slice(ptr: *mut i32, len: usize) {
// We forgot to assert that the length of the slice is less than isize::MAX!
// If we justified our call to from_raw_parts_mut, we would have been much
// more likely to remember.
let slice = slice::from_raw_parts_mut(ptr, len);
for x in slice.iter_mut() {
*x = 0;
}
}
Predictability
Smart pointers do not add inherent methods
See C-SMART-PTR.
Conversions live on the most specific type involved
See C-CONV-SPECIFIC
Functions with a clear receiver are methods
See C-METHOD.
Functions do not take out-parameters
See C-NO-OUT.
Operator overloads are unsurprising
See C-OVERLOAD.
Only smart pointers implement Deref
and DerefMut
See C-DEREF.
Constructors are static, inherent methods
See C-CTOR.
Flexibility
Functions expose intermediate results to avoid duplicate work
See C-INTERMEDIATE.
Caller decides where to copy and place data
See C-CALLER-CONTROL.
Functions minimize assumptions about parameters by using generics
See C-GENERIC.
Traits are object-safe if they may be useful as a trait object
See C-OBJECT.
Type safety
Newtypes provide static distinctions
See C-NEWTYPE.
Arguments convey meaning through types, not bool
or Option
See C-CUSTOM-TYPE.
Types for a set of flags are bitflags
, not enums
See C-BITFLAG.
Builders enable construction of complex values
See C-BUILDER.
Dependability
Functions validate their arguments
See C-VALIDATE.
Destructors never fail
See C-DTOR-FAIL.
Destructors that may block have alternatives
See C-DTOR-BLOCK.
Debuggability
All public types implement Debug
See C-DEBUG.
Debug
representation is never empty
See C-DEBUG-NONEMPTY.
Future Proofing
Sealed traits protect against downstream implementations
See C-SEALED.
Structs have private fields
See C-STRUCT-PRIVATE.
Newtypes encapsulate implementation details
See C-NEWTYPE-HIDE.
Data structures do not duplicate derived trait bounds
See C-STRUCT-BOUNDS.
Updating the guidelines
To propose additions or modifications, open a CL and cc
fuchsia-rust-api-rubric@google.com
to ensure it is reviewed. Use any
feedback to iterate on the proposal.
Once feedback has been addressed, any one of the OWNERS of this file who is
not the proposal author may act as facilitator and move the proposal to last
call. The facilitator will send an email to fuchsia-rust-api-rubric@google.com
announcing last call on the proposal. The proposal will be open for feedback
for 7 calendar days.
At the end of the last call period and once relevant concerns have been discussed or addressed, a facilitator will comment on the CL with a final decision based on the review feedback and discussion. Any controversial decisions should be made with adequate public discussion of the relevant issues, and should include the rationale in the CL comment. The decision outcome should also be sent to the email thread.
If a proposal is accepted, the facilitator will leave a +2 and the author can then submit it.
Pending Topics
Pending topics are tracked in the Rust Issue Tracker component.
Relationship with upstream Rust API guidelines
This rubric contains most of the Rust API Guidelines, however the following official guidelines are omitted:
- C-FEATURE as Fuchsia does not currently support features for crates.
- C-METADATA as Fuchsia
does not maintain internal
Cargo.toml
files. - C-HTML-ROOT as
Fuchsia does not currently publish most Rust code to
crates.io
. - C-RELNOTES as most Rust code in Fuchsia "lives at HEAD".
- C-STABLE as Fuchsia does
not currently publish most Rust code to
crates.io
. - C-PERMISSIVE as all of Fuchsia's Rust code is under the Fuchsia license.
The following Fuchsia-specific guidelines are included:
- Every
unsafe
block has an accompanying justification - Unsafe traits are documented, unsafe trait impls are justified
- Unsafe operations are always in an
unsafe
block