[SOLVED] What is Safety?

All through the literature on Rust there is mention of Safety.
But what is Safety?
I think of Safety as protection from evil-doers on the Internet but apparently in the context of writing Rust code, it has to do with computational errors; memory leaks maybe or something.
If Rust is so safe then what is it safe from?

I am not a computer scientist.

2 Likes

Purely from a standpoint of terminology, generally, "safety" in Rust means safe from undefined behavior.

Basically, undefined behavior is a contract on the programmer; a compiler promises to produce a reasonably efficient program that performs the instructions written in the code on the condition that the programmer doesn't do X. The compiler has no obligation to verify that X never occurs (because it may be impossible), and it is given free reign to do anything if it does occur.

This makes bugs caused by undefined behavior often extremely difficult to debug.


Now of course, ultimately this definition doesn't say much, since the definition of "undefined behavior" depends on the language, and behavior which is "defined but clearly nonsense" (see == in JavaScript) is not much better!

So moving away from terminology: Personally, when I call Rust a safe language, I mean that Rust is safer from mistakes. Rust forces me to think upfront about many edge cases that I would otherwise forget about, because it has many good abstractions that encourage correct thinking.

In the end, I find that code I write in rust has fewer bugs to debug than similar code in C/C++, and is more likely to work properly on the first run.

No doubt this is very different from your definition of "safe!"

3 Likes

Safety from out-of-bounds reads/writes, use-after-free, null pointer dereference and data races. All of the above lead to potentially exploitable behavior.

2 Likes

Unless you explicitly opt-in to unsafe behavior (using an unsafe block), access to program state will be constrained by the type system.

For example, consider the following code:

mod non_zero {
    pub struct NonZero {
        value: u32,
    }
    impl NonZero {
        pub fn new(value: u32) -> NonZero {
            if value == 0 {
                panic!("Tried to construct a NonZero with a zero value");
            }
            NonZero { value: value }
        }
        pub fn set(&mut self, value: u32) {
            if value == 0 {
                panic!("Tried to write a zero to a NonZero");
            }
            self.value = value;
        }
        pub fn get(&self) -> u32 {
            self.value
        }
    }
}

fn main() {
    use self::non_zero::NonZero;
    // Here we can *only* call public methods on `NonZero`. We can't mess with the internals without using `unsafe`.
    /* ... do something ... */
}

Assuming there's no other code in the non_zero module, rust guarantees that calling the get() method on a NonZero will never return a zero (unless you use an unsafe block) because there's no way to set it to zero using a public method on NonZero.

It's also possible to exploit a bug in the rust compiler, LLVM, or, ask the operating system to mess with your program's memory.

The Rust Reference has moved is the documentation on this.

Unsafe operations are those that potentially violate the memory-safety guarantees of Rust's static semantics.

  • Data races
  • Dereferencing a null/dangling raw pointer
  • Reads of undef (uninitialized) memory
  • Breaking the pointer aliasing rules with raw pointers (a subset of the rules used by C)
  • &mut T and &T follow LLVM’s scoped noalias model, except if the &T contains an UnsafeCell<U>. Unsafe code must not violate these aliasing guarantees.
  • Mutating non-mutable data (that is, data reached through a shared reference or data owned by a let binding), unless that data is contained within an UnsafeCell.
  • Invoking undefined behavior via compiler intrinsics:
    • Indexing outside of the bounds of an object with std::ptr::offset (offset intrinsic), with the exception of one byte past the end which is permitted.
    • Using std::ptr::copy_nonoverlapping_memory (memcpy32/memcpy64 intrinsics) on overlapping buffers
  • Invalid values in primitive types, even in private fields/locals:
    • Dangling/null references or boxes
    • A value other than false (0) or true (1) in a bool
    • A discriminant in an enum not included in the type definition
    • A value in a char which is a surrogate or above char::MAX
    • Non-UTF-8 byte sequences in a str
  • Unwinding into Rust from foreign code or unwinding from Rust into foreign code. Rust's failure system is not compatible with exception handling in other languages. Unwinding must be caught and handled at FFI boundaries.

Thanks to all.
I would mark this issue as closed if I could find a way.

You can change the title from

What is Safety?

to

[SOLVED]What is Safety?

Willi

thanks

Joe

deleted by author

Please note the difference between Safety and Security. Some languages (like German) don't distinguish between those two. Safety is protection from undefined behavior. Security is protection from evil doers. In fact both are closely related and often security mechanisms are safety mechanisms and vice versa.

Rust's memory safety is in fact acting as a security mechanism, protecting your software from attacks like buffer overflow, buffer underrun and other attacks on your heap and stack. Given, of course, that all the unsafe code in your application is correct.

It could also be worth pointing out that safety is a subset of security. E.g. while Rust safety measures can help prevent certain kinds of security flaws, it cannot guarantee that it will catch every possible security flaw.

Consider a contrived example where an authorization mechanism uses a weak cipher. That's well-defined code, safe from buffer overflows and the like, but the weak cipher can be attacked in other ways that are outside the scope of language, compile-time, or run-time protections.

1 Like

That's just encapsulation and controlling invariants. You can do that in C++ just as easily and better.

No, type safety and memory safety are mathematically equivalent in rust (not memory safe → not type safe && not type safe → not memory safe). I just prefer talking about type safety because that's what users really care about. You don't care about segfaulting, you care about the bugs that lead to segfaulting (the bugs that violate type safety). As a matter of fact, segfaults are a feature to stop an out-of-control program from doing any more damage.

Note: In C++, you can trivially violate type safety by casting to a char * and manually messing with the underlying memory.

Rust doesn't even have any way to hide struct members. What is preventing me from just editing that struct and messing with it's invariants? Definitely not it's strong side.

They're hidden by default (from other modules); you need to prefix them with pub to make them accessible. Without this, rust couldn't provide any safety (you could just change internal pointers at will).

3 Likes