1
00:00:01,540 --> 00:00:02,410
In this module,
2
00:00:02,420 --> 00:00:09,559
we're going to go into detail about the slice data type. In unit 1, module 4, we briefly mentioned the slice
3
00:00:09,560 --> 00:00:12,300
primitive data type.
4
00:00:12,430 --> 00:00:13,890
Slices borrow data. So, now that you know about borrowing,
5
00:00:13,950 --> 00:00:21,100
we can cover slices more thoroughly. In this module, we'll talk about what a slice is and how to create them.
6
00:00:21,640 --> 00:00:26,350
We'll show why it's a good idea to use slices as parameter types in functions or methods.
7
00:00:26,840 --> 00:00:33,750
And we'll discuss how we were able to borrow a String in the last module and pass that to a function with a string slice as a parameter type.
8
00:00:35,240 --> 00:00:37,070
So, what is a slice?
9
00:00:38,540 --> 00:00:43,260
A slice is a data type that always borrows data owned by some other data structure,
10
00:00:43,440 --> 00:00:45,980
such as the String type that we covered in module 2.
11
00:00:46,990 --> 00:00:49,310
A slice consists of a pointer and a length.
12
00:00:50,340 --> 00:00:53,790
The pointer is a reference to the start of the data that the slice contains,
13
00:00:54,700 --> 00:00:58,880
and the length is the number of elements after the start that the slice contains.
14
00:01:00,080 --> 00:01:03,350
This slice contains the two ls from the hello string.
15
00:01:04,840 --> 00:01:07,060
Now that we know how a slice is stored,
16
00:01:07,210 --> 00:01:09,350
let's look at how to create a slice in code.
17
00:01:10,840 --> 00:01:14,950
Even though a slice is stored as a pointer and a length, in code,
18
00:01:15,120 --> 00:01:17,290
we create a slice using an ampersand,
19
00:01:17,590 --> 00:01:23,710
the variable we're referencing, and square brackets containing a range.
20
00:01:23,770 --> 00:01:26,569
In this case, the starting index is 2, and the ending index is
21
00:01:26,570 --> 00:01:27,250
4.
22
00:01:28,740 --> 00:01:31,320
If the slice you create starts at index 0,
23
00:01:31,580 --> 00:01:33,130
the starting index is optional,
24
00:01:33,190 --> 00:01:35,890
and you can just put two dots and the ending index.
25
00:01:36,740 --> 00:01:39,740
If the slice you create goes all the way to the end of the data structure,
26
00:01:40,100 --> 00:01:41,750
the ending index is optional.
27
00:01:42,740 --> 00:01:44,850
To make a slice that borrows all of the data,
28
00:01:45,240 --> 00:01:47,910
all you need inside the square brackets are two dots.
29
00:01:49,440 --> 00:01:53,919
We can also create slices from arrays and vectors. Recall from unit 1, module
30
00:01:53,920 --> 00:01:58,550
4, that arrays are fixed-length collections where all of the elements have the same type,
31
00:01:59,000 --> 00:02:01,450
such as this array of f64 values.
32
00:02:02,440 --> 00:02:07,950
We also mentioned, in that module, that vectors are similar to arrays but can grow or shrink in size,
33
00:02:08,270 --> 00:02:17,650
such as this vector of i32 values that we can add an element onto. Creating a slice from an array or vector is similar to creating a slice from a string:
34
00:02:18,240 --> 00:02:24,130
use an ampersand, then a range and square brackets.
35
00:02:24,190 --> 00:02:28,530
Like plain references, the borrow checker ensures slices are always valid. At runtime,
36
00:02:28,730 --> 00:02:30,730
Rust will panic and stop your program
37
00:02:30,750 --> 00:02:33,010
if slice indices are out of bounds.
38
00:02:33,530 --> 00:02:35,250
These protections prevent bugs.
39
00:02:36,790 --> 00:02:39,920
Let's look an example of trying to use invalid indices.
40
00:02:40,540 --> 00:02:41,430
In this program,
41
00:02:41,440 --> 00:02:43,430
we have a vector containing three elements,
42
00:02:43,440 --> 00:02:46,050
and we try to create a slice up to index 9.
43
00:02:47,040 --> 00:02:51,720
Even though we, as humans, can see that index 9 is going to be out of bounds in this example,
44
00:02:51,950 --> 00:02:55,250
Rust does not analyze the index with the slice when it compiles the code.
45
00:02:55,840 --> 00:03:00,149
If we had used a value provided by the user of the program rather than hardcoding the index,
46
00:03:00,150 --> 00:03:00,960
as we have here,
47
00:03:01,280 --> 00:03:04,050
Rust wouldn't even be able to do an analysis at compile time.
48
00:03:05,690 --> 00:03:07,760
This code will compile without any errors.
49
00:03:07,990 --> 00:03:10,510
Slice indices are not analyzed at compile time.
50
00:03:11,140 --> 00:03:11,780
However,
51
00:03:11,870 --> 00:03:13,040
when we run this program,
52
00:03:13,220 --> 00:03:17,170
it panics with the error index 9 out of range for slice of length 3.
53
00:03:20,040 --> 00:03:23,370
Because Rust can't prove the slice indices are valid at compile time,
54
00:03:23,590 --> 00:03:27,500
it checks the slice indices at runtime and deliberately terminates the program
55
00:03:27,510 --> 00:03:28,500
if they're invalid.
56
00:03:29,140 --> 00:03:31,040
This prevents the use of invalid memory,
57
00:03:31,250 --> 00:03:33,750
which would lead to seg faults or undefined behavior.
58
00:03:35,280 --> 00:03:37,330
String slices having an additional protection.
59
00:03:37,730 --> 00:03:42,100
The indices of the slice range must be at valid Unicode character boundaries.
60
00:03:43,620 --> 00:03:44,330
In this code,
61
00:03:44,520 --> 00:03:46,580
we have a string literal containing emoji,
62
00:03:46,750 --> 00:03:48,550
each of which use multiple bytes.
63
00:03:49,340 --> 00:03:51,950
We then attempt to create a slice from 0 to 1.
64
00:03:53,440 --> 00:03:55,739
This code also compiles.
65
00:03:55,740 --> 00:03:57,479
Character boundaries aren't checked at compile time
66
00:03:57,480 --> 00:03:57,950
either.
67
00:03:58,940 --> 00:04:00,010
When we run the program,
68
00:04:00,080 --> 00:04:02,229
it panics with the error message
69
00:04:02,230 --> 00:04:07,580
byte index 1 is not a char boundary. To ensure your program doesn't panic,
70
00:04:07,860 --> 00:04:11,360
consider using methods on String, like chars or char_indices,
71
00:04:11,600 --> 00:04:16,300
rather than slicing at arbitrary indices.
72
00:04:16,310 --> 00:04:23,670
In general, it's better to use a slice or a string slice as parameters to functions or methods, rather than borrowing a Vec, array, or String.
73
00:04:23,970 --> 00:04:27,320
Let's look at why. This function,
74
00:04:27,330 --> 00:04:29,270
named only_reference_to_array,
75
00:04:29,360 --> 00:04:34,010
has a parameter with the type of a reference to an array of three i32 values.
76
00:04:34,220 --> 00:04:36,750
That's the only type that this function will accept.
77
00:04:38,240 --> 00:04:39,120
This function,
78
00:04:39,130 --> 00:04:41,110
named only_reference_to_vector,
79
00:04:41,320 --> 00:04:42,820
also borrows its parameter.
80
00:04:43,060 --> 00:04:48,250
But this function will only accept a reference to a vector containing some number of i32 values.
81
00:04:49,840 --> 00:04:50,800
In contrast,
82
00:04:50,810 --> 00:04:51,580
this function,
83
00:04:51,590 --> 00:04:54,080
named reference_to_either_array_or_vector,
84
00:04:54,140 --> 00:04:56,770
has a slice of i32 values as its parameter.
85
00:04:57,090 --> 00:05:02,560
This function can be called with some number of i32 values borrowed from either an array or a vector.
86
00:05:02,890 --> 00:05:05,200
It can even be called with a slice of a slice.
87
00:05:06,740 --> 00:05:09,460
As you can see from this usage of each of the functions,
88
00:05:09,680 --> 00:05:15,850
using a slice as a parameter gives callers more flexibility and means functions can be used in more contexts.
89
00:05:17,240 --> 00:05:18,150
Similarly,
90
00:05:18,490 --> 00:05:22,820
by specifying string slices as parameters rather than borrowing an owned String,
91
00:05:23,170 --> 00:05:26,650
functions can accept either borrowed strings or string literals.
92
00:05:28,140 --> 00:05:30,560
String literals create string slices.
93
00:05:30,810 --> 00:05:33,270
Their text appears in your compiled program,
94
00:05:33,380 --> 00:05:35,850
which is also stored in memory, just like your data.
95
00:05:36,640 --> 00:05:39,250
The string slice points to this literal text.
96
00:05:40,740 --> 00:05:41,490
Finally,
97
00:05:41,500 --> 00:05:48,350
you may have been wondering how we're able to call functions that take a string slice by passing an argument that is a reference to an owned String.
98
00:05:49,940 --> 00:05:54,650
This is due to a combination of features Rust provides to make using slices more ergonomic.
99
00:05:55,440 --> 00:05:59,710
We're going to discuss how this works at a high level.
100
00:05:59,760 --> 00:06:04,140
First, the Standard Library includes the implementation of a trait called Deref on String,
101
00:06:04,290 --> 00:06:06,070
such that Rust knows how to convert
102
00:06:06,080 --> 00:06:10,300
a reference to a String into a string slice containing the whole String.
103
00:06:11,540 --> 00:06:12,120
Next,
104
00:06:12,400 --> 00:06:15,050
Rust has a feature called deref coercion.
105
00:06:15,490 --> 00:06:17,700
This means that when you call a function or method,
106
00:06:17,890 --> 00:06:20,530
the compiler will automatically dereference the arguments,
107
00:06:20,650 --> 00:06:21,440
if need be,
108
00:06:21,600 --> 00:06:24,150
to convert them to match the function parameter type.
109
00:06:25,690 --> 00:06:29,250
This is why we were able to call the pluralize function in the last module,
110
00:06:29,640 --> 00:06:32,230
even though it took a string slice as its parameter type,
111
00:06:32,400 --> 00:06:35,350
and we had an argument of a reference to an owned String.
112
00:06:36,770 --> 00:06:38,329
This works for arrays and vectors
113
00:06:38,330 --> 00:06:38,850
too.
114
00:06:39,240 --> 00:06:40,030
For example,
115
00:06:40,340 --> 00:06:40,669
earlier,
116
00:06:40,670 --> 00:06:43,260
when we called the either_array_or_vector function,
117
00:06:43,600 --> 00:06:51,550
we actually didn't need the square brackets and the range. A reference to the array or vector coerces to a slice of the whole array or vector.
118
00:06:52,940 --> 00:06:56,610
See the Deref traits documentation for other implementations.
119
00:06:58,100 --> 00:06:59,010
In this module,
120
00:06:59,070 --> 00:07:03,650
you learned that a slice is a way to borrow a contiguous chunk of data owned elsewhere.
121
00:07:04,740 --> 00:07:07,000
You know how to create a slice using an ampersand,
122
00:07:07,170 --> 00:07:08,950
square brackets, and a range.
123
00:07:09,940 --> 00:07:21,550
We demonstrated that using slices as parameters makes functions more flexible, and how the Deref trait and deref coercion combine to make slices ergonomic to use in common situations.
124
00:07:22,700 --> 00:07:23,670
In the next module,
125
00:07:23,770 --> 00:07:26,410
we'll talk about how borrowing interacts with mutability.