Standards for use of unsafe Rust in the kernel
Rust is intended to let programmers write safer code. But compilers are not omniscient, and writing Rust code that interfaces with hardware (or that works with memory outside of Rust's lifetime paradigm) requires, at some point, the programmer's assurance that some operations are permissible. Benno Lossin suggested adding some more documentation to the Rust-for-Linux project clarifying the standards for commenting uses of unsafe in kernel code. There's general agreement that such standards are necessary, but less agreement on exactly when it is appropriate to use unsafe.
Lossin's proposed documentation starts by describing the purpose of the unsafe keyword in Rust: to explicitly signal when code relies on guarantees that are not checked by the compiler. Since programmers writing unsafe code are relying on properties that the compiler cannot help check, it is especially important to make sure that the properties are documented: both what they are, and why they hold.
These two different types of information about program safety actually correspond to two subtly different uses of the unsafe keyword in Rust, Lossin points out. Sometimes unsafe is used to signify that the programmer knows that they're using an operation the compiler can't guarantee is safe, and sometimes it is used to inform the compiler that it cannot fully understand when a newly-defined function is safe to call. Each kind of use comes with different things that should be documented; the former should have an explanation of why the operation is safe in this case, while the latter should explain what the requirements are to use the function safely.
The examples given in the patch set are somewhat sparse, however. Alice Ryhl asked for the documentation to show how to do it right:
I think it is worth explicitly pointing out that the safety comment must explain why the preconditions are satisfied, *not* what the preconditions are. It's a really really common mistake to mix up these, and it probably even makes sense to include two examples showing the difference.
Lossin agreed, noting that if his proposed documentation standards are acceptable to the other kernel developers, he plans to go through the tree and try to improve some existing safety comments, which should provide a good source of examples of what to do or not do.
The patches highlight several important aspects of writing safety documentation for a Rust project — such as the difference between safety and soundness. In Rust, safe code has several specific guarantees. It won't access uninitialized memory, have data races, mutate immutable constants, or engage in any of the other behaviors considered undefined in Rust — provided that all unsafe code used by the program is "sound". Unsafe code is considered sound when no combination of values passed through a safe abstraction (like a function call that is not marked unsafe) can make the unsafe code engage in undefined behavior.
Lossin's documentation notes:
"The term undefined behavior in Rust has a lot stricter
meaning than in C or C++: UB in Rust is totally forbidden.
" To be able to
truly rely on Rust's safety guarantees, all unsafe code must be sound, not
merely rely on the current behavior of the compiler happening not to cause
problems. Boqun Feng
worried about how that requirement would interact with the
Linux kernel memory model (LKMM):
Basically, since LKMM relies on a few things that C standard doesn't say, e.g. volatile accesses on certain types are atomic, behaviors of asm blocks, dependencies. Let alone we have data_race() where for example, the diagnostic code accesses the shared variable out of the core synchronization design.
Ultimately, Feng said, it would be better to teach Rust about some of the
special behaviors that the Linux kernel relies on. But right now, that's simply not
feasible, and sometimes the developers will need the freedom to act outside of
Rust's constraints, he continued. Lossin
disagreed, saying that the Rust code in the kernel was an opportunity to
start with a clean slate, and not to need to rely on any compiler-specific
behaviors. He did suggest it might be reasonable to talk about exceptions on a
case-by-case basis, but "'sometimes UB
is actually ok' is something that I don't want to accept in Rust as a
general statement.
"
Daniel Almeida approved of adding additional documentation, but thought that just documentation was not enough, suggesting that the project should use a linter to ensure that there are comments in the appropriate places. Miguel Ojeda pointed out that Clippy already supports such a check, so it would be easy to add to the kernel's build. A later message said that Ojeda had gotten consensus on it from other developers and would be posting a patch set to do so.
Almeida also raised concerns over a section of Lossin's documentation
that exhorted developers to centralize the use of unsafe blocks, in
order to make reasoning about their behavior easier.
"By all means,
let's strive to push as much of the unsafe bits into the kernel crate. But,
IMHO, we shouldn't treat Rust drivers as some unprivileged entity, they're
also kernel code, after all.
"
Ryhl disagreed with Almeida's interpretation, saying that Lossin's documentation is not as strict as Almeida makes it out to be. Danilo Krummrich likewise thought that there was no real problem with Lossin's recommendation, since Rust makes it possible to build abstractions that cover the most obvious use cases for unsafe in drivers. Everyone did agree, however, that this would likely make a good topic of discussion at Kangrejos, the upcoming Rust-for-Linux conference.
In any case, there is more work to be done before this documentation change is likely to be accepted. Lossin has already stated that he plans to incorporate some suggestions where the documentation is unclear, as well as adding more examples, and bringing the existing code up to the standard set in the documentation. All of those tasks are likely to be easier, however, than getting the Rust-for-Linux developers to agree on exact standards for unsafe code — a task that will certainly require further discussion.
