Most libraries rely on internal invariants, e.g. about their data, resource ownership, or protocol states. In Rust, broken invariants cannot produce segfaults, but they can still lead to wrong answers.
Library-level invariants should be turned into guarantees whenever practical. They should hold no matter what the client does, modulo explicit opt-outs. Depending on the kind of invariant, this can be achieved through a combination of static and dynamic enforcement, as described below.
Guaranteeing invariants almost always requires hiding, i.e. preventing the client from directly accessing or modifying internal data.
For example, the representation of the str
`strtype is hidden, which means that any value of type
` type is hidden,
which means that any value of type str
`strmust have been produced through an API under the control of the
` must have been produced
through an API under the control of the str
`str` module, and these
APIs in turn ensure valid utf-8 encoding.
Rust's type system makes it possible to provide guarantees even while
revealing more of the representation than usual. For example, the
as_bytes()
`as_bytes()method on
` method on &str
`&str` gives a read-only view into the
underlying buffer, which cannot be used to violate the utf-8 property.
Malformed inputs from the client are hazards to library-level guarantees, so library APIs should validate their input.
For example, std::str::from_utf8_owned
`std::str::from_utf8_ownedattempts to convert a
` attempts to convert a u8
`u8slice into an owned string, but dynamically checks that the slice is valid utf-8 and returns
`
slice into an owned string, but dynamically checks that the slice is
valid utf-8 and returns Err
`Err` if not.
See the discussion on input validation for more detail.
Static enforcement provides two strong benefits over dynamic enforcement:
Sometimes purely static enforcement is impossible or impractical. In these cases, a library should check as much as possible statically, but defer to dynamic checks where needed.
For example, the std::string
`std::stringmodule exports a
` module exports a String
`String` type with the guarantee
that all instances are valid utf-8:
Any consumer of a String
`Stringis statically guaranteed utf-8 contents. For example, the
` is statically guaranteed utf-8 contents. For example,
the append
`appendmethod can push a
` method can push a &str
`&stronto the end of a
` onto the end of a String
`Stringwithout checking anything dynamically, since the existing
` without
checking anything dynamically, since the existing String
`Stringand
` and &str
`&str` are
statically guaranteed to be in utf-8.
Some producers of a String
`Stringmust perform dynamic checks. For example, the
` must perform dynamic checks. For example, the
from_utf8
`from_utf8function attempts to convert a
` function attempts to convert a Vec<u8>
`Vecinto a
` into a String
`String`, but
dynamically checks that the contents are utf-8.
Providing library-level guarantees sometimes entails inconvenience (for static checks) or overhead (for dynamic checks). So it is sometimes desirable to allow clients to sidestep this checking, while promising to use the API in a way that still provides the guarantee. Such escape hatches should only be be introduced when there is a demonstrated need for them.
It should be trivial for clients to audit their use of the library for escape hatches.
See the discussion on input validation for conventions on marking opt-out functions.