12 Type System Features of Rust's Type System

12 Type System: What are the characteristics of Rust’s type system? #

Hello, I’m Tian Chen. Today we start learning about the type system.

If you use a static language without generics like C/Golang, or dynamic languages like Python/Ruby/JavaScript, this part might be difficult, and you should be prepared to shift your mindset; if you use static languages with generics support like C++/Java/Swift, you can compare the similarities and differences between Rust and them.

In fact, we have already written quite a bit of Rust code in previous courses, using various data structures, and I believe you already have a very superficial impression of Rust’s type system. So what exactly is a type system? What can it be used for? When should it be used? Let’s find out today.

As a core element of a language, the type system greatly shapes the user experience of the language and the safety of the program. Why do I say this? Because, in the world of machine code, there is no concept of types, instructions only deal with immediate values or memory, and the data stored in memory are byte streams.

So, it can be said that the type system is entirely a tool, used by the compiler at compile-time to perform static checks on data, or by the language at runtime to perform dynamic checks, to ensure that the data being processed in an operation is the type expected by the developer.

Now can you understand why the Rust type system is particularly strict (always errors) on type issues?

Basic concepts and classifications of the type system #

Before talking specifically about Rust’s type system, let’s first clarify some concepts of the type system and agree on a basic understanding.

As mentioned in [the second lecture], type is a distinction of values, which includes information such as the length of values in memory, alignment, and the operations that can be performed on values.

For example, the u32 type, which is an unsigned 32-bit integer, has a length of 4 bytes, alignment is also 4 bytes, and its value range is between 0-4G; the u32 type has implemented interfaces such as addition, subtraction, multiplication, division, and size comparison, so operations like 1+2, i<=3 can be done.

The type system is essentially a system for defining, checking, and processing types. Therefore, based on different stages of type operations, we have different division standards and corresponding classifications. Let’s look at them one by one.

Based on whether the type can be implicitly converted after being defined, it can be divided into strong and weak types. Rust does not allow automatic conversion between different types, so it is a strong type language, while C/C++/JavaScript perform automatic conversions and are weak type languages.

Based on the timing of type checking, whether at compile-time or run-time, it can be divided into static and dynamic type systems. For static type systems, it can be further divided into explicit static and implicit static, Rust/Java/Swift, etc., are explicit static languages, and Haskell is an implicit static language.

In the type system, polymorphism is a very important concept, which refers to using the same interface, different types of objects will adopt different implementations.

For dynamic type systems, polymorphism is implemented through duck typing; for static type systems, polymorphism can be achieved through parametric polymorphism, ad hoc polymorphism, and subtype polymorphism.

  • Parametric polymorphism refers to the situation where the type operated by the code is a parameter that meets certain constraints, rather than a specific type.
  • Ad hoc polymorphism refers to the polymorphism where the same behavior has multiple different implementations. For example, addition, which can be 1+1, or “abc”+“cde”, matrix1+matrix2, or even matrix1+vector1. In object-oriented programming languages, ad hoc polymorphism usually refers to the overloading of functions.
  • Subtype polymorphism means that at runtime, subtypes can be used as parent types.

In Rust, parametric polymorphism is supported through generics, ad hoc polymorphism through traits, and subtype polymorphism can be supported with trait objects. We will talk about parametric polymorphism in a moment an