Re: C aggregate passing (Rust kernel policy)

Benno Lossin
2025-02-24T10:31:42Z

On 24.02.25 10:57, Ventura Jack wrote:
> On Sun, Feb 23, 2025 at 5:27 PM Gary Guo <gary@garyguo.net> wrote:
>>
>> On Sun, 23 Feb 2025 08:30:06 -0700
>> Ventura Jack <venturajack85@gmail.com> wrote:
>>
>>> - In unsafe Rust, it is the programmer's responsibility
>>> to obey the aliasing rules, though the type system
>>> can offer limited help.
>>> - The aliasing rules in Rust are possibly as hard or
>>> harder than for C "restrict", and it is not possible to
>>> opt out of aliasing in Rust, which is cited by some
>>> as one of the reasons for unsafe Rust being
>>> harder than C.
>>
>> The analogy is correct, you can more or less treat all Rust references
>> a `restrict` pointers. However it is possible to opt out, and it is
>> done at a per-type basis.
>>
>> Rust provides `UnsafeCell` to make a immutable reference mutable (i.e.
>> "interior mutability"), and this makes `&UnsafeCell<T>` behaves like
>> `T*` in C.
>>
>> There's another mechanism (currently under rework, though) that makes a
>> mutable reference behave like `T*` in C.
>>
>> RfL provides a `Opaque` type that wraps these mechanisms so it
>> absolutely cancel out any assumptions that the compiler can make about
>> a pointer whatsoever. For extra peace of mind, this is used for all
>> data structure that we share with C.
>>
>> This type granularity is very useful. It allows selective opt-out for
>> harder to reason stuff, while it allows the compiler (and programmers!)
>> to assume that, say, if you're dealing with an immutable sequence of
>> bytes, then calling an arbitrary function will not magically change
>> contents of it.
>>
>> Best,
>> Gary
>
> In regards to `UnsafeCell`, I believe that you are correct in regards
> to mutability. However, if I understand you correctly, and if I
> am not mistaken, I believe that you are wrong about `UnsafeCell`
> making it possible to opt-out of the aliasing rules. And thus that
> `UnsafeCell` does not behave like `T*` in C.

`UnsafeCell<T>` does not behave like `T*` in C, because it isn't a
pointer. Like Gary said, `&UnsafeCell<T>` behaves like `T*` in C, while
`&mut UnsafeCell<T>` does not. That is what you quote from the docs
below. (Those ampersands mark references in Rust, pointers that have
additional guarantees [1])

For disabling the uniqueness guarantee for `&mut`, we use an official
"hack" that the Rust language developers are working on replacing with
a better mechanism (this was also mentioned by Gary above).

[1]: https://doc.rust-lang.org/std/primitive.reference.html

> Documentation for `UnsafeCell`:
> https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html
>
> "Note that only the immutability guarantee for shared
> references is affected by `UnsafeCell`. The uniqueness
> guarantee for mutable references is unaffected. There is no
> legal way to obtain aliasing `&mut`, not even with `UnsafeCell<T>`."
>
> "Note that whilst mutating the contents of an `&UnsafeCell<T>`
> (even while other `&UnsafeCell<T>` references alias the cell) is
> ok (provided you enforce the above invariants some other way),
> it is still undefined behavior to have multiple
> `&mut UnsafeCell<T>` aliases."
>
> The documentation for `UnsafeCell` is long, and also mentions
> that the precise aliasing rules for Rust are somewhat in flux.
>
> "The precise Rust aliasing rules are somewhat in flux, but the
> main points are not contentious:"
>
> In regards to the `Opaque` type, it looks a bit like a C++
> "smart pointer" or wrapper type, if I am not mistaken.

It is not a smart pointer, as it has nothing to do with allocating or
deallocating. But it is a wrapper type that just removes all aliasing
guarantees if it is placed behind a reference (be it immutable or
mutable).

> Documentation and related links for `Opaque`:
> https://rust.docs.kernel.org/kernel/types/struct.Opaque.html
> https://rust.docs.kernel.org/src/kernel/types.rs.html#307-310
> https://github.com/Rust-for-Linux/pinned-init
>
> It uses `UnsafeCell`, Rust "pinning", and the Rust for Linux library
> "pinned-init".

pinned-init is not specific to `Opaque` and not really relevant with
respect to discussing aliasing guarantees.

> "pinned-init" uses a number of experimental, unstable and nightly
> features of Rust.

This is wrong. It uses no unstable features when you look at the version
in-tree (at `rust/kernel/init.rs`). The user-space version uses a single
unstable feature: `allocator_api` for accessing the `AllocError` type
from the standard library. You can disable the `alloc` feature and use
it on a stable compiler as written in the readme.

> Working with the library implementation requires having a good
> understanding of unsafe Rust and many advanced features of Rust.

pinned-init was explicitly designed such that you *don't* have to write
unsafe code for initializing structures that require pinning from the
get-go (such as the kernel's mutex). Yes, at some point you need to use
`unsafe` (eg in the `Mutex::new` function), but that will only be
required in the abstraction.
I don't know which "advanced features of Rust" you are talking about,
since a user will only need to read the docs and then use one of the
`[try_][pin_]init!` macros to initialize their struct.
(If you have any suggestions for what to improve in the docs, please let
me know. Also if you think something isn't easy to understand also let
me know, then I might be able to improve it. Thanks!)

> `Opaque` looks interesting. Do you know if it will become a more
> widely used abstraction outside the Linux kernel?

Only in projects that do FFI with C/C++ (or other such languages).
Outside of that the `Opaque` type is rather useless, since it disables
normal guarantees and makes working with the inner type annoying.

---
Cheers,
Benno