Rust Slices Tutorial

In this Rust tutorial we learn how to create subsets of elements of arrays, vectors and strings by slicing them, allowing us to access data in contiguous memory blocks safely and efficiently.

What is a slice

A slice in Rust is a data type that doesn’t have an owner, it’s just a pointer to a block of memory. We can use slices to access portions of data that’s stored in contiguous memory blocks, like arrays, vectors and strings. It allows safe and efficient access to these memory blocks without copying.

As mentioned above, a slice is a pointer to the memory address of the actual data. It’s not the data itself, like an integer variable would be.

Slices are borrowed, that’s to say, they are passed by reference to a function and a slice’s size is determined at runtime.

How to create a slice

A slice is created by slicing an existing data container, such as a string, into the parts we want. So, a slice is not created directly like say, a variable would be.

We create a slice by specifying from which index the slice should start, and at which index the slice should end between square brackets and separated by two dot operators.

Syntax: create a slice
// collection data container
// like array, string etc.
let var_name = some_data;

// slice
&var_name[start_index..end_index];
Example: create a slice
fn main() {

    let msg = "Hello World";

    println!("Pre-slice (msg): {}", msg);

    let sliced_msg = &msg[0..5];

    println!("Post-slice (sliced_msg): {}", sliced_msg);

}

In the example above, the slice will take the characters 0 to 5 from the string “Hello World”, to form the slice “Hello”.

Because a slice is a pointer, we should remember to to reference the address of the data container with the & reference operator.

When a slice uses the very first index number as a starting point, or the very last index as an ending point, those numbers can be omitted.

Example:
fn main() {

    let msg = "Hello World";

    // slice from first index so
    // we are allowed to omit the
    // number
    let sliced_msg_1 = &msg[..5];

    println!("Slice from first: {}", sliced_msg_1);

    // slice to the last index so
    // we are allowed to omit the
    // number
    let sliced_msg_2 = &msg[6..];

    println!("Slice to end: {}", sliced_msg_2);
}

In the example above, we omit the 0 when slicing from the first index and we omit the 11 when slicing to the last index.

How to slice with a function

We can pass a value to a function to be sliced.

To do this, we need three parameters:

  1. A reference to a container to be sliced.
  2. A value for the start index.
  3. A value for the stop index.

We also need to add a safety measure to check if the stop index number isn’t larger than the amount of values in the container.

Example: slice with a function
fn main() {

    let msg = "Hello World";

    slice_str(&msg, 1, 5, true);
}

fn slice_str(slice:&str, start:usize, mut end: usize, log: bool) {

    // ensure end is not greater
    // than the string's length
    if end > slice.len() {
        end = slice.len();
    }

    // slice the string
    let sliced = &slice[start..end];

    // print the string (optional)
    if log {
        println!("{} sliced from {} to {}: '{}'", slice, start, end, sliced);
    }
}

How to mutate a slice

We can make a slice mutable if we mark it with the &mut keyword.

Note that we have to mark the slice as mut in the container definition, the function definition parameter list and the function call parameter list.

Example: mutate a slice
fn main() {

    let mut num = [10, 20, 30, 40, 50];

    slice_num(&mut num);

}

fn slice_num(slice:&mut [i32]) {

    // slice before mutation
    println!("Slice before: {:?}", slice);

    // mutate a slice value
    slice[0] = 5;

    // slice after mutation
    println!("Slice after: {:?}", slice);
}

Once the slice is marked as mutable, we can mutate each individual element by changing the value at its corresponding index (just like mutating an array).

Summary: Points to remember

  • A slice is a data type without an owner, which allows us to create subsets from arrays, vectors and strings.
  • A slice is created from an existing variable of an array, vector or string by referencing its memory address.
  • A slice needs a start and end index number to specify where to pull the values from.
  • Values in a slice can be mutated if we mark the original data container as mut.
  • A data container can be passed to a function as a value to be sliced if we mark the parameter with the & reference operator.