In what way are you more limited by the compiler in Rust than with C? Just write "unsafe" and you're off to the races.
Writing correct C is very hard and most definitely not fun. It's like juggling with with 7 balls and if you drop one you'll be shot. C is defined for a weirdo abstract machine that doesn't match what computers really do, and when people apply their intuition and knowledge about computers to their C programs "because C is low-level", it's a crapshoot whether they will trigger undefined behavior and the compiler goes off the rails with wild optimizations.
If I designed a low-level language I would enable such optimizations by making it easy to communicate your precise intent to the compiler. Not by making the standard a minefield of undefined behaviors.
> Just write "unsafe" and you're off to the races.
I'm pretty sure I mention this in the LWN comments, but, since it gets repeated so often the contradiction might as well be repeated as well:
No. Unsafe Rust only gets to do three things that aren't related to the "unsafe" keyword itself. It can dereference raw pointers, it can access C-style unions, it can mutate global static variables.
That's everything. Your C program is free to define x as an array with four elements and then access x[6] anyway - but Rust deliberately cannot do that. Not in Safe Rust, but also not in Unsafe Rust either. Writing "unsafe" doesn't mean "Do this anyway" it only unlocks those three specific things I mentioned, and so sure enough x[6] is still not allowed because that's a buffer overflow.
In fact by default the Rust compiler would warn you, if you write unsafe { foo[z] = 0; } that unsafe isn't doing anything useful here and you should remove it. That array dereference either is or, if z is small enough, is not, an overflow, and either way unsafe makes no difference.
> Your C program is free to define x as an array with four elements and then access x[6] anyway - but Rust deliberately cannot do that. Not in Safe Rust, but also not in Unsafe Rust either.
Woot?
fn main() {
let a = [0, 1, 2];
let _b = [42, 42, 42];
println!("{}", unsafe{a.get_unchecked(6)});
}
Calling get_unchecked on this array ends up as get_unchecked on the slice containing that whole array, which ends up as as get_unchecked on the slice index, and in the end it is...
Dereferencing a raw pointer. One of the three specific things I said unsafe Rust can in fact do.
This is not a "funny syntax" thing, a[6] is the idiomatic and obvious way to express this in Rust, and, it isn't allowed because it's a buffer overflow. Whereas a[6] is also the idiomatic and obvious way to express this in C and the result is Undefined Behaviour.
The claim was about `x[6]`, which does not appear in your program. The point is that `[]` is always bounds-checked, and the bounds-checking cannot be opted out of even with `unsafe`.
By explicitly doing it so, in an operation that is easy to grep for, or in the case of a binary library, search for the symbol during the linking phase.
Something that is impossible to validate in C, unless one is using a custom compiler, like Apple is doing for iBoot firmware.
> By explicitly doing it so, in an operation that is easy to grep for, or in the case of a binary library, search for the symbol during the linking phase.
Most of the time, these operations will be inlined, so they will already be gone by the time it gets to the linker. The compiler phase is the latest point where they are still visible.
>No. Unsafe Rust only gets to do three things that aren't related to the "unsafe" keyword itself.
>[...]
>Your C program is free to define x as an array with four elements and then access x[6] anyway - but Rust deliberately cannot do that. Not in Safe Rust, but also not in Unsafe Rust either.
>[...]
>In fact by default the Rust compiler would warn you, if you write unsafe { foo[z] = 0; } that unsafe isn't doing anything useful here and you should remove it. That array dereference either is or, if z is small enough, is not, an overflow, and either way unsafe makes no difference.
Since the conversation is claiming that you can do everything in Rust that you can do in C, I want to provide some counter-nuance. :) I am guessing what people actually mean is that all the operations you want to do can be done via unsafe Rust somehow, and yes, you can do that. But also yes, it is not literally "just write 'unsafe'". You do need to use raw pointers.
For instance, if you want to overflow a buffer intentionally,
fn main() {
let mut a = [1, 2, 3, 4];
let b = [5, 6, 7, 8];
let ptr: *mut i32 = &mut a[0];
unsafe { ptr.add(5).write(999); }
println!("{:?}", b);
}
(Note that this is not just extremely platform-specific and compiler-specific about whether a is in front of b or vice-versa, it is straight-up Undefined Behavior because you write past the end of an object... but the equivalent C code is also Undefined Behavior, and subject to the same LLVM optimizations. So if you were happy with the corresponding C code, this is the equivalent Rust.)
If you really, really want, you can write your own UnsafeSlice type that does the unsafe stuff internally and exposes the standard indexing operator, which would make foo[z] actually accept arbitrary indices just like in C. But you shouldn't. https://play.rust-lang.org/?version=stable&mode=debug&editio...
(Among other things, a code reviewer should be suspicious of your use of "unsafe" in the internals of a thing without stating why the higher-level abstraction is safe, and in fact the abstraction is wildly unsafe here, so it's bad style to write code that launders the unsafety, so to speak. In the Rust for Linux patches, there are "SAFETY" comments above each use of "unsafe" defending their logical safety.)
UnsafeSlice is a terrible idea, but let us at least give it the normal ergonomics of a wrapper type so we can say UnsafeSlice(&mut a) rather than needing curly braces to make one :)
You don't need anymore than that to match what C offers, C as in standard C, not in the folklore of C as a portable assembler. In fact you're freer in Rust than in C, because there is a simple, defined way to type-pun memory in Rust.
> Your C program is free to define x as an array with four elements and then access x[6] anyway
It's free to do anything, but you can't be sure that it will do that, because of the utterly weak specification.
Note that the C version of this might compile but the result is undefined, so it could be what you intuitively thought what would happen, or anything else.
Writing correct C is very hard and most definitely not fun. It's like juggling with with 7 balls and if you drop one you'll be shot. C is defined for a weirdo abstract machine that doesn't match what computers really do, and when people apply their intuition and knowledge about computers to their C programs "because C is low-level", it's a crapshoot whether they will trigger undefined behavior and the compiler goes off the rails with wild optimizations.
If I designed a low-level language I would enable such optimizations by making it easy to communicate your precise intent to the compiler. Not by making the standard a minefield of undefined behaviors.