Primitive Data Types

  • bool

    true or false
let x = true;
let y: bool = false;

// ⭐️ no TRUE, FALSE, 1, 0
  • char

    A single Unicode scalar value
let x = 'x';
let y: char = '😎';

// ⭐️ no "x", only single quotes

Because of Unicode support, char is not a single byte, but four.

  • i8, i16, i32, i64, i128

    8, 16, 32, 64 and 128 bit fixed sized signed(+/-) integer types
DATA TYPE MIN MAX
i8 -128 127
i16 -32768 32767
i32 -2147483648 2147483647
i64 -9223372036854775808 9223372036854775807
i128 -170141183460469231731687303715884105728 170141183460469231731687303715884105727

💡 The min and max values are based on the following equation; from -(2ⁿ⁻¹) to 2ⁿ⁻¹-1. You can use min_value() and max_value() functions to find min and max of each integer type. ex.i8::min_value();

let x = 10; // ⭐️ The default integer type in Rust is i32
let y: i8 = -128;
  • u8, u16, u32, u64, u128

    8, 16, 32, 64 and 128 bit fixed sized unsigned(0/+) integer types
DATA TYPE MIN MAX
u8 0 255
u16 0 65535
u32 0 4294967295
u64 0 18446744073709551615
u128 0 340282366920938463463374607431768211455

💡 The min and max values are based on the following equation; from 0 to 2ⁿ-1. Same way you can use min_value() and max_value() functions to find min and max of each integer type. ex.u8::max_value();

  • isize, usize

    Pointer sized signed and unsigned integer types

The actual bit size depends on the computer architecture you are compiling your program for. By default, the sizes are equal to 32 bit on 32-bit platforms and 64 bit on 64-bit platforms.

🔎 Search more about cross-compiling and Supported Tiers of Rust programs.

  • f32, f64

    32 and 64 bit sized floating point numbers(numbers with decimal points)

Rust follows IEEE Standard for Binary Floating-Point Arithmetic. The f32 type is similar to float(Single precision) in other languages, while f64 is similar to double(Double precision) in other languages.

let x = 1.5; // ⭐️ The default float type in Rust is f64
let y: f64 = 2.0;

💡 Should avoid using f32, unless you need to reduce memory consumption badly or if you are doing low-level optimization, when targeted hardware does not support for double-precision or when single-precision is faster than double-precision on it.

  • arrays

    Fixed size list of elements of same data type
let a = [1, 2, 3];
let a: [i32; 3] = [1, 2, 3]; // [Type; NO of elements]

let b: [i32; 0] = []; // An empty array

let mut c: [i32; 3] = [1, 2, 3];
c[0] = 2;
c[1] = 4;
c[2] = 6;

println!("{:?}", c); // [2, 4, 6]
println!("{:#?}", c);
//  [
//      2,
//      4,
//      6,
//  ]

let d = ["my value"; 3]; // ["my value", "my value", "my value"]

⭐️ Arrays are immutable by default and even with mut, its element count cannot be changed.

🔎 If you are looking for a dynamic/ growable array, you can use vectors. Vectors can contain any type of elements but all elements must be in the same data type.

  • tuples

    Fixed size ordered list of elements of different(or same) data types
let a = (1, 1.5, true, 'a');
let a: (i32, f64, bool, char) = (1, 1.5, true, 'a');

let mut b = (1, 1.5);
b.0 = 2;
b.1 = 3.0;

println!("{:?}", b); // (2, 3.0)
println!("{:#?}", b);
// (
//   2,
//   3.0,
// )

let (c, d) = b; // c = 2, d = 3.0
let (e, _, _, f) = a; // e = 1, f = 'a'

let g = (0,); // single-element tuple
let h = (b, (2, 4), 5); // ((2, 3.0), (2, 4), 5)

⭐️ Tuples are also immutable by default and even with mut, its element count cannot be changed. Also, if you want to change an element’s value, the new value should have the same data type of previous value.

  • slice

    Dynamically-sized reference to another data structure

Imagine you want to get/ pass a part of an array or any other data structure. Instead of copying it to another array (or same data structure), Rust allows for creating a view/ reference to access only that part of the data. This view/ reference can be mutable or immutable.

let a: [i32; 4] = [1, 2, 3, 4]; // Parent Array

let b: &[i32] = &a; // Slicing whole array
let c = &a[0..4]; // From 0th position to 4th(excluding)
let d = &a[..]; // Slicing whole array

let e = &a[1..3]; // [2, 3]
let f = &a[1..]; // [2, 3, 4]
let g = &a[..3]; // [1, 2, 3]
  • str

    Unsized UTF-8 sequence of Unicode string slices
let a = "Hello, world."; //a: &'static str
let b: &str = "こんにちは, 世界!";

⭐️ It’s an immutable/ statically allocated slice holding an unknown sized sequence of UTF-8 code points stored in somewhere in memory. &str is used to borrow and assign the whole array to the given variable binding.

  • functions

    As we discussed in the functions section, b is a function pointer to plus_one() in the below code.
fn plus_one(a: i32) -> i32 {
    a + 1
}

let b: fn(i32) -> i32 = plus_one;
let c = b(5); //6

Before going to the next…

  • The String type is a heap-allocated string. This string is growable and is also guaranteed to be UTF-8. In general, you should use String when you need ownership, and &str when you just need to borrow a string.

  • A String type can be generated from a &str type, via the to_string() or String::from() methods. With as_str() method, a String type can be converted to a &str type.

let s: &str = "Hello"; // &str

let s = s.to_string(); // String
let s = String::from(s); // String

let s = s.as_str(); // &str