How unsafe is unsafe?

This may sound like a bizarre question at first, but I come from C programming, where everything is essentially “unsafe.”

I absolutely love the memory safety features of Rust and it’s the main reason that I use Rust. However, I do very low-level things and am heavily accustomed to using pointers. For example, I am currently writing a program which reads and parses a file format’s headers for learning experience.

In doing so, I ran into some complications implementing the From trait. I’m trying to implement From<& mut [u8]> for one of my file format’s header data structures, but From requires a from() which returns Self and not a Result<Self, Error> (I know there’s a TryFrom but I’m on stable). The From trait implementation would allow me to easily parse a byte vector or slice into a data structure.

This cornered me into calling .unwrap() on each of my calls to read_exact() when parsing each group of bytes into my structure members…

I decided to view how the byteorder crate handles some of this stuff and saw that they end up using unsafe code for most of this type of stuff.

So that raised a question - doesn’t unsafe essentially break Rust’s guarantees? How do I know that a crate which uses unsafe is trustworthy and not going to lead to undefined behavior or segfaults, etc…? I sorta felt like I was at square one again. However, I’ve also read that it’s possible to wrap unsafe code into a safe wrapper. I need to know for 2 reasons: 1) I’m going to be working with bytes a lot and 2) My argument for using Rust is largely memory-safety so if I start to introduce crates with a lot of unsafe usage, my peers may wonder what the benefit is in regards to memory-safety.

Thanks.

2 Likes

This is a matter of interpretation; but I'd say Rust guarantees no undefined behavior or data races due to bugs in safe code. If there are bugs in unsafe code, all bets are off.

In principle, you don't, unless you can verify that it contains no bugs.

In practice, I can't remember if I've ever run into a heisenbug caused by improper usage of unsafe in a library; though I sometimes do peek at library sources and create issues if I can identify a bug in their usage of unsafe, and I've stopped using ones where the author was slow to respond.

As for arguments for using Rust: Grepping a library for the word unsafe is certainly not something you can get away with in C++!

3 Likes

You can never have a guarantee, but if a large number of people are using it, it's likely good. More eyes means more confidence, for the most part.

2 Likes

I think this is the critical part: One tries to wrap up the unsafe stuff into a purely safe API for whatever it happens to be that you need, so you can verify that one mod/crate and not worry about the safe code that uses it.

3 Likes

At some point, you have to trust something to do unsafe things like access the hardware, or talk to the operating system.

Rust isn't about eliminating unsafe code. It's about containing it.

4 Likes

A slightly broader way to write this is

Rust guarantees no undefined behaviour or data races due to bugs outside the event horizon of unsafe code.

Some unsafe code may be relying on details of nearby safe code for correct execution. It is limited in scope and will definitely not reach outside a crate, in the majority of cases it will not even reach outside a module.

1 Like

I don’t agree with this at all. Lots of people use OpenSSL yet it still has very very bad bugs such as Heartbleed. Most users don’t look at a crate’s source code at all.

3 Likes

The keyword is not well named: it’s more like safety-is-still-guaranteed-but-by-programmer-not-the-compiler. It’s not supposed to allow any unsafety into the program.

Rust’s Vec is written using unsafe, and yet it’s safe to use.

2 Likes

Here’s one way to put it. Normally Rust’s safety guards prevent you from pointing the powerful weapons toward yourself. Unsafe lets you disable it for tasks which require you to point it towards yourself, but it’s still up to you to not pull the trigger while it’s pointed at your foot.

If you remember C and all the safety precautions programmers do to prevent their programs from blowing in their faces, that’s pretty much the level of caution that should be performed when you use unsafe.

1 Like

In practice unsafe actually means safety-is-CLAIMED-by-the-programmer-rather-than-GUARANTEED-by-the-compiler. @RalfJung recently found two cases of UB in library routines, and he’s found others previously. That’s one reason why he has developed and is extending miri, to permit checking whether non-FFI unsafe code actually avoids UB.

6 Likes

Though I regularly use it in my toy prj, I really don’t like the keyword unsafe. It’s too short, doesn’t match precisely with its meaning, and too short so I sometimes can’t block myself to spam unsafe block everywhere rather than properly isolate them.

The entire Rust ecosystem would be much safer if we can rename them to unsafe_but_trust_me_i_know_what_i_wrote. :stuck_out_tongue:

1 Like

There’re many good answers on “what does unsafe mean” but wanted to check with you on this bit:

You shouldn’t feel compelled to use From - you can define your own fn to do the conversion returning a Result<...>.

Unless you’re using some generic code that wants From (or Into), there’s no requirement to go with From in your code. Once TryFrom stabilizes, it’ll make cases like this much better though.

5 Likes

You don’t need to look at the source code to know there’s a bug. Incorrect behavior is another way! And the more people using a package, the more likely it is that problems will be discovered and fixed.

It’s named perfectly fine. unsafe permits doing many things which can lead to bugs including (memory and thread) safety errors, hence its use is absolutely, positively unsafe.

Now, despite that using unsafe is unsafe, sometimes you still need it or using it is worth the increased risk factor. It is also possible (and programmers are hoping to) write bug-free code using unsafe. This doesn’t make it any safer, and its name is entirely appropriate.

It’s not called guaranteed_ub or ensure_bugs_happen, after all, and that’s not what unsafe suggests. Don’t confuse the potential for introducing errors with the actual introduction of errors.

Well… I might have renamed unsafe { ... } to safe { ... } instead because you are in actuality not stating that the code is unsafe, but that you have manually proven the code to be safe and that the compiler should trust you… But; it’s not a big deal :slight_smile:

Well, in that case a safe keyword would be confusing. I would propose a safely { ... } for Rust 2.0.

The problem is the keyword unsafe has two disjoint usage in Rust: declare unsafety AND using unsafety.

Former part is simple. We can mark functions and traits as unsafe to indicate calling/implementing it is unsafe. Unlike safe context, compiler can’t enforce every assumptions and invariants this code requires, so the most(but not all!) sales points the Rust language advertises can’t be applied. But we can’t use them in safe rust normally, so it’s ok for now.

Problem is the latter part. We can mark block expressions and impl blocks to empower your code with some arcane black magic. unsafe impl is conceptically similar to unsafe block, so lets ignore it for now.

Within unsafe block, we can call any unsafe functions. As described above, unsafe functions are, ehem, unsafe. It’s certainly possible to produce memory safety violation with them if one didn’t read its docs extra carefully and write code strictly as-is.

But importantly, the unsafe block itself is located in safe context, so it MUST NOT produce any memory safety violation! That’s why I blamed its name and suggested such (intensionally) long and weird alternative. Of course it was a half joke, but who knows?

2 Likes