ring inherited C, C++, and assembly language code from BoringSSL, and the style guidelines for that code are in the second section of this document.
ring Style Guide (for code not in crypto/)
ring usually follows the Rust Guidelines, but there are some differences and ring adds additional guidelines.
In general, avoid nesting modules and avoid exporting any non-module items from
the main ring
crate. Instead, prefer a flat module structure that is one
level deep. Thus, for example, we have ring::digest::SHA256
but not
ring::SHA256
or ring::digest::sha256::SHA256
or ring::digest::sha2::SHA256
.
Sometimes it is useful to break up a module's source code into multiple files.
In this case, it is useful to make use of the Rust visibility rule where a
submodule can use non-public items defined in the enclosing module. In that
case, it is OK to use nested submodules. The nested submodules must be
non-public (mod x
, not pub mod x
) and the enclosing module must re-export,
using pub use submodule::x
, the items that are intended to be public. This
way, the implementation details that drove the choice to use nested submodules
do not affect the public API.
Note that this is only necessary when the module has submodules.
Use Result<T, ()>
as the return type for functions that may fail. Never use
Option<T>
or bool
or other types as return values to indicate failure.
Result
is used because it is annotated #[must_use]
, so the Rust compiler
will not let callers silently ignore the return value of functions that return
Result
s.
ring functions generally do not report error codes for a variety of reasons;
when they fail, they only report that they fail. If a function only needs to
return a boolean indicator that it succeeded or failed, use Result<(), ()>
as
the return type.
If an external function (e.g. part of the Rust standard library) returns
Option<T>
to indicate failure, use ok_or(())
to map it to Result<T, ()>
.
Use the early-return-on-failure pattern using the ?
operator. Do not use
Result::or_else
, Result::and
, etc. to chain together strings of
potentially-failing operations.
// The return type is of the form `Result<_, ()>`, not `Option<_>` or something
// else.
fn good_example(x: u32, y: u32) -> Result<u32, ()> {
// * `ok_or` is used to map `Option<u32>` to `Result<u32, ()>` here.
let sum = x.checked_add(y).ok_or(())?;
// Early return is used.
foo(sum)?;
bar(sum)
}
When creating a slice from the start of a indexable value, use x[..n]
, not
x[0..n]
. Similarly, use x[n..]
, not x[n..x.len()]
for creating a slice
from a specific point to the end of the value.
When copying and filling arrays and slices, use the functions in ring::polyfill when possible.
Avoid using the as
operator. When using as
seems necessary, see if there is
already a safer function for doing the conversion in
ring::polyfill. If not, add one to ring::polyfill
.
The C code generally uses the C int
type as a return value, where 1 indicates
success and 0 indicates failure. The module ring::bssl contains
a transparent Result
type which should be used as the return type when
declaring foreign functions which follow this convention. A
ring::bssl::Result
should be converted to a core::result::Result
using the
pattern in the following example (note the placement of unsafe
):
extern {
unsafe_fn1() -> bssl::Result;
/* ... */
}
fn foo() -> Result<(), ()> {
Result::from(unsafe {
unsafe_fn2(when, the, entire, thing, does, not, fit, on, a, single,
line)
})?;
Result::from(unsafe {
unsafe_fn1() // Use the same style even when the call fits on one line.
})?;
// The return value of `foo` will be the mapped result of calling
// `unsafe_fn3`.
Result::from(unsafe {
unsafe_fn3()
})
}
In general, prefer using unsigned types over signed types, and prefer using
checked arithmetic (e.g. x.checked_add(y)
, x.checked_mul(y)
, etc.) over
unchecked arithmetic. Prefer using checked arithmetic over explicit bounds
checks. Example:
fn good_example(a: u64, b: u64) -> Result<u64, ()> {
let n = a.checked_add(b).ok_or(());
}
fn bad_example(a: u64, b: u64) -> Result<u64, ()> {
if usize::max_value() - a > b {
return Err(());
}
Ok(a + b)
}
In general, avoid using unsafe
whenever it is practical to do so. The ring
developers chose to use Rust because of the goodness of the safe subset; stuff
that requires unsafe
is generally better off being written in C or assembly
language code. Generally, this means that unsafe
is only used to call
functions written in C or assembly language. Even if your goal is to replace C
and/or assembly language code with Rust code, don't be afraid to leave, or even
add, C code to avoid adding a load of unsafe
Rust code.
In particular, prefer references and indexing (which is checked at runtime) to pointers and pointer arithmetic. Example:
fn good_example(x: &[u8], n: usize) {
unsafe {
unsafe_fn(x[n..].as_ptr()) // The compiler inserts bounds checks for us.
}
}
fn bad_example(x: &[u8], n: usize) {
unsafe {
// If we do things this way, the compiler won't do bounds checking for
// us. Also, since `offset` takes an `isize`, we have to do a cast from
// `usize` to `isize` which is potentially unsafe because an `isize`
// cannot hold every positive value of `usize`.
unsafe_fn(x.as_ptr().offset(n as isize))
}
}
When you must use unsafe
, minimize the scope of unsafe
. Example:
fn good_example() {
unsafe { unsafe_fn(); }
safe_fn();
unsafe { unsafe_fn(); }
}
fn bad_example() {
unsafe {
unsafe_fn();
safe_fn(); // No safe statements allowed in an unsafe block.
unsafe_fn();
}
}
But, don't go overboard:
fn ok_example(x: &[u8], n: usize) {
unsafe {
unsafe_fn1(x[n]); // `x[n]` is a safe expression
}
}
fn bad_example(x: &[u8], n: usize) {
let x_n = x[n]; // This is going overboard.
unsafe {
unsafe_fn1(x_n);
}
}