Free of Undefined Behavior

The following C program compiles without errors or warnings:

int main(int argc, char **argv) {
    unsigned long a[1];
    a[3] = 0x7ffff7b36cebUL;
    return 0;
}
  • The array a is only one element long, so using a[3] is, according to the C programming language standard, undefined behavior.
  • Undefined behavior doesn’t just have an unpredictable result: the standard explicitly permits the program to do anything at all.
    • Undefined behavior: Behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements.

C and C++ are the industry standards for systems programming. They have hundreds of rules for avoiding undefined behavior. There rules are mostly common sense: don’t access memory you shouldn’t, don’t let arithmetic operations overflow, don’t divide by zero, and so on. The compiler does not enforce these rules. The responsibility for avoiding undefined behavior falls entirely on the programmer.

Assuming that you can avoid undefined behavior in C and C++ is like assuming you can win a game of chess simply because you know the rules.

Inadvertent undefined behavior has also been a major cause of security flaws since the 1988 Morris Worm used a variation of the technique to propagate from one computer to another on the early Internet. Stuxnet, a computer worm found breaking into industrial control equipment in 2010, gained control of the victims’ computers using, among many other techniques, undefined behavior in code that parsed TrueType fonts embedded in word processing documents.

It’s not just operating systems and servers that need to worry about security: any software that might handle data from an untrusted source could be the target of an exploit.

The Rust language makes you a simple promise: if your program passes the compiler’s checks, it is free of undefined behavior.

  • Dangling pointers, double-frees, and null pointer dereferences are all caught at compile time.
  • Array references are secured with a mix of compile-time and run-time checks, so there are no buffer overruns.

In practice, taking undefined behavior off the table substantially changes the character of development for the better.

Free of Data Races

The same restrictions that ensure memory safety in Rust also ensure that Rust programs are free of data races.

  • You can share data freely between threads, as long as it isn’t changing.
  • Data that does change can only be accessed using synchronization primitives.

All the traditional concurrency tools are available: mutexes, condition variables, channels, atomics, and so on. Rust simply checks that you’re using them properly. This makes Rust an excellent language for exploiting the abilities of modern multicore machines.

Performant

Systems programming is often concerned with pushing the machine to its limits.

In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.

Rust shares the ambitions. Rust is designed with efficient defaults and gives you the ability to control how memory gets used and how the processor’s attention is spent.

Easier Collaboration

Rust’s package manager and build tool, Cargo, makes it easy to use libraries published by others on Rust’s public package repository. You simply add the library’s name and required version number to a file, and Cargo takes care of downloading the library, together with whatever other libraries it uses in turn, and linking the whole lot together.

The language itself is also designed to support collaboration:

  • Rust’s traits and generics let you create libraries with flexible interfaces so that they can serve in many different contexts.
  • Rust’s standard library provides a core set of fundamental types that establish shared conventions for common cases, making different libraries easier to use together.

References