Yes. All `&mut` references in Rust are equivalent to C's `restrict` qualified pointers. In the past I measured a ~15% real world performance improvement in one of my projects due to this (rustc has/had a flag where you can turn this on/off; it was disabled by default for quite some time due to codegen bugs in LLVM).
I was confused by this at first since `&T` clearly allows aliasing (which is what C's `restrict` is about). But I realize that Steve meant just the optimization opportunity: you can be guaranteed that (in the absence of UB), the data behind the `&T` can be known to not change in the absence of a contained `UnsafeCell<T>`, so you don't have to reload it after mutations through other pointers.
Yes. It's a bit tricky to think about, because while it is literally called 'noalias', what it actually means is more subtle. I already linked to a version of the C spec below, https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf but if anyone is curious, this part is in "6.7.4.2 Formal definition of restrict" on page 122.
In some ways, this is kind of the core observation of Rust: "shared xor mutable". Aliasing is only an issue if the aliasing leads to mutability. You can frame it in terms of aliasing if you have to assume all aliases can mutate, but if they can't, then that changes things.
I used to use it, but very rarely, since it's instant UB if you get it wrong. In tiny codebases which you can hold in your head it's probably practical to sprinkle it everywhere, but in anything bigger it's quite risky.
Nevertheless, I don't write normal everyday C code anymore since Rust has pretty much made it completely obsolete for the type of software I write.
restrict works by making some situations undefined behavior that would otherwise be defined without it. It is probably unwise to use casually or habitually.
But of course the only thing restrict does in C is potentially introduce certain kinds of undefined behavior into a program that would be correct without it (and then things can be optimized on the assumption that the code is not invoked in a way that it would happen)
Aliasing info is gold dust to a compiler in various situations although the absence of it in the past can mean that they start smoking crack when it's provided.
The simplest example is `memcpy(dst, src, len)` and similar iterative byte copying operations. If the function did not use noalias, the compiler wouldn't be free to optimize individual byte read/writes into register-sized writes, as the destination may overlap with the source. In practice this means 8x more CPU instructions per copy operation on a 64-bit machine.
Note that memcpy specifically may already be implemented this way under the hood because it requires noalias; but I imagine similar iterative copying operations can be optimized in a like manner ad-hoc when aliasing information is baked in like it is with Rust.
That's not a great example, since memcpy() already has all the information it needs to determine whether the regions overlap (src < dest + len && dst < src + len) and even where and by how much. So pretty much any quality implementation is already performing this test and selecting an appropriate code path, at the cost of a single branch rather than 8x as many memory operations.
The real purpose of restrict is to allow the compiler to cache a value that may be used many times in a loop (in memcpy(), each byte/word is used only once) in a register at the start of the loop, and not have to worry about repeatedly reaching back to memory to re-retrieve it because it might have been modified as a side effect of the loop body.
Say you have 2 pointers (that might overlap). You (or the compiler) keep one value read from the first pointer in a register, since the value is needed multiple times.
You then write access the second pointer. Now the value you kept in the register is invalidated since you might have overwritten it through the overlapping pointers.
Yes. Specifically since Rust's design prevents shared mutablity, if you have 2 mutable data-structures you know that they don't alias which makes auto vectorization a whole lot easier.
what about generics (equivalent to templates in C++), which allow compile time optimizations all the way down which may not possible if the implementation is hidden behind a void*?
Unless you use `dyn`, all code is monomorphized, and that code on its own will get optimized.
This does come with code-bloat. So the Rust std sometimes exposes a generic function (which gets monomorphized), but internally passes it off to a non-generic function.
This to avoid that the underlying code gets monomorphized.
> This does come with code-bloat. So the Rust std sometimes exposes a generic function (which gets monomorphized), but internally passes it off to a non-generic function.
There's no free lunch here. Reducing the amount of code that's monomorphised reduces the code emitted & improves compile times, but it reduces the scope of the code that's exposed to the input type, which reduces optimisation opportunities.
In C, the only way to write a monomorphized hash table or array list involves horribly ugly macros that are difficult to write and debug. Rust does monomorphization by default, but you can also use &dyn trait for vtable-like behaviour if you prefer.