Arrays

The type [T; N] represents an array of N values, each of type T.

An array’s length is part of its type and fixed at compile time.

An array’s size is a constant determined at compile time and is part of the type; you can’t append new elements or shrink an array.

When you need an array whose length varies at run time, use a vector instead.

The useful methods on arrays—iterating over elements, searching, sorting, filling, filtering, and so on—are all provided as methods on slices, not arrays. Rust implicitly converts an array to a slice when searching for methods.

Vectors

The type Vec<T>, called a vector of Ts, is a dynamically allocated, growable sequence of values of type T.

A vector’s elements are allocated on the heap, so you can resize vectors at will.

Vec is an ordinary type defined in Rust, not built into the language.

A Vec<T> consists of three values:

  1. A pointer to the heap-allocated buffer for the elements, which is created and owned by the Vec<T>;
  2. The number of elements that buffer has the capacity to store;
    • A vector’s capacity method returns returns the number of elements it could hold without reallocation.
  3. The number it actually contains now (its length);

Slices

A slice, written [T] without specifying the length, is a region of an array or vector.

You can think of a slice as a pointer to its first element, together with a count of the number of elements you can access starting at that point.

Since a slice, which is a region of an array or vector by definition, can be any length, slices can’t be stored directly in variables or passed as function arguments. Slices are always passed by reference.

The types &[T] and &mut [T], called a shared slice of Ts and mutable slice of Ts, are references to a series of elements that are a part of some other value, like an array or vector.

A reference to a slice is a fat pointer: a two-word value comprising a pointer to the slice’s first element, and the number of elements in the slice. Consider the following code:

1
2
3
4
5
let v: Vec<f64> = vec![0.0, 0.707, 1.0, 0.707];     // v on stack, data on heap
let a: [f64; 4] =     [0.0, -0.707, -1.0, -0.707];  // stack

let sv: &[f64] = &v;
let sa: &[f64] = &a;

In the last two lines, Rust automatically converts the &Vec<f64> reference and the &[f64; 4] reference to slice references that point directly to the data. By the end, memory looks like this:

A vector `v` and an array `a` in memory, with slices `sa` and `sv` referring to each

Whereas an ordinary reference is a non-owning pointer to a single value, a reference to a slice is a non-owning pointer to a range of consecutive values in memory. This makes slice references a good choice when you want to write a function that operates on either an array or a vector.

Since slices almost always appear behind references, we often just refer to types like &[T] or &str as “slices,” using the shorter name for the more common concept.

Strings

Rust strings are sequences of Unicode characters. They are not stored in memory as arrays of chars. Instead, they are stored using UTF-8, a variable-width encoding. Each ASCII character in a string is stored in one byte. Other characters take up multiple bytes.

The following figure shows the String and &str values created by the following code:

1
2
3
let noodles = "noodles".to_string();
let oodles = &noodles[1..];
let poodles = "ಠ_ಠ";
`String`, `&str`, and `str`

String

A String has a resizable buffer holding UTF-8 text. The buffer is allocated on the heap, so it can resize its buffer as needed or requested. You can think of a String as a Vec<u8> that is guaranteed to hold well-formed UTF-8; in fact, this is how String is implemented.

String is analogous to Vec<T>.

str

The str type, also called a ‘string slice’, is the most primitive string type. Like other slice references, a &str is a fat pointer, containing both the address of the actual data and its length. You can think of a &str as being nothing more than a &[u8] that is guaranteed to hold well-formed UTF-8.

&str is very much like &[T]: a fat pointer to some data.

It is impossible to modify a &str:

1
2
3
let mut s = "hello";
s[0] = 'c';    // error: `&str` cannot be modified, and other reasons
s.push('\n');  // error: no method named `push` found for reference `&str`

A &str can refer to any slice of any string, whether it is a string literal (stored in the executable) or a String (allocated and freed at run time). This means that &str is more appropriate for function arguments when the caller should be allowed to pass either kind of string.

String Literals

A string literal is a &str that refers to preallocated text, typically stored in read-only memory (in the executable) along with the program’s machine code.