You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nomicon/meet-safe-and-unsafe.md

4.3 KiB

% Meet Safe and Unsafe

Programmers in safe "high-level" languages face a fundamental dilemma. On one hand, it would be really great to just say what you want and not worry about how it's done. On the other hand, that can lead to some really poor performance. It may be necessary to drop down to less clear or idiomatic practices to get the performance characteristics you want. Or maybe you just throw up your hands in disgust and decide to shell out to an implementation in a less sugary-wonderful unsafe language.

Worse, when you want to talk directly to the operating system, you have to talk to an unsafe language: C. C is ever-present and unavoidable. It's the lingua-franca of the programming world. Even other safe languages generally expose C interfaces for the world at large! Regardless of why you're doing it, as soon as your program starts talking to C it stops being safe.

With that said, Rust is totally a safe programming language.

Well, Rust has a safe programming language. Let's step back a bit.

Rust can be thought of as being composed of two programming languages: Safe and Unsafe. Safe is For Reals Totally Safe. Unsafe, unsurprisingly, is not For Reals Totally Safe. In fact, Unsafe lets you do some really crazy unsafe things.

Safe is the Rust programming language. If all you do is write Safe Rust, you will never have to worry about type-safety or memory-safety. You will never endure a null or dangling pointer, or any of that Undefined Behaviour nonsense.

That's totally awesome.

The standard library also gives you enough utilities out-of-the-box that you'll be able to write awesome high-performance applications and libraries in pure idiomatic Safe Rust.

But maybe you want to talk to another language. Maybe you're writing a low-level abstraction not exposed by the standard library. Maybe you're writing the standard library (which is written entirely in Rust). Maybe you need to do something the type-system doesn't understand and just frob some dang bits. Maybe you need Unsafe Rust.

Unsafe Rust is exactly like Safe Rust with all the same rules and semantics. However Unsafe Rust lets you do some extra things that are Definitely Not Safe.

The only things that are different in Unsafe Rust are that you can:

  • Dereference raw pointers
  • Call unsafe functions (including C functions, intrinsics, and the raw allocator)
  • Implement unsafe traits
  • Mutate statics

That's it. The reason these operations are relegated to Unsafe is that misusing any of these things will cause the ever dreaded Undefined Behaviour. Invoking Undefined Behaviour gives the compiler full rights to do arbitrarily bad things to your program. You definitely should not invoke Undefined Behaviour.

Unlike C, Undefined Behaviour is pretty limited in scope in Rust. All the core language cares about is preventing the following things:

  • Dereferencing null or dangling pointers
  • Reading uninitialized memory
  • Breaking the pointer aliasing rules
  • Producing invalid primitive values:
    • dangling/null references
    • a bool that isn't 0 or 1
    • an undefined enum discriminant
    • a char outside the ranges [0x0, 0xD7FF] and [0xE000, 0x10FFFF]
    • A non-utf8 str
  • Unwinding into another language
  • Causing a data race
  • Double-dropping a value

That's it. That's all the Undefined Behaviour baked into Rust. Of course, unsafe functions and traits are free to declare arbitrary other constraints that a program must maintain to avoid Undefined Behaviour. However these are generally just things that will transitively lead to one of the above problems. Some additional constraints may also derive from compiler intrinsics that make special assumptions about how code can be optimized.

Rust is otherwise quite permissive with respect to other dubious operations. Rust considers it "safe" to:

  • Deadlock
  • Have a race condition
  • Leak memory
  • Fail to call destructors
  • Overflow integers
  • Abort the program
  • Delete the production database

However any program that actually manages to do such a thing is probably incorrect. Rust provides lots of tools to make these things rare, but these problems are considered impractical to categorically prevent.