Mastering Rust
上QQ阅读APP看书,第一时间看更新

Type systems and why they matter

"Be conservative in what you send, be liberal in what you accept."                                                                                                                                                                                                                                                                                                         - John Postel

Why do we need types in a language? That's a good question to ask as a motivation to understand type systems in programming languages. As programmers, we know that programs written for computers are represented in binary as combinations of 0s and 1s at the lowest level. In fact, the earliest computers had to be programmed manually in machine code. Eventually, programmers realized that this is very error-prone, tedious, and time-consuming. It's not practical for a human to manipulate and reason about these entities at the binary level. Later, during the 1950s, the programming community came up with machine code mnemonics, which turned into the assembly language we know of today. Following that, programming languages came into existence, which compiled down to assembly code and allowed programmers to write code that is human readable yet easy for computers to compile down to machine code. However, the languages that we humans speak can be quite ambiguous, so a set of rules and constraints needed to be put in place to convey what is possible and what is not in a computer program written in a human-like language, that is, the semantics. This brings us to the idea of types and type systems.

A type is a named set of possible values. For example, u8 is a type that can contain only positive values from 0 to 255. Types provide us with a way to bridge the gap between the lower-level representation and the mental model we create of these entities. Apart from this, types also provide us with a way to express intent, behavior, and constraints for an entity. They define what we can and cannot do with types. For example, it is undefined to add a value of a type string to a value of a type number. From types, language designers built type systems, which are sets of rules that govern how different types interact with one another in a programming language. They act as a tool for reasoning about programs and help ensure that our programs behave correctly and according to the specification. Type systems are qualified based on their expressiveness, which simply means the extent to which you can express your logic, as well as invariants in the program using only the type system. For example, Haskell, a high-level language, has a very expressive type system, while C, a low-level language, provides us with very few type-based abstractions. Rust tries to draw a fine line between these two extremes.

Rust's type system is inspired quite a bit by functional languages such as Ocaml and Haskell with their ADTs such as enums and structs, traits (akin to haskell typeclasses), and error handling types (Option and Result). The type system is characterized as a strong type system, which simply means that it performs more type checks at compile time rather than throwing them at runtime. Furthermore, the type system is static, which means that variables that are, for example, bound to an integer value, cannot be changed to point to a string later. These features enable robust programs that rarely break the invariants at runtime, with the cost that writing programs requires a bit of planning and thinking from the programmer. Rust tries to put more planning on your plate when designing programs, which can put off some programmers looking to prototype things fast. However, it is a good thing from the long-term perspective of maintaining software systems.

With that aside, let's start by exploring how Rust's type system enables code reuse.