Hacker News new | ask | show | jobs
by kibwen 620 days ago
> In the areas where Zig really shines, the equivalent code in Rust would probably have a lot of “unsafe” keywords which basically disables the memory safety features anyway.

This is a common misconception, but the `unsafe` keyword in Rust does not disable any of the features that enforce memory safety, rather it just unlocks the ability to perform a small number of new operations whose safety invariants must be manually upheld. Even codebases that have good reason to use `unsafe` in many places still extensively benefit from Rust's memory safety enforcement features.

4 comments

> the `unsafe` keyword in Rust does not disable any of the features that enforce memory safety, rather it just unlocks the ability to perform a small number of new operations

If you view the locks on those operations as guard rails ensuring memory safety, GP's phrasing makes sense: The unsafe keyword disables them.

I really wish they called it unchecked instead of unsafe, it would have cleared lots of misconceptions about it.

Still, I think rust is safer than zig (ReleaseSafe) is safer than zig (ReleaseFast) is equally safe as unchecked rust.

IMO "unchecked" is liable to cause the same sort of confusion; Rust is still performing all the usual checks, but we the programmer just introducing new things that must be manually upheld. I've come around to the notion that the keyword for the block should be `promise` (though 10 years ago this might have caused confusion with Javascript programmers), whereas the keyword for the function should remain as `unsafe`.
I like something such as “trusted”, but maybe that gives the wrong impression to newbies.
Maybe "unleashed" to connote a potentially dangerous power is loose.
Huh, are you really trying to say unsafe Rust is safe? By this logic, C is safe, it also just has "safety invariants that must be manually upheld."

Unsafe Rust is even less safe than C because the rules that must be manually upheld are stricter. For example in C you can create an invalid pointer and it's fine as long as you don't access it. In Rust you can't even create an invalid reference or you have already invoked unchecked undefined behavior.

There's no common misconception here. I think you're misunderstanding the quoted comment due to being overly pedantic.

> Huh, are you really trying to say unsafe Rust is safe?

I'm unclear what part of my comment would lead someone to such an extreme conclusion. As mentioned, the `unsafe` keyword is used to unlock new operations and create new safety invariants that must be manually upheld. Naturally, failure to manually uphold those new invariants would lead to memory unsafety. But an `unsafe` block introduces no unsafety by itself. Which is to say, if you take a working Rust program with no unsafe blocks, and then wrap the body of `main` in an unsafe block, this is a no-op; it does nothing.

> By this logic, C is safe, it also just has "safety invariants that must be manually upheld."

Certainly, this is true, and I'm not sure why anyone would think otherwise. The problem is not that it is theoretically impossible to write correct C; rather the problem is that it is empirically infeasible to do so at scale. By locking unsafe operations behind an unsafe block, Rust attempts to make it feasible to identify the areas of most concern in a codebase and focus attention on proving those areas correct manually.

> Unsafe Rust is even less safe than C because the rules that must be manually upheld are stricter.

Unfortunately this is another misconception, although it's understandable why one would think this. The rules for raw pointers in Rust are less strict than the rules for raw pointers in C, which is to say, manipulating raw pointers in Rust is safer than doing the same in C. The misconception here comes from the conflation of raw pointers with Rust's references, which do have more safety invariants to uphold, and for several years there were footguns to be found here due to language-level deficiences WRT the inability to avoid creating temporary references when working with uninitialized or unaligned memory. The good news is that this was addressed with the addition of std::mem::addr_of in Rust 1.51.

> For example in C you can create an invalid pointer and it's fine as long as you don't access it.

Unfortunately, this is incorrect, though it illustrates why raw pointer manipulation is more fraught in C than it is in Rust. In C, using pointer arithmetic to cause a pointer to point outside the bounds of an array (save for one element past the end) is undefined behavior, even if you never dereference that pointer. In contrast, this is not undefined behavior in Rust. As another example, comparing pointers from two different allocations with less-than/greater-than is undefined behavior in C, but this is not undefined behavior in Rust.

> There's no common misconception here. I think you're misunderstanding the quoted comment due to being overly pedantic.

I have seen this misconception arise regularly for years. If this is not what the parent commenter intended, then I apologize for misreading it. At the same time, I don't regret clarifying Rust's semantics for the benefit of people who may be unfamiliar with them. Surely it benefits us all to learn from each other.

or, in other words, code in an unsafe block is assumed by the compiler to be safe. safety here meaning rust’s specification of safety rules.
> code in an unsafe block is assumed by the compiler to be safe.

This is another instance of the same misconception. For every Rust operation that can exist outside of an `unsafe` block, Rust enforces memory safety even when that operation exists inside of an unsafe block. In other words, Rust does not assume that all code inside of an unsafe block is safe; e.g. you can neither disable the borrow checker nor disable bounds checking merely by wrapping code in an unsafe block.

What this means is that you still receive the benefits of Rust's normal safety guarantees even in the presence of unsafe blocks. Instead, what unsafe blocks do is allow you to invent your own safety invariants to layer on top of Rust's ordinary semantics (which is also what you're doing in C and Zig).

Right but specifically it’s about being able to violate certain invariants you can’t otherwise and that’s it. Namely

* Call unsafe functions

* do memory aliasing

* change the lifetime the compiler sees

That’s about it. The syntax and rules otherwise are still rust and violating those rules (eg aliasing in a way not allowed by rust) still results in UB. This can surprise some rust people even within popular crates and stdlib