Re: C aggregate passing (Rust kernel policy)

Miguel Ojeda
2025-02-25T22:45:22Z

On Tue, Feb 25, 2025 at 9:25 PM Linus Torvalds
<torvalds@linux-foundation.org> wrote:
>
> No, that's most definitely NOT the whole point of unsafe.

There are a few viewpoints here, which can be understood as correct in
different senses.

It is true that unsafe Rust is supposed to be used when you cannot
implement something in safe Rust (be it because the safe subset does
not support it or for performance reasons). In that sense, the point
of unsafe is indeed to expand on what you can implement.

It is also true that `unsafe` blocks in Rust are just a marker, and
that they don't change any particular rule -- they "only" enable a few
more operations (i.e the only "rule" they change is that you can call
those operations). Of course, with those extra operations one can then
implement things that normally one would not be able to.

So, for instance, the aliasing rules apply the same way within
`unsafe` blocks or outside them, and Rust currently passes LLVM the
information which does get used to optimize accordingly. In fact, Rust
generally passes so much aliasing information that it surfaced LLVM
bugs in the past that had to be fixed, since nobody else was
attempting that.

Now, the thing is that one can use pointer types that do not have
aliasing requirements, like raw pointers, especially when dealing with
`unsafe` things. And then one can wrap that into a nice API that
exposes safe (and unsafe) operations itself, e.g. an implementation of
`Vec` internally may use raw pointers, but expose a safe API.

As an example:

fn f(p: &mut i32, q: &mut i32) -> i32 {
*p = 42;
*q = 24;
*p
}

optimizes exactly the same way as:

fn f(p: &mut i32, q: &mut i32) -> i32 {
unsafe {
*p = 42;
*q = 24;
*p
}
}

Both of them are essentially `restrict`/`noalias`, and thus no load is
performed, with a constant 42 returned.

However, the following performs a load, because it uses raw pointers instead:

fn f(p: *mut i32, q: *mut i32) -> i32 {
unsafe {
*p = 42;
*q = 24;
*p
}
}

The version with raw pointers without `unsafe` does not compile,
because dereferencing raw pointers is one of those things that unsafe
Rust unblocks.

One can also define types for which `&mut T` will behave like a raw
point here, too. That is one of the things we do when we wrap C
structs that the C side has access to.

Cheers,
Miguel