Data types

Learn the basic data types in Rust.

One of the most robust features of Rust is its type system. It allows us to catch bugs in compile-time and improve code readability.

Numeric types

Rust has two numeric data types:

  • Integer
  • Float

Integer

The basic integer data types are described below:

Length Signed Unsigned
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize

A signed integer is a number that can be positive or negative, whereas unsigned integers can only be positive numbers.

The length of usize and isize depends on the architecture. Its size is four bytes on a 32-bit target and eight bytes on a 64-bit target.

Float

Rust has two float data types:

  • f32
  • f64
use std::mem;
fn main() {
println!("i8 has {} byte", mem::size_of::<i8>());
println!("i16 has {} bytes", mem::size_of::<i16>());
println!("i32 has {} bytes", mem::size_of::<i32>());
println!("i64 has {} bytes", mem::size_of::<i64>());
println!("i128 has {} bytes", mem::size_of::<i128>());
println!("isize has {} bytes", mem::size_of::<isize>());
println!("f32 has {} bytes", mem::size_of::<f32>());
println!("f64 has {} bytes", mem::size_of::<f64>());
}

Boolean

If we want to define whether a variable should be true or false, we can use the bool type.

fn main(){
let n: bool = true;
if n {
println!("This is true");
}
}

Spot an error in the above code as an exercise.

Text types

Rust has three textual data types:

  • char
  • &str
  • String

The char type is used when we need a single character of four bytes in size. It is always surrounded by single quotes.

fn main() {
println!("This is a char: {}", 'šŸ˜€');
}

The &str type is a reference to str, mainly used as a string literal. We might not need it often.

fn main(){
let s = "This is a string literal";
print_type_of(s);
let n = "This is not a string literal".to_string();
print_type_of(n);
}

The variable n is not a string literal because weā€™re casting it to a String type.

The String type is the most commonly used data type. Itā€™s the data type weā€™ll be using in our web projectsā€”for example, when we need a database field that needs to store text values, they will always be a String type.

// model product
#[derive(Debug)]
struct Product {
id: i32,
name: String, // we'll be using String most of the time
description: String,
price: f64,
stock: f64
}
fn main() {
println!("A product object: {:#?}",
Product {
id: 1,
name: "Shoes".to_string(),
description: "Shoes for the feet".to_string(),
price: 100.45,
stock: 15.00
}
)
}

Collection types

Collection data type can save a group of items, like Tuple, Array, and Vector.

A Tuple is a collection with the below features:

  • Its items can be of different types.
  • Itā€™s immutableā€”we canā€™t add or remove an item.
fn main() {
let tuple = ("Hello", "World", 45.98);
println!("This is a tuple: {:#?}", tuple);
}

An Array is a fixed collection of items of the same type; we canā€™t add or delete an item like we do in a tuple.

fn main() {
let array = [1,2,3,4,5];
println!("This is an array: {:#?}", array);
}

A Vector is the equivalent of an array in other languagesā€”for example, it can grow, and it does not have a fixed size.

One of the best features of Rust is immutability, which means if we need to change something, we just have to use the keyword mut.

fn main() {
let mut vector = vec![1,2,3,4,5];
println!("This is a vector: {:#?}", vector);
vector.push(6);
println!("This is the same vector with an aditional item: {:#?}", vector);
}

Custom types

We can use a struct or an enum when the primitive types do not provide the additional behavior we need.

Weā€™ll cover them in the next lesson, but we can see an example of their use as custom types.

Generics

Sometimes, we need to cover a group of types that share some attributes. In such cases, we use generics.

For example, letā€™s say you need a function to process some calculations over a variable, you know the variable must be a number, but, there are different types of numbers (i8, i16, u8, u64, f32, f64, etc.). In this case, you can use Generics.

Another example, you want to know the length of a collection and want to create a method for that, but you know this can be useful for a lot of types (ex. arrays, sets, vectors, etc.), in this case, a generic trait would be useful and then it can be instantiated in every data type required.

A capital letter is used to represent a generic. For example, T is the most commonly used letter to indicate a single generic.

We declare the generic we need between <> after the function name. Next, we use it as the function parameter type.

use std::fmt::Display;
fn print_this<T: Display>(a: T) {
println!("{}", a);
}
fn main() {
print_this("Hello World!");
print_this(43.458);
print_this('šŸ™‚');
}

In the previous example we created a function to print any kind of values, a generic type is used as an argument.

While we can use generics, sometimes we would need to be specific about what features the type needs to implement.

In the case of the code above, we need to make it clear that the type needs the Display trait implemented to work.

Quiz

What type is the below statement?

1

ā€œHello World!ā€

A)

&str

B)

char

C)

String

Question 1 of 30 attempted