DEV Community

Cover image for Slices in Rust
Francesco Ciulla
Francesco Ciulla

Posted on

Slices in Rust

The Slice type in Rust

Rust has a built-in type called Slice that is used to reference a contiguous sequence of elements in a collection. Slices are a very important concept in Rust, and they are used extensively in the standard library. In this lesson, we will explore the Slice type and how it is used in Rust.

If you prefer a video version

All the code is available on GitHub (link available in the video description)

What is a Slice?

A slice is a reference to a contiguous sequence of elements in a collection.

Slices are used to reference a portion of a collection, and they are used extensively in the standard library.

Slices are a very important concept in Rust, and they are used extensively in the standard library. In this lesson, we will explore the Slice type and how it is used in Rust.

A first example

Let's see a simple example of a slice. We will use an array:



fn main() {
    let a = ["a", "b", "c", "d", "e"];
    let slice = &a[1..3];
    println!("{:?}", slice);
}


Enter fullscreen mode Exit fullscreen mode

In this example, we have an array a with five elements. We then create a slice slice that references the second and third elements of the array.

When we run the program, we get the following output:



["b", "c"]


Enter fullscreen mode Exit fullscreen mode

The Range 1..3 is used to create the slice. The first number is the starting index, and the second number is the ending index.

The ending index is exclusive, which means that the element at the ending index is not included in the slice.

A second example

Let's try a slice of a vector of integers:



fn main() {
    let v = vec![10, 20, 30, 40, 50];
    let slice = &v[3..4];
    println!("{:?}", slice);
}


Enter fullscreen mode Exit fullscreen mode

In this example, we have a vector v with five elements. We then create a slice slice that references the fourth element of the vector.

When we run the program, we get the following output:



[40]


Enter fullscreen mode Exit fullscreen mode

Third Example

Let's try a slice of a string:



fn main() {
    let s = String::from("hello world");
    let hello = &s[0..5];
    let world = &s[6..11];
    println!("{:?}", hello);
    println!("{:?}", world);
}


Enter fullscreen mode Exit fullscreen mode

In this example, we have a string s with the value "hello world". We then create two slices, hello and world, that reference the first five and last five characters of the string, respectively.

When we run the program, we get the following output:



"hello"
"world"


Enter fullscreen mode Exit fullscreen mode

Slices in Rust - Rust programming tutorial

Range shortcuts for slices

There are some shortcuts for creating slices. For example, if you want to start at index 0, you can omit the first number:



let s = String::from("hello");
let slice = &s[0..2];
let slice = &s[..2];


Enter fullscreen mode Exit fullscreen mode

Will have as an output:



"he"


Enter fullscreen mode Exit fullscreen mode

Also, if you want to go to the end of the string, you can omit the second number:



let s = String::from("hello");
let len = s.len();
let slice = &s[3..len];
let slice = &s[3..];


Enter fullscreen mode Exit fullscreen mode

Will have as an output:



"lo"


Enter fullscreen mode Exit fullscreen mode

You can also get the entire string by using the .. syntax:



let s = String::from("hello");
let len = s.len();
let slice = &s[0..len];
let slice = &s[..];


Enter fullscreen mode Exit fullscreen mode

Will have as an output:



"hello"


Enter fullscreen mode Exit fullscreen mode

Exercise (without Slices)

Let's write a program that takes a string and returns the first word in the string.

First, let'simplement a solution without using slices.

Here is the code:



fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s);
    println!("the first word is: {}", word);
}


fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }

    s.len()
}


Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The as_bytes method returns a slice of the string's bytes.
  • The iter method returns an iterator over the slice.
  • The enumerate method returns an iterator that yields the index and the value of each element in the slice.
  • The b' ' syntax is used to create a byte literal.
  • The s.len() method returns the length of the string.

We get this output:



the first word is: 5


Enter fullscreen mode Exit fullscreen mode

In this case, 5 is the index of the first space in the string, which is the end of the first word.

But there is a problem with this code. If we try to clear the string s after calling the first_word function, we will NOT get a compile error. The variable word will still have the value 5, even though the string s has been cleared.



fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s); // word will get the value 5
    println!("the s is: {}", s);
    println!("the first word is: {}", word);

    s.clear(); // this empties the String, making it equal to ""
    println!("the s is: {}", s);
    println!("the first word is: {}", word);    
}


Enter fullscreen mode Exit fullscreen mode

The output will be:



the s is: hello world
the first word is: 5
the s is:
the first word is: 5


Enter fullscreen mode Exit fullscreen mode

The word variable still has the value 5, but the string s has been cleared.

Let's see how we can fix this using slices.

Exercise (using slices)

Let's go back to our programming example.

Using slices, we can rewrite the first_word function like this:



fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}


Enter fullscreen mode Exit fullscreen mode

Now, the first_word function returns a string slice, which is a reference to part of the original string.

If now we have somerthing like this:



fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s); // word will get the value 5
    println!("the s is: {}", s);
    println!("the first word is: {}", word);

    s.clear(); // this empties the String, making it equal to ""
    println!("the s is: {}", s);
    println!("the first word is: {}", word);    
}


Enter fullscreen mode Exit fullscreen mode

We will get a compile error, because we are trying to use word after s has been cleared.

String literals as Slices

Recall that we talked about string literals being stored inside the binary.

Now that we know about slices, we can understand string literals.

Update the code in the main function to use a string literal below the s variable (you can keep the lines that use s as well). :



fn main() {
    let s = String::from("hello world");
    let word = first_word(&s); // word will get the value 5
    println!("the s is: {}", s);
    println!("the first word is: {}", word);

    let s2 = "hello world";
    let word2 = first_word(&s2); // word will get the value 5
    println!("the s is: {}", s2);
    println!("the first word is: {}", word2);
}

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}


Enter fullscreen mode Exit fullscreen mode

The type of s is &str, which is a slice of a string. This means that s is a reference to a contiguous sequence of characters in memory.

As a final note, the type of s2 is &str, which is a slice of a string. This means that s2 is a reference to a contiguous sequence of characters in memory.

So we can remove the &s2 from the first_word function and the code will still work.

Conclusion

In this lesson, we explored the Slice type in Rust. We learned that a slice is a reference to a contiguous sequence of elements in a collection.

We also learned how to create slices using the Range type and how to use slices to reference parts of a string.

In the next lesson, we will see the Struct type in Rust.

If you prefer a video version

All the code is available on GitHub (link available in the video description)

Top comments (6)

Collapse
 
lexlohr profile image
Alex Lohr

I'm missing the ability to define the end of the slice as inclusive in this otherwise very good description, e.g. like s[1..=3].

Collapse
 
francescoxx profile image
Francesco Ciulla

you are right, thanks for pointing it out!

Collapse
 
get_pieces profile image
Pieces 🌟

New Rust guide!🔥

Collapse
 
francescoxx profile image
Francesco Ciulla

you are welcome

Collapse
 
shedrick profile image
Shedrick Williams

Nice piece! Slices of collection types are something I’ve kind of had a hard time grasping, especially which part would be inclusive vs. which would be exclusive, so this helps for sure 👍

Collapse
 
francescoxx profile image
Francesco Ciulla

you are welcome