What Does Safety Mean?

The core concept to understand safe and unsafe is safety. The safety of safe code is verified by compiler, the safety of unsafe code is verified by programmers. But does safety has some specific meanings? From the information I found(from unsafe keyword doc, unsafe reference and book), safety means

  • memory safety
  • no undefined behaviour
  • no specific meanings, just a contract between programmers or between programmer and compiler

Which one is the intensional meaning of safety?

In the doc of keyword unsafe, section unsafe and traits, there is a demo trait MakeEven, which in my opinion doesn't involve memory safety issues and typically will not cause undefined behaviour. Is this usage of unsafe recommended or is it just for demonstration?

In my opinion, safety can be anything important, that violation of it can cause severe results. Am I correct?

This is what safety means. Safe is code verified by the compiler to be sound. Unsafe code is verified by the compiler and programmer together to be sound.

Sound code is code that will never exhibit undefined behavior when combined with any other sound code. A sound unsafe fn will never exhibit undefined behavior when combined with any sound code that upholds its requirements.

Undefined behavior is any code that has some runtime property that violates the Rust compiler's assumptions, which are here. Mainly: data races, incorrect pointer aliasing, writing read-only memory, reading uninitialized memory, and making invalid values.

So any unsafe code can be sound or unsound. Often people will use "unsound" and "undefined behavior" interchangeably, but you can understand "code with undefined behavior" to mean "code that when run (either run at all or run with certain inputs), will exhibit undefined behavior".


The unsafe keyword applies to the first two, not the last one. The last one cannot be enforced by the Rust compiler, since the language does not have formal contracts.

EDIT: You may also be asking about informal use of the work unsafe in a Rust context. To avoid confusion I hope it is always used to mean memory safety and UB, the same as the unsafe keyword, but of course people sometimes use it in more general sense.

1 Like

It has to be just a demonstration.

1 Like

If wrote this implementation

struct Nefarious;
unsafe impl MakeEven for Nefarious {
    fn make_even(&self) -> i32 {
        1 // Evil laugh

And someone else wrote this program:

// n.b. no unsafe
use make_even::*;
use nefarious::*;
fn main() {

The program would be UB because executing unreachable_unchecked is UB, and Nefarious::make_even returning 1 make the program flow logically reach the unreachable_unchecked.

The point of the example is that the implementor of MakeEven for Nefarious is to blame for not upholding the requirements in their unsafe impl. The author of use_make_even is not to blame even though they had an unsafe block, because they only relied on the unsafe trait requirements being upheld.

(The author of the program above cannot be to blame because their code has no unsafe.)


I would say no. As Rust uses it, it's about the avoidance of undefined behaviour.

There are lots of things that are defined, but might be bad. Deleting a file might be intentional, but it also might be malicious. Decrypting your bank password and sending it to the web might be exactly what's supposed to happen -- because you're logging in -- or might be horrible -- because you got a virus that's exfiltrating it.

But by staying in the domain of defined behaviour, you can trust things like testing, logging, code analysis, etc.

The evilness of UB is that you can't trust what you see. If you've hit UB, that "if password != expected" check that you can see clearly in the code might not actually happen. If you hit UB, it might look like it works fine when you test it, but then you change the version number to release it and now it doesn't work anymore. If you hit UB, what gets logged may or may not reflect what actually happened.

Safety is a necessary but not sufficient condition for Correctness. You can (obviously) still write code in safe Rust that doesn't do what you intended, but at least it's defined what it does so you can test it, have logs, etc.

(If you also care about lots of different kinds of relevant safety, see things like Two Kinds of Invariants: Safety and Validity about what it means more precisely for code to be safe.)


Ok, all, I think we are pretty clear now, safety means memory safety in rust, and violation of the Rust compiler's assumptions will produce undefined behavior. But there is still one thing I don't get. Since we can transform any assumption into a memory safety assumption, as in the MakeEven demo

if (!condition_hold()) {
    unsafe { std::hint::unreachable_unchecked() };

which means any trait with any postcondition that compiler can not verify can/should be defined as unsafe trait.

Only if that trait intends for unsafe code to rely on it. Otherwise that trait won't lead to UB.


No, it means that unsafe blocks cannot rely on postconditions from safe traits for safety.

Of course, for correctness you can rely on postconditations from safe traits, under the usual "garbage in garbage out" rule.

(For example, Rust's sort doesn't rely for memory safety on the postconditions of Ord::cmp. That's different from C++'s std::sort, which does in some implementations. But of course sort might not actually sort the items if Ord::cmp is implemented incorrectly.)


But don't we only really care about the cases where it is actually tied to a memory safety or UB assumption? I think those are the only cases where the unsafe keyword should be used.

I tried to describe one such case: the use of unsafe for code that enforces utf8 invariants.

1 Like

Ok, I think I get it now. Summary:

  • In Rust, safety specifically means memory safety.
  • We mark a trait as unsafe only when it intends for unsafe code to rely on it. We don't mark a trait as unsafe otherwise, even when we want to emphasize the importance of contracts.

Thank you all!


This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.