Rust - A statically-typed language

A look at type systems

What is a type in the context of a programming language?

Types in C++ code

int foo(int val) {
    return val * 2;
}

int main() {
    int val = 42;
    long long long_val = val;
    int* val_ptr = &val;
    float what_are_types = *reinterpret_cast<float*>(val_ptr);
    int val_twice = foo(val);
    //foo("hello"); //Does not compile because of invalid types
}

Types in C++ code

int foo(int val) {
    return val * 2;
}

int main() {
    int val = 42;
    long long long_val = val;
    int* val_ptr = &val;
    float what_are_types = *reinterpret_cast<float*>(val_ptr);
    int val_twice = foo(val);
    //foo("hello"); //Does not compile because of invalid types
}
  • Explicit data types: int, long long, int* etc.

Types in C++ code

int foo(int val) {
    return val * 2;
}

int main() {
    int val = 42;
    long long long_val = val;
    int* val_ptr = &val;
    float what_are_types = *reinterpret_cast<float*>(val_ptr);
    int val_twice = foo(val);
    //foo("hello"); //Does not compile because of invalid types
}
  • Explicit data types: int, long long, int* etc.
  • Implicit types: 42 is an integer literal and has type int

Types in C++ code

int foo(int val) {
    return val * 2;
}

int main() {
    int val = 42;
    long long long_val = val;
    int* val_ptr = &val;
    float what_are_types = *reinterpret_cast<float*>(val_ptr);
    int val_twice = foo(val);
    //foo("hello"); //Does not compile because of invalid types
}
  • Explicit data types: int, long long, int* etc.
  • Implicit types: 42 is an integer literal and has type int
  • Function types: int(int), int()

Type systems

  • The set of type rules in a programming language is called it’s type system and it has two tasks:
    • It assigns a property called type to all code statements
    • It enforces a set of logical rules on these types:
int foo(int val) {
    return val * 2;
}

int main() {
    foo("hello"); // The type system detects a violation: 
                  // Expected int but found const char*
}

Why are type systems useful?

  • How else would we convert source code into machine code instructions?
    • This is why we have int, short, char* etc.
  • A good type system also prevents bugs
    • Performing operations on the wrong type of data (i.e. "hello" * 2)
    • Modifying state that we don’t want to modify (i.e. const)
    • Using data that does not exist anymore

Three different type systems

x86-64 assembly code:

push    rbp
mov     rbp, rsp
mov     DWORD PTR [rbp-4], edi
mov     DWORD PTR [rbp-8], esi
mov     eax, DWORD PTR [rbp-4]
cdq
idiv    DWORD PTR [rbp-8]
pop     rbp
ret

C++:

int div(int a, int b) {
    return a / b;
}

Python:

def div(a, b):
    return a // b

Observation: Strong types are documentation

  • How is a function / class / module meant to be used?
  • Ideally: Prevent us from doing invalid things
    • Goals: Robustness and maintainability of software
    • Debatable: Linux is written in C, which has a weak type system

Can we use these code snippets incorrectly?

x86-64 assembly code:

push    rbp
mov     rbp, rsp
mov     DWORD PTR [rbp-4], edi
mov     DWORD PTR [rbp-8], esi
mov     eax, DWORD PTR [rbp-4]
cdq
idiv    DWORD PTR [rbp-8]
pop     rbp
ret

C++:

int div(int a, int b) {
    return a / b;
}

Python:

def div(a, b):
    return a // b

Examples of invalid usage

  • Wrong arguments:
    • div(2, 0) might crash the program
  • Unintuitive behavior:
    • What is div(3.0, 2.0)? What would be a reasonable assumption?
  • The type systems of x86-64 assembler, C++ and Python don’t help us here!

The Rust type system in action

  • A first example of how a strong type system can prevent invalid usage
  • One of multiple core ideas:
    • Prevent invalid states
    • Make abstractions zero-cost
    • Enforce correct usage through explicit intent
    • Make resource usage safe and deterministic

Preventing invalid states

  • In Rust we might write this:
fn div(a: i32, b: NonZeroI32) -> i32 {
    a / b.get()
}

div(3, NonZeroI32::new(2));
div(3, NonZeroI32::new(0));
  • Explicitly convert the argument to a non-zero number
    • Is this code slower than the C++ version?
  • What happens when we write NonZeroI32::new(0)?
    • Currently a program crash, is this a good idea?

Building NonZeroI32 from scratch

Live example

Recap for NonZeroI32

  • Use the Rust visibility rules to prevent invalid states (e.g. use new function for creating instances)
  • Make usage explicit: div requires a non-zero second argument!
  • Gain a bit of ergonomics by using Into
  • What about the overhead?

Is NonZeroI32 zero-cost?

  • Two options:
    1. Measure the performance in various situations
    2. Check the resulting assembly code
  • Let’s use Compiler Explorer: