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);
}
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"]
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);
}
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]
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);
}
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"
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];
Will have as an output:
"he"
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..];
Will have as an output:
"lo"
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[..];
Will have as an output:
"hello"
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()
}
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
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);
}
The output will be:
the s is: hello world
the first word is: 5
the s is:
the first word is: 5
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[..]
}
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);
}
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[..]
}
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)
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]
.you are right, thanks for pointing it out!
New Rust guide!🔥
you are welcome
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 👍
you are welcome