# Rust From the Ground Up — Part 3


Introduction

This week we will about data types in Rust. We will check the existing primitive data types in Rust, learn how to create our own using structs and talk about how Rust handles Sum Types through Enums.

I was planning to also talk about control flow, but this post grew too large and I’ll only talk about types.

## Types

Being a strongly typed language, every value must have a type. A types tells the Rust compiler what is the format of the data, which values it can have and and which it cannot.

Rust exposes a complete set of primitive types with different sizes. Most numeric values follow the naming convention *&lt;data-type&gt;&lt;size&gt;*. For instance, the type `u8 `represents a *unsigned integer* of size 8, while a `i32 `represent a *signed integer* of size 32. Let’s check them all.

## Integer Types

An *integer* is a number without a fractional component. Rust provides 12 different options for such types, varying according to signedness and size, ranging from 8 to 128 bits (with one exception). The following table extracted from [[Data Types — The Rust Programming Language]](https://doc.rust-lang.org/book/ch03-02-data-types.html) shows these values.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1613818102499/N4NT1Khh1.png)

As you can see, there’s a huge range of options.

Two cases are special in this table, `isize` and `usize`. The size of these is architecture dependent. These types are used to represent indexing information on collections and vary according to the architecture the computer is compiled on. It will be 64 bits on a 64-bits architecture and 32 bits in a 32-bit architecture. `usize` roughly map to the `size_t` type in C++.

A special note should be taken regarding overflow. program compiled in debug mode, numerical operations are checked, so if a overflow occurs the application is terminated with an error. The release mode does *not* include such checks.

If you *want* overflowing types you can use [std::num::Wrappin](https://doc.rust-lang.org/std/num/struct.Wrapping.html)g.

## Floating Point Types

Floating point types represents numbers with a fractional component. Rust provides two such types, the `f32` type, which represents a floating point number with 32 bit size and `f64` type which represents a more precise, 64 bit size. On modern architectures operations on `f64` are roughly the same speed as `f32`, but with more precision. The floating point types follow the [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) standard, with `f32`being a single-precision float and `f64` being double precision.

## Boolean Type

The boolean type is inhabited by only two possible values: `true` and `false`. Its main usage is for control flow of a program, which we will be discussing on the next post. It is represented by the `bool`type in Rust.

## Character Type

A `char`is a special type, representing a four byte (32 bit) Unicode Scalar Value. Notice that this means it does not map to a `char`in C/C++, which is usually 8 bits. A common intuition to have is that a `char` represents a letter. This does not mean that it can only represent a letter though. While it maps all letters in the ASCII set as expected, it can represent much more, such as accented letters, emojis, Arabic, Chinese, Japanese and other languages “letter set”.

Unicode is a really complex topic to get into and we don’t have time to go into right now, but we will talk a little more about it once we talk about the `String `type.

## Structs

Structs is one way of doing type aggregation. It is a form a Product Type, where each of the fields are tagged by a name. It maps roughly to a struct in C, but not exactly like a class in OOP languages. On the next post we will see how to group data and behavior together in Rust, but for now it is easier to think of a struct as just an aggregation of data.

Defining a struct is again similar to C:

```
struct Point {
 x: f64,
 y: f64,
}
```


Constructing a value is done by giving a value for each field. Field access is done by name:

```rust
let point = Point { x: 0.0, y: 0.0 };

println!(“x: {}, y: {}”, point.x, point.y);
```

## Tuples

The Tuple types represents another way of aggregating data of possibly distinct but defined types. A simple way of thinking about it is that a Tuple is an array of a fixed size (this size is called the “arity” of the tuple) where each entry might be of a different, but predefined type. Tuples are similar to structs in that it represents a Product Type. But different from structs, fields do not have names and are accessed through their index. We see these concepts in the following code snippets:

```rust
// A tuple of airty 3, where the third element is another tuple of arity 2 
let x: (i32, f64, (i32, f64)) = (1, 2.0 (3, 4.2));
// Access is done through the index of the entry
assert_eq!(x.2, (3, 4.2))
```

We can do destructuring of tuples, making it easy to access some partial information about the tuple. This will come in handy for pattern matching on tuples on our next topic (you can also destructure in structs! We will take a look at that when we talk about pattern matching).

```rust
let x: (i32, f64, (i32, f64)) = (1, 2.0 (3, 4.2));
// Destructure the tuple, so we can access each entry by a name
// Notice that the destructuring nests, so we can extract information of nested tuples
// If we want to ignore some information we can just use `_` so we don’t give a name
// to that field of the tuple
let (a, _, (_, d)) = x;
println!(“{} {}”, a, d); // prints 1 4.2
```

We can create our own tuple based types. This gives a syntax similar to constructors in other languages when building these new types:

```rust
// Creates a new type that wraps a pair of f64
struct Point(f64, f64);

// We create instances by calling the type name with the parameters
let my_point: Point = Point(1.0, 2.0);

// Access is done through indexing
println!(“{}”, my_point.0); // prints 1.0

// As with normal tuples we can also destructure it
let (x, y) = my_point;
println!(“{} {}”, x, y);
```

A common usage of tuples is the “New Type” pattern. This is a common pattern in Domain Driven Development, where we give a type to each aspect of our application. Say we have a database where we want to associate names and addresses. We could just pass `String`s around, but `String`s are not a name or an addresses (though it might be its representation in memory). Using the New Type pattern we create a wrapper type so we can reason about our Domain and our code in the same “language” (called ubiquitous language in DDD):

```rust
struct Name(String)
struct Address(String)

fn register_in_database(name: Name, address: Address) {…}
```

## Enums

While structs and tuples, which are Product Types, are one side of the [Algebraic data type](https://en.wikipedia.org/wiki/Algebraic_data_type) coin, Enums represents the other side, being a Sum Type. Enums represent the “one of” abstraction, and are declared by giving each of it’s possible values a name:

```rust
enum Shape {
 Rectangle,
 Circle
}
```

We can construct the value by referring to its name. We can also evaluate which of the possible values the enum holds by using a match expression:

```rust
let my_shape: Shape = Shape::Rectangle;

match my_shape {
 Shape::Rectangle -&gt; println!(“Rectangle!”),
 Shape::Circle -&gt; println!(“Circle!”)
}
```

We will go more in depth about match expressions on the next post when we talk about pattern matching.

What makes Enums even more powerful is that each of the cases may hold a different set of Product Types (tuples or structs):

```rust
enum Shape {
 Rectangle {
 lower_left: Point,
 upper_right: Point
 },
 Circle {
 center: Point,
 radius: f64
 }
}
```

In memory, the size of a enum value will always be the size of the “largest” possible value, so some care must be taken for performance reasons. For instance, supose we have the following enum:

```rust
enum Shape {
 OptionA(AHugeDataStruct),
 OptionB(f64)
}
```

If most of the time objects of `OptionB` value is created, even though it looks like it should be 64 bits in size, it ends up taking the size of `AHugeDataStruct`.

There are a bunch of special optimizations around enums and it’s size that I might do a short post about in the future.

## Arrays

Similar to tuples, arrays group multiple values into one value type. Where it differs is that all values must have the same type and the size is part of the type signature. This means that the size of the array must be known and is static. The main use of arrays is when you wish to allocate a collection of values on the stack. Rust also provides collections with dynamic size, but these values allocate memory on the heap. The type declaration of a array follows the format `[&lt;data-type&gt;; &lt;size&gt;]`.

Access is done through indexing. Indexing into arrays is always checked. This means that at runtime Rust will always check if the value you are trying to access is withing the bounds of the array. If it is not a runtime error will be thrown. This check does incur some performance cost, but most of the time it is small. If you are sure of what you are doing the language does provide a scape hatch using unsafe code. We will talk about that in a future post.

```rust
// Creating an array
let x: [i32; 5] = [1, 2, 3, 4, 5];

assert_eq!(x[0], 1)

let boom = x[100]; // Runtime error
```

## Slices

A slice represents a view into a contiguous sequence of elements in a collection. There are two main forms of slices, String slices which we will see in the next topic and array slices.

```rust
// Creating an array
let x: [i32; 5] = [1, 2, 3, 4, 5];

// We slice into the elements from the second to the third elements
let slice: &[i32] = &x[1..3];

// We can index into the slice
assert_eq!(slice[0], 2);
assert_eq!(slice[2], 4);

// Bounds checking also works with slides
slice[100];
```

The syntax `&lt;beggining&gt;..&lt;end&gt;` creates a range of values from the `beggining` value (inclusive) to the `end` value (exclusive). Slices are useful as a “view” into the data, without needing to copy it.

This is the first time we are seeing one of the most powerful tools in Rust, the `&` symbol which stands for a “reference to”. For now you can think of it just as a pointer in other languages. You can read the type `&[i32]` as “a reference to a array of `i32`s”. Another point to take note is that the indexing on the slice is relative to that slice’s bounds, **not** the source data. This can be better explained in an image:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1613818104444/_NRBK5jXB.png)

## Strings

Strings are one of the most common data types in any language, and is no different in Rust. It represent some form of text, such as a file’s content or an user input. The language provides two representations for strings, the `String` type and the `str`type (usually used as a reference, `&str` which represents a *string slice*). A *string slice* can be seen as a view (read only) into a the raw data of a `String`. `String`s can be easily converted into `&str` values. It is usually more efficient to pass the slice around then the whole String. This is the same philosophy of the new `std::string_view`type in C++17.

```rust
// This is a string slice
let my_string_slice: &str = “Hello World!”;
// We can create strings from string slices
let my_string: String = my_string_slice.to_string();
// Or create the string with the “from” conversion method
let my_string2: String = String::from(“Hello Universe!”);

// If a function takes a &str
fn my_fn(value: &str) {…}

// We can pass it our string reference
// Under the hood there’s a trait called deref
// That does some magic to us.
// We’ll talk about that on the next post
my_fn(&my_string);
```

Strings in Rust are UTF-8 encoded by default and, roughly speaking, are internally represented by a Vector of `u8`s (`Vec&lt;u8&gt;` in Rust’s notation). Notice that one might find it confusing since the `char` type is 32 bit in size. This is because the UTF-8 format “compacts” the String representation so the most common case ASCII can be represented in just one `u8` value (technically, half of a `u8` since there are 128 ASCII values, which are mapped directly into UTF-8), while more complex “letter” representations group sequences of up to 4 `u8`s to encode its value.

This means that you can’t directly access String data through indexing. After all it would not be clear what you are trying to access. Is it the underlying `u8` value? Or maybe you specifically want to access the `char` that might be composed of a cluster of `u8`s? If you know specifically what you want to access, the methods `bytes` and `chars` will give you iterators into each representation.

```rust
let my_string = “Hello World”;
assert_eq!(my_string[0], ‘H’); // error!

assert_eq!(my_string.chars().nth(0), ‘H’); // Ok!
```

If you need a String representation that is closer to the C/C++ style, you can use the type `CString`.

PS: Such a data representation is biased towards western languages for historical reasons.

## Conclusion

Now that we know how to write our own data types we can start to have some real *fun*. On the next post we will talk about control flow and traits, which is a abstraction to represent the functionalities of a particular type.

The following post will have a discussion on Rust’s view on Object Oriented Programming and how to map what is now the most common programming paradigm into Rust’s way of thinking.

See you then!
