Skip to main content
  1. Posts/

Rust types

·4 mins
Rust type system is simple and powerful, it helps you to avoid common mistakes while at the same time empowers you to go as far as you need.

Rust to the rescue #

If you are coming from a dynamic language like Python you will notice that Rust is way more strict for your own sake, for example:

# Works ok on Python
>>> a = 9223372036854775807
>>> b = 1
>>> b = a
// Does't work a regular assignment without type
>> let mut a = 9223372036854775807;
[overflowing_literals] Error: literal out of range for `i32`
   ╭─[command:1:1]
   
 1  let mut a = 9223372036854775807;
                ─────────┬─────────
                         ╰─────────── error: literal out of range for `i32`

By default, numbers are i32 in Rust so our number overflows the capacity and Rust is telling us, so let’s try to assign different capacity types:

>> let a:i64 = 9223372036854775807;
>> let b:i32 = a;
[E0308] Error: mismatched types
   ╭─[command:1:1]
   
 1  let mut b:i32 = a;
              ─┬─   ┬│
               ╰─────── expected due to this
                    ││
                    ╰── expected `i32`, found `i64`
                     
                     ╰─ help: you can convert an `i64` to an `i32` and panic if the converted value doesn't fit: `.try_into().unwrap()`
───╯

This is good, Rust is telling the truth (“doesn’t fit”), helping us to avoid common mistakes, and what about this?

>> let a:i64 = 100;
>> let b:i32 = a;
[E0308] Error: mismatched types
   ╭─[command:1:1]
   
 1  let mut b:i32 = a;
              ─┬─   ┬│
               ╰─────── expected due to this
                    ││
                    ╰── expected `i32`, found `i64`
                     
                     ╰─ help: you can convert an `i64` to an `i32` and panic if the converted value doesn't fit: `.try_into().unwrap()`
───╯

The same, and that’s because you as a developer can fail to assign a value that can overflow the capacity and Rust will not let you do that because of the type system itself, you will not be allowed to assign a smaller value into a bigger container.

>> let a:i32 = 100;
>> let b:i64 = a;
[E0308] Error: mismatched types
   ╭─[command:1:1]
   
 1  let mut b:i64 = a;
              ─┬─   ┬│
               ╰─────── expected due to this
                    ││
                    ╰── expected `i64`, found `i32`
                     
                     ╰─ help: you can convert an `i32` to an `i64`: `.into()`
───╯

Types #

Primitive types #

We have the same primitive types so I am not going to list and explain all of them but you can check them in the primitives section of rust documentation so let’s focus on the aggregate types (Arrays, Tuples and Structs).

Aggregate types #

Let’s start with arrays, if you come from any language I guess arrays are familiar to you, declaration in Rust is simple, type and size:

let a: [i32, 10] = [10, 20, 30, 40, 50];

Pretty straightforward, now let’s take a look tuples.

struct Point(f32, f32);
let tuple = (5.0, 10.1);

Easy right? now let’s dive into enums.

enum HttpCode {
  Ok = 200,
  NotFound = 404,
}

The things start to get better at this point, with enum we can improve readability, for example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
>> enum Vehicle {
    Car,
    Truck,
    Motorcycle,
    Bicycle,
}

enum Direction {
    Forward,
    Back,
}

fn move_vehicle(vehicle: Vehicle, direction: Direction) {
  let move_action = match direction {
    Direction::Forward => "forward",
  };
}

move_vehicle(Vehicle::Car, Direction::Forward);
[E0004] Error: non-exhaustive patterns: `Direction::Back` not covered
    ╭─[command:1:1]
    
 14    let move_action = match direction {
                               ────┬────
                                   ╰────── pattern `Direction::Back` not covered
 15      Direction::Forward => "forward",
                                        
                                        ╰─ help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown: `,
    Direction::Back => todo!()`
────╯

This function is type-safe and easy to read and we can achieve something super useful related to functional programming also, pattern matching (match block code).

And things get even better! have you realized emus are sum types of algebraic data types (only one value at a time using “or”) and tuples are product types (multiple values grouped using “and”)? Check what are algebraic data types post to learn more about it.

Rust types are interesting and this is just the top of the iceberg.

Type inference #

Worth mentioning, in Rust you don’t need to set the type of a variable, they are inferred by the compiler and as we have seen, they are always checked for safeness.

Inmutability #

Rust uses inmutability by default but you can use the mut keyword to allow mutations.

Common enums in Rust #

Option is used to express values that can be something (Some(T)) or null (None).

Option<T>

Result is used to express the result of an operation, the type T is the resulting type of the operation and E is the error in case of failure.

Result<T,E>

Conclusions #

Rust type system is pretty simple and impressive, using functional concepts while at the same time helping to avoid errors.