Source code style guide
Identifier names
Long versus abbreviated
We use unabbreviated identifier names to avoid ambiguity. Short (even single letter) variable names are allowed in lambdas (closures), in one-liners, and when the context allows it.
The shorter the scope the shorter the variable names, and the longer the function […] names. And vice versa.
Source: Twitter
The usage of well-known acronyms like CPU, TLS or OIDC are allowed.
|
|
Closures and one-liners
It should be noted that the second example is meant to illustrate the use of single letter variable names in closures. It does not reflect production-level Rust code. The snippet would be simplified in the real world. |
let length = parameter.map(|p| p.len());
let sum: usize = vec![Some(2), None, Some(4), Some(3), None]
.iter()
.filter(|o| o.is_some())
.map(|n| n.unwrap())
.map(|n| n * 2)
.sum()
Optional function parameters and variables
Optional function parameters and variables containing Option
must not use any prefixes or suffixes to indicate the value is of type Option
.
This rule does not apply to function names like Client::get_opt()
.
|
|
Structs and enums
Naming convention
Structs can use singular and plural names.
Enums must use singular names, because only one variant is valid, e.g. Error::NotFound
and not Errors::NotFound
.
|
|
Formatting of struct fields and enum variants
We add newlines to struct fields and enum variants when they include additional information like documentation comments or attributes, because the variants can become difficult to read.
This is especially the case when fields include doc comments, attributes like #[snafu()]
, and in case of enum variants, various embedded types.
Enum variants and struct fields don’t need to be separated when no additional information is attached to any of the variants or fields.
|
|
Any single uncommented variants or fields in an otherwise-commented enum or struct is considered to be a smell. If any of the items are commented, all items should be. It should however also be noted that there is no requirement to comment fields or variants. Comments should only be added if they provide additional information not available from context.
Error handling
Choice of error crate and usage
We use snafu
for all error handling in library and application code because we want to provide as much context to the user as possible.
Further, snafu
allows us to use the same source error in multiple error variants.
This feature can be used for cases were we need / require more fine-grained error variants.
This behaviour is not possible when using thiserror
, as it uses the From
trait to convert the source error into an error variant.
Additionally, we restrict the usage of the #[snafu(context(false))]
atrribute on error variants.
This ensures that fallible functions need to call .context()
to pass the error along.
The usage of thiserror
is considered invalid.
|
|
Error messages
All our error messages must start with a lowercase letter and must not end with a dot. It is recommended to start the error messages with "failed to…" or "unable to …".
|
|
Examples for "failed to …" error messages
-
failed to parse config file
to indicate the parsing of the config file failed, usually because the file doesn’t conform to the configuration language. -
failed to construct http client
to indicate we wanted to construct a HTTP client to retrieve remote content.
String formatting
Named versus unnamed format string identifiers
For simple string formatting (up to two substitutions), we allow unnamed (and thus also uncaptured) identifiers.
For more complex formatting (more than two substitutions), we require named identifiers to avoid ambiguity, and to decouple argument order from the text (which can lead to incorrect text when the wording is changed and {}
are reordered while the arguments aren’t).
This rule needs to strike a balance between explicitness and concise format!()
invocations.
Long format!()
expressions can lead to rustfmt breakage.
It might be better to split up long formatting strings into multiple smaller ones.
Mix-and-matching of named versus unnamed identifiers must be avoided. See the next section about captured versus uncaptured identifiers.
|
|
Captured versus uncaptured format string identifiers
We place no restriction on named format string identifiers. All options below are considered valid.
let greetee = "world";
format!("Hello, {greetee}!");
format!("Hello, {greetee}!", greetee = "universe");
format!("Hello {name}, hello again {name}", name = greetee);
Specifying resources measured in bytes and CPU fractions
We follow the Kubernetes convention described here.
Resources measured in bytes
|
|
Resources measured in CPU fractions
|
|