Hacker News new | ask | show | jobs
by mysterydip 1054 days ago
What does "written in safe Rust as much as possible" mean? Are there functions with no equivalent in rust?
1 comments

Interfacing with hardware means you have to drop to "unsafe" rust at some (few) points.

"Unsafe" rust isn't named good, because it's still safer than e.g. C.

Some invariants you cannot break in rust, no matter if "safe" or "unsafe"

> it's still safer than e.g. C.

This is arguable and I think overall it's actually harder to correctly write unsafe Rust, even if sometimes maybe in some sense safer than C when you screw up.

In Rust everything has to obey Rust's semantic constraints. For safe Rust that's fine because the language itself promises you're obeying. You can't introduce anything which would be a problem, so you needn't even care what those problems are.

But in unsafe Rust you are responsible for the same guarantees that safe Rust gave everybody. And the rules you're responsible for obeying are truly difficult so that you may not properly understand them. If you screw up, that's instantly Undefined Behaviour.

Let's take a fairly old but brutal real example from Rust's standard library. core::mem::uninitialized<T>(). This function is labelled deprecated (as well as unsafe) in your Rust, but once upon a time it was the usual way to make some uninitialized buffer in which to construct something.

But it was actually UB almost always†. Because what it says is, OK, I know I didn't initialize a T, but trust me, I'll sort that out later, lets say this is a T anyway. And for a time people persuaded themselves that this is OK for at least some types. After all, if T was u8 (a byte) then who cares what its value is, any value is valid, isn't it? Well, yes, but "uninitialized" isn't a value, it's a 257th possible state, the compiler knows we didn't initialize this, and therefore all optimisations are valid even if they wouldn't be valid for any possible initialized state of the memory - we didn't initialize it so we're not entitled to assume it had any of those values.

In C you will get away with this but in Rust you've created Undefined Behaviour, which is not OK. Today you would use the MaybeUninit<T> type so that you can explicitly initialize it (once you have something to initialize it with) and then MaybeUninit::assume_init() to get your T instead now that it's initialized, and (if you did it correctly) that is safe.

† If T is a Zero Size Type then this function isn't dangerous, because it makes nothing and then says this nothing is actually a T, and the compiler says well, thanks for telling me, I don't really care but whatever. No UB. Likely this only happens in generic code, but it's safe.

part of the problem is that rust has not yet a standardized memory model (there are candidates, wip)

this means there are limits to soundness analysis tools and guardrails you can provide ins table rust

through there had been pretty convincing examples about how under some of the (more promising) memory model candidates you can provide additional/different functions which are much harder to accidentally misuse

and soundness analysis tools do already exist, too

I believe that rust has the _potential_ to make it easier to write a lot of unsafe code correctly in rust then in C -- in the future.

Through the issue with people using a "it's only bits" mind set when doing unsafe code stays around, and is wrong, not just in rust but in C, too. No matter how much some people try to pretend C is a high level assembly.

> Some invariants you cannot break in rust, no matter if "safe" or "unsafe"

a better description IMHO is that unsafe rust enables additional functions which are normally not usable as they can create unsoundness if used incorrectly

because this are just additional functions it means also the checks and type safety of all other code is still always there and normally sound, as long as you don't misuse the additional functions to brake invariants

I do count pointer dereferencing as a function converting a pointer to a reference in this context, it's technically not quite right, but conceptually not really wrong either.

So e.g. unsafe rust don't allow you to write to a &T (immutable reference), but using unsafe you could technically bit-wise transmute the &T into a &mut T (100% guaranteed unsound!!!) and write to that. Still at any point all constraints when handling &T did still apply and so do constraints of handling &mut T, you just (unsoundly) converted one into another using an additional function unlocked by using unsafe.

Would it be more aptly named “risky rust”?
Yes! Another apt name would be "trustme". (The normal case in Rust is trust the compiler - and all the people who wrote "trustme" code that you depend on!)