]>
Commit | Line | Data |
---|---|---|
83c7162d XL |
1 | ## Storing Lists of Values with Vectors |
2 | ||
3 | The first collection type we’ll look at is `Vec<T>`, also known as a *vector*. | |
4 | Vectors allow you to store more than one value in a single data structure that | |
5 | puts all the values next to each other in memory. Vectors can only store values | |
6 | of the same type. They are useful when you have a list of items, such as the | |
7 | lines of text in a file or the prices of items in a shopping cart. | |
8 | ||
9 | ### Creating a New Vector | |
10 | ||
11 | To create a new, empty vector, we can call the `Vec::new` function, as shown in | |
12 | Listing 8-1: | |
13 | ||
14 | ```rust | |
15 | let v: Vec<i32> = Vec::new(); | |
16 | ``` | |
17 | ||
18 | <span class="caption">Listing 8-1: Creating a new, empty vector to hold values | |
19 | of type `i32`</span> | |
20 | ||
21 | Note that we added a type annotation here. Because we aren’t inserting any | |
22 | values into this vector, Rust doesn’t know what kind of elements we intend to | |
23 | store. This is an important point. Vectors are implemented using generics; | |
24 | we’ll cover how to use generics with your own types in Chapter 10. For now, | |
25 | know that the `Vec<T>` type provided by the standard library can hold any type, | |
26 | and when a specific vector holds a specific type, the type is specified within | |
27 | angle brackets. In Listing 8-1, we’ve told Rust that the `Vec<T>` in `v` will | |
28 | hold elements of the `i32` type. | |
29 | ||
30 | In more realistic code, Rust can often infer the type of value you want to | |
31 | store once you insert values, so you rarely need to do this type annotation. | |
32 | It’s more common to create a `Vec<T>` that has initial values, and Rust | |
33 | provides the `vec!` macro for convenience. The macro will create a new vector | |
34 | that holds the values you give it. Listing 8-2 creates a new `Vec<i32>` that | |
35 | holds the values `1`, `2`, and `3`: | |
36 | ||
37 | ```rust | |
38 | let v = vec![1, 2, 3]; | |
39 | ``` | |
40 | ||
41 | <span class="caption">Listing 8-2: Creating a new vector containing | |
42 | values</span> | |
43 | ||
44 | Because we’ve given initial `i32` values, Rust can infer that the type of `v` | |
45 | is `Vec<i32>`, and the type annotation isn’t necessary. Next, we’ll look at how | |
46 | to modify a vector. | |
47 | ||
48 | ### Updating a Vector | |
49 | ||
50 | To create a vector and then add elements to it, we can use the `push` method, | |
51 | as shown in Listing 8-3: | |
52 | ||
53 | ```rust | |
54 | let mut v = Vec::new(); | |
55 | ||
56 | v.push(5); | |
57 | v.push(6); | |
58 | v.push(7); | |
59 | v.push(8); | |
60 | ``` | |
61 | ||
62 | <span class="caption">Listing 8-3: Using the `push` method to add values to a | |
63 | vector</span> | |
64 | ||
65 | As with any variable, if we want to be able to change its value, we need to | |
66 | make it mutable using the `mut` keyword, as discussed in Chapter 3. The numbers | |
67 | we place inside are all of type `i32`, and Rust infers this from the data, so | |
68 | we don’t need the `Vec<i32>` annotation. | |
69 | ||
70 | ### Dropping a Vector Drops Its Elements | |
71 | ||
72 | Like any other `struct`, a vector is freed when it goes out of scope, as | |
73 | annotated in Listing 8-4: | |
74 | ||
75 | ```rust | |
76 | { | |
77 | let v = vec![1, 2, 3, 4]; | |
78 | ||
79 | // do stuff with v | |
80 | ||
81 | } // <- v goes out of scope and is freed here | |
82 | ``` | |
83 | ||
84 | <span class="caption">Listing 8-4: Showing where the vector and its elements | |
85 | are dropped</span> | |
86 | ||
87 | When the vector gets dropped, all of its contents are also dropped, meaning | |
88 | those integers it holds will be cleaned up. This may seem like a | |
89 | straightforward point but can get a bit more complicated when you start to | |
90 | introduce references to the elements of the vector. Let’s tackle that next! | |
91 | ||
92 | ### Reading Elements of Vectors | |
93 | ||
94 | Now that you know how to create, update, and destroy vectors, knowing how to | |
95 | read their contents is a good next step. There are two ways to reference a | |
96 | value stored in a vector. In the examples, we’ve annotated the types of the | |
97 | values that are returned from these functions for extra clarity. | |
98 | ||
94b46f34 XL |
99 | Listing 8-5 shows the method of accessing a value in a vector with |
100 | indexing syntax: | |
83c7162d XL |
101 | |
102 | ```rust | |
103 | let v = vec![1, 2, 3, 4, 5]; | |
104 | ||
105 | let third: &i32 = &v[2]; | |
83c7162d XL |
106 | ``` |
107 | ||
94b46f34 XL |
108 | <span class="caption">Listing 8-5: Using indexing syntax to |
109 | access an item in a vector</span> | |
110 | ||
111 | Listing 8-6 shows the method of accessing a value in a vector, with | |
112 | the `get` method: | |
113 | ||
114 | ```rust | |
115 | let v = vec![1, 2, 3, 4, 5]; | |
116 | let v_index = 2; | |
117 | ||
118 | match v.get(v_index) { | |
119 | Some(_) => { println!("Reachable element at index: {}", v_index); }, | |
120 | None => { println!("Unreachable element at index: {}", v_index); } | |
121 | } | |
122 | ``` | |
123 | ||
124 | <span class="caption">Listing 8-6: Using the `get` method to | |
83c7162d XL |
125 | access an item in a vector</span> |
126 | ||
127 | Note two details here. First, we use the index value of `2` to get the third | |
128 | element: vectors are indexed by number, starting at zero. Second, the two ways | |
129 | to get the third element are by using `&` and `[]`, which gives us a reference, | |
130 | or by using the `get` method with the index passed as an argument, which gives | |
131 | us an `Option<&T>`. | |
132 | ||
133 | Rust has two ways to reference an element so you can choose how the program | |
134 | behaves when you try to use an index value that the vector doesn’t have an | |
135 | element for. As an example, let’s see what a program will do if it has a vector | |
136 | that holds five elements and then tries to access an element at index 100, as | |
94b46f34 | 137 | shown in Listing 8-7: |
83c7162d XL |
138 | |
139 | ```rust,should_panic | |
140 | let v = vec![1, 2, 3, 4, 5]; | |
141 | ||
142 | let does_not_exist = &v[100]; | |
143 | let does_not_exist = v.get(100); | |
144 | ``` | |
145 | ||
94b46f34 | 146 | <span class="caption">Listing 8-7: Attempting to access the element at index |
83c7162d XL |
147 | 100 in a vector containing five elements</span> |
148 | ||
149 | When we run this code, the first `[]` method will cause the program to panic | |
150 | because it references a nonexistent element. This method is best used when you | |
151 | want your program to crash if there’s an attempt to access an element past the | |
152 | end of the vector. | |
153 | ||
154 | When the `get` method is passed an index that is outside the vector, it returns | |
155 | `None` without panicking. You would use this method if accessing an element | |
156 | beyond the range of the vector happens occasionally under normal circumstances. | |
157 | Your code will then have logic to handle having either `Some(&element)` or | |
158 | `None`, as discussed in Chapter 6. For example, the index could be coming from | |
159 | a person entering a number. If they accidentally enter a number that’s too | |
160 | large and the program gets a `None` value, you could tell the user how many | |
161 | items are in the current vector and give them another chance to enter a valid | |
162 | value. That would be more user-friendly than crashing the program due to a typo! | |
163 | ||
164 | When the program has a valid reference, the borrow checker enforces the | |
165 | ownership and borrowing rules (covered in Chapter 4) to ensure this reference | |
166 | and any other references to the contents of the vector remain valid. Recall the | |
167 | rule that states you can’t have mutable and immutable references in the same | |
94b46f34 | 168 | scope. That rule applies in Listing 8-8, where we hold an immutable reference to |
83c7162d XL |
169 | the first element in a vector and try to add an element to the end, which won’t |
170 | work: | |
171 | ||
172 | ```rust,ignore | |
173 | let mut v = vec![1, 2, 3, 4, 5]; | |
174 | ||
175 | let first = &v[0]; | |
176 | ||
177 | v.push(6); | |
178 | ``` | |
179 | ||
94b46f34 | 180 | <span class="caption">Listing 8-8: Attempting to add an element to a vector |
83c7162d XL |
181 | while holding a reference to an item</span> |
182 | ||
183 | Compiling this code will result in this error: | |
184 | ||
185 | ```text | |
186 | error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable | |
187 | --> | |
188 | | | |
189 | 4 | let first = &v[0]; | |
190 | | - immutable borrow occurs here | |
191 | 5 | | |
192 | 6 | v.push(6); | |
193 | | ^ mutable borrow occurs here | |
194 | 7 | | |
195 | 8 | } | |
196 | | - immutable borrow ends here | |
197 | ``` | |
198 | ||
94b46f34 | 199 | The code in Listing 8-8 might look like it should work: why should a reference |
83c7162d XL |
200 | to the first element care about what changes at the end of the vector? This |
201 | error is due to the way vectors work: adding a new element onto the end of the | |
202 | vector might require allocating new memory and copying the old elements to the | |
203 | new space, if there isn’t enough room to put all the elements next to each | |
204 | other where the vector currently is. In that case, the reference to the first | |
205 | element would be pointing to deallocated memory. The borrowing rules prevent | |
206 | programs from ending up in that situation. | |
207 | ||
208 | > Note: For more on the implementation details of the `Vec<T>` type, see “The | |
209 | > Rustonomicon” at https://doc.rust-lang.org/stable/nomicon/vec.html. | |
210 | ||
211 | ### Iterating over the Values in a Vector | |
212 | ||
213 | If we want to access each element in a vector in turn, we can iterate through | |
214 | all of the elements rather than use indexes to access one at a time. Listing | |
94b46f34 | 215 | 8-9 shows how to use a `for` loop to get immutable references to each element |
83c7162d XL |
216 | in a vector of `i32` values and print them: |
217 | ||
218 | ```rust | |
219 | let v = vec![100, 32, 57]; | |
220 | for i in &v { | |
221 | println!("{}", i); | |
222 | } | |
223 | ``` | |
224 | ||
94b46f34 | 225 | <span class="caption">Listing 8-9: Printing each element in a vector by |
83c7162d XL |
226 | iterating over the elements using a `for` loop</span> |
227 | ||
228 | We can also iterate over mutable references to each element in a mutable vector | |
94b46f34 | 229 | in order to make changes to all the elements. The `for` loop in Listing 8-10 |
83c7162d XL |
230 | will add `50` to each element: |
231 | ||
232 | ```rust | |
233 | let mut v = vec![100, 32, 57]; | |
234 | for i in &mut v { | |
235 | *i += 50; | |
236 | } | |
237 | ``` | |
238 | ||
94b46f34 | 239 | <span class="caption">Listing 8-10: Iterating over mutable references to |
83c7162d XL |
240 | elements in a vector</span> |
241 | ||
242 | To change the value that the mutable reference refers to, we have to use the | |
243 | dereference operator (`*`) to get to the value in `i` before we can use the | |
94b46f34 | 244 | `+=` operator . We'll talk more about `*` in Chapter 15. |
83c7162d XL |
245 | |
246 | ### Using an Enum to Store Multiple Types | |
247 | ||
248 | At the beginning of this chapter, we said that vectors can only store values | |
249 | that are the same type. This can be inconvenient; there are definitely use | |
250 | cases for needing to store a list of items of different types. Fortunately, the | |
251 | variants of an enum are defined under the same enum type, so when we need to | |
252 | store elements of a different type in a vector, we can define and use an enum! | |
253 | ||
254 | For example, say we want to get values from a row in a spreadsheet in which | |
255 | some of the columns in the row contain integers, some floating-point numbers, | |
256 | and some strings. We can define an enum whose variants will hold the different | |
257 | value types, and then all the enum variants will be considered the same type: | |
258 | that of the enum. Then we can create a vector that holds that enum and so, | |
94b46f34 | 259 | ultimately, holds different types. We’ve demonstrated this in Listing 8-11: |
83c7162d XL |
260 | |
261 | ```rust | |
262 | enum SpreadsheetCell { | |
263 | Int(i32), | |
264 | Float(f64), | |
265 | Text(String), | |
266 | } | |
267 | ||
268 | let row = vec![ | |
269 | SpreadsheetCell::Int(3), | |
270 | SpreadsheetCell::Text(String::from("blue")), | |
271 | SpreadsheetCell::Float(10.12), | |
272 | ]; | |
273 | ``` | |
274 | ||
94b46f34 | 275 | <span class="caption">Listing 8-11: Defining an `enum` to store values of |
83c7162d XL |
276 | different types in one vector</span> |
277 | ||
278 | Rust needs to know what types will be in the vector at compile time so it knows | |
279 | exactly how much memory on the heap will be needed to store each element. A | |
280 | secondary advantage is that we can be explicit about what types are allowed in | |
281 | this vector. If Rust allowed a vector to hold any type, there would be a chance | |
282 | that one or more of the types would cause errors with the operations performed | |
283 | on the elements of the vector. Using an enum plus a `match` expression means | |
284 | that Rust will ensure at compile time that every possible case is handled, as | |
285 | discussed in Chapter 6. | |
286 | ||
287 | When you’re writing a program, if you don’t know the exhaustive set of types | |
288 | the program will get at runtime to store in a vector, the enum technique won’t | |
289 | work. Instead, you can use a trait object, which we’ll cover in Chapter 17. | |
290 | ||
291 | Now that we’ve discussed some of the most common ways to use vectors, be sure | |
292 | to review the API documentation for all the many useful methods defined on | |
293 | `Vec<T>` by the standard library. For example, in addition to `push`, a `pop` | |
294 | method removes and returns the last element. Let’s move on to the next | |
295 | collection type: `String`! |