The debate between programmers who prefer dynamic typing over static typing is likely to continue for decades to come, but it’s hard to argue about the benefits of static types.
The rise of languages such as TypeScript and the availability of features such as Python’s type annotations indicate that people have become frustrated with the current state of dynamic typing.
Statically typed languages allow compiler-verifiable constraints on data and behavior, reducing cognitive overhead and misunderstandings.
However, not all static typing methods are equivalent. Many statically typed languages support the NULL concept.
This means that any value can be missing, thus creating a second possible type for each type. Like Haskell and some other modern programming languages, Rust implements this feature with the optional type, and the compiler requires you to specify case None.
This prevents the error: TypeError: Cannot read property ‘foo’ of null at runtime, instead the error occurs at compile time, and you can fix it before the user sees it. Here is an example of a function to greet someone whether we know their name or not; if we forgot the None case in match, or tried to use the name as if it were an ever-present string value, the compiler would throw an error.
Static typing in Rust does its best not to get in the way of the programmer while still providing support. Some statically typed languages place a heavy burden on the programmer, requiring the type of a variable to be repeated many times, hindering readability and refactoring. Other statically typed languages allow type inference throughout the program.
While this is useful early in development, it reduces the compiler’s ability to provide useful error information when types mismatch. Rust learns from both of these styles and requires top-level elements such as function arguments and constants to have explicit types, while still allowing type inference within function bodies. In this example, the Rust compiler can infer the type twice, 2 and 1, because the val parameter and return type are both declared as 32-bit signed integers.
Inheritor of languages with a garbage collector
One of the biggest benefits of using a systems programming language is the ability to control low-level details.
Rust allows you to choose between storing data on the stack or on the heap, and determines at compile time that the memory is no longer needed and can be cleared. This allows efficient use of memory. Tilde, an early Rust user of their Skylight product, found that they were able to reduce memory usage from 5GB to 50MB by rewriting some Java HTTP endpoints in Rust. Such savings become especially significant when cloud providers change the price of additional memory.
Without the need for constant garbage collection, Rust projects are well suited to be used as libraries by other programming languages through interfaces with external functions. This allows existing projects to replace performance-critical parts with fast Rust code without the memory safety risks of other system programming languages. Some projects have even been gradually rewritten in Rust using these techniques.
With direct access to hardware and memory, Rust is an ideal language for developing embedded and bare-metal systems. You can write extremely low level code, such as operating system kernels or microcontroller applications. Rust’s core types and functions, as well as reusable library code, work well in these particularly complex environments.
The successor of systems programming languages
Most people see Rust as an alternative to systems programming languages like C or C++. Rust’s biggest advantage is borrow checking. This is the part of the compiler responsible for ensuring that references don’t outlive the data they refer to, which helps eliminate whole classes of bugs caused by unsafe memory usage.
Unlike many existing systems programming languages, Rust doesn’t require you to spend all your time immersing yourself in the smallest details. Rust strives to have as many zero-cost abstractions as possible – abstractions that are as efficient as the equivalent handwritten code. In this example, we’ll show how iterators, Rust’s core abstraction, can be used to briefly create a vector containing the first ten square numbers.
In cases where secure Rust is not enough, we can use insecure Rust. This provides additional options, but you yourself must ensure that the code is safe. This code can then be wrapped in higher-level abstractions that ensure that all uses of the abstraction are safe.
Using unsafe Rust should be a thoughtful decision, as using it properly requires as much thought and care as in any other language where you are responsible for preventing undefined behavior. Minimizing unsafe code is the best way to minimize the possibility of failures and vulnerabilities due to memory insecurity.
Systems programming languages are meant to effectively last forever. While some modern designs don’t require that lifetime, many companies want to know that their fundamental codebase will be usable for the foreseeable future. Rust recognizes this and has made conscious design decisions regarding backwards compatibility and stability; it is a language designed for the next 40 years.