% Input to functions and methods
Prefer
fn foo(b: Bar) {
// use b as owned, directly
}
over
fn foo(b: &Bar) {
let b = b.clone();
// use b as owned after cloning
}
If a function requires ownership of a value of unknown type T
, but does not
otherwise need to make copies, the function should take ownership of the
argument (pass by value T
) rather than using .clone()
. That way, the caller
can decide whether to relinquish ownership or to clone
.
Similarly, the Copy
trait bound should only be demanded it when absolutely
needed, not as a way of signaling that copies should be cheap to make.
Prefer
fn foo(b: Bar) -> Bar { ... }
over
fn foo(b: Box<Bar>) -> Box<Bar> { ... }
for concrete types Bar
(as opposed to trait objects). This way, the caller can
decide whether to place data on the stack or heap. No overhead is imposed by
letting the caller determine the placement.
The fewer assumptions a function makes about its inputs, the more widely usable it becomes.
Prefer
fn foo<T: Iterator<i32>>(c: T) { ... }
over any of
fn foo(c: &[i32]) { ... }
fn foo(c: &Vec<i32>) { ... }
fn foo(c: &SomeOtherCollection<i32>) { ... }
if the function only needs to iterate over the data.
More generally, consider using generics to pinpoint the assumptions a function needs to make about its arguments.
On the other hand, generics can make it more difficult to read and understand a function's signature. Aim for "natural" parameter types that a neither overly concrete nor overly abstract. See the discussion on traits for more guidance.
Prefer either of
fn foo(b: &Bar) { ... }
fn foo(b: &mut Bar) { ... }
over
fn foo(b: Bar) { ... }
That is, prefer borrowing arguments rather than transferring ownership, unless ownership is actually needed.
Prefer
fn foo() -> (Bar, Bar)
over
fn foo(output: &mut Bar) -> Bar
for returning multiple Bar
values.
Compound return types like tuples and structs are efficiently compiled and do not require heap allocation. If a function needs to return multiple values, it should do so via one of these types.
The primary exception: sometimes a function is meant to modify data that the caller already owns, for example to re-use a buffer:
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize>
(From the Reader trait.)
Note: this material is closely related to library-level guarantees.
Rust APIs do not generally follow the robustness principle: "be conservative in what you send; be liberal in what you accept".
Instead, Rust code should enforce the validity of input whenever practical.
Enforcement can be achieved through the following mechanisms (listed in order of preference).
Choose an argument type that rules out bad inputs.
For example, prefer
fn foo(a: ascii::Ascii) { ... }
over
fn foo(a: u8) { ... }
Note that
ascii::Ascii
is a wrapper around u8
that guarantees the highest bit is zero; see
newtype patterns for more details on creating typesafe wrappers.
Static enforcement usually comes at little run-time cost: it pushes the
costs to the boundaries (e.g. when a u8
is first converted into an
Ascii
). It also catches bugs early, during compilation, rather than through
run-time failures.
On the other hand, some properties are difficult or impossible to express using types.
Validate the input as it is processed (or ahead of time, if necessary). Dynamic checking is often easier to implement than static checking, but has several downsides:
- Runtime overhead (unless checking can be done as part of processing the input).
- Delayed detection of bugs.
- Introduces failure cases, either via
fail!
orResult
/Option
types (see the error handling guidelines), which must then be dealt with by client code.
Same as dynamic enforcement, but with the possibility of easily turning off expensive checks for production builds.
Same as dynamic enforcement, but adds sibling functions that opt out of the checking.
The convention is to mark these opt-out functions with a suffix like
_unchecked
or by placing them in a raw
submodule.
The unchecked functions can be used judiciously in cases where (1) performance dictates avoiding checks and (2) the client is otherwise confident that the inputs are valid.
[FIXME] Should opt-out functions be marked
unsafe
?