What slicing does
A slice selects a run of consecutive characters from a string, the way you would cut a piece out of a ribbon. In Rust you write a range in square brackets and put a & in front:
let language = "JavaScript";
let substring = &language[2..6];
println!("{}", substring);
println!("{}", language);
vaSc
JavaScript
The range 2..6 starts at position 2 and stops just before position 6, so it covers positions 2, 3, 4 and 5. Positions count from 0. The result is the four letters vaSc.
A slice is a borrowed view
The slice does not copy anything. The type of &language[2..6] is &str, a window onto the bytes of the original string. That is why the original is unchanged, and why the leading & is needed: without it, language[2..6] is a bare str of unknown size, and a variable can't hold that. If you want an independent string, copy the slice with .to_string(), which gives a String you can modify.
Range forms
You can leave out either end of the range:
| Range | Meaning | On "JavaScript" |
|---|---|---|
2..6 | from 2 up to, not including, 6 | vaSc |
4.. | from 4 to the end | Script |
..4 | from the start up to, not including, 4 | Java |
.. | the whole string | JavaScript |
2..=5 | from 2 to 5, both included | vaSc |
The end of a normal range is excluded, so the length of a..b is b - a. An empty range such as 3..3 gives an empty string.
Positions are bytes
Rust measures a slice in bytes, not characters. For ASCII text such as English letters and digits, one character is one byte, so the numbers match what you count by eye. A character such as é takes two bytes. If a range starts or ends inside a character, the program panics at run time with a message saying the byte index is not a char boundary. If a position is beyond the end, or the start is greater than the end, it panics too:
let f = "Avocado";
let a = 4;
println!("{}", &f[a..2]);
begin <= end (4 <= 2) when slicing `Avocado`
When the positions might be bad, use get, which returns an Option<&str>: message.get(5..40) is None for an 18-byte message instead of a panic.
Computed positions
The range ends can be any usize expression, not only literals. With let n = 12; the slice &message[..n] is the first 12 bytes, and &message[message.len() - 5..] is the last five. Since len() is the number of bytes, subtracting from it counts back from the end.
The lesson's exercise
The starter code gives message as "Coding feels great". The exercise cuts out three words.
- The first word. The first 6 characters sit at positions 0 to 5, so the range is
0..6. - The middle word. The 8th through 12th characters are at positions 7 to 11, because positions start at 0. The range is
7..12, and it skips the space at position 6. - The last word. The last 5 characters begin at position 13 and run to the end. Leaving the end off,
13.., means "to the end of the string".
let message = "Coding feels great";
let first_word = &message[0..6];
let middle_word = &message[7..12];
let last_word = &message[13..];
These give Coding, feels and great. The whole message is 18 bytes long, and 13.. covers positions 13 to 17, which is five characters.
Common mistakes
- Including the end. Writing
0..5for "the first six characters" gives only five. Take the last wanted position and add one. - Forgetting the
&.let w = message[0..6];is a compile error, becausestrhas no size known at compile time. - Off-by-one on the space. The words in the exercise are separated by spaces, which have positions of their own. The space after
Codingis at position 6, so the second word starts at 7. - Slicing text that is not ASCII with byte numbers computed by hand.
Key takeaways
&s[a..b]borrows the bytes fromaup to, but not including,b.- Either end may be left out;
a..runs to the end and..bstarts at the beginning. - The slice is a
&strview and the original string does not change. - Bad ranges panic;
getreturnsNoneinstead.
