1 00:00:00,040 --> 00:00:07,540 Welcome to module 3, Visualizing lifetimes to understand borrow checker errors. In the previous module, 2 00:00:07,550 --> 00:00:16,000 we looked at lifetimes of values and references in four examples that compiled because all of the lifetimes were valid. 3 00:00:16,070 --> 00:00:20,950 In this module, we're going to look at five examples that don't compile because they would create invalid references. 4 00:00:21,940 --> 00:00:31,250 The examples are returning a reference to a value created within an inner scope, returning a reference to a value created within a function, referencing a value that is moved, 5 00:00:31,740 --> 00:00:41,440 creating a struct that refers to itself, and storing references in a HashMap. 6 00:00:41,820 --> 00:00:43,950 For each example, we'll draw the lifetimes to understand why the references would be invalid, and we'll talk about how to fix the problems. 7 00:00:45,490 --> 00:00:49,650 Let's start with returning a reference to a value that's created within an inner scope. 8 00:00:51,240 --> 00:00:55,450 This first example is similar to examples 1 and 2 from the previous module. 9 00:00:55,940 --> 00:00:58,850 We create a vector and bind it to the variable list, 10 00:00:59,340 --> 00:01:02,350 and then we create a reference to the first two items in the list. 11 00:01:03,380 --> 00:01:07,000 The difference is that rather than creating the list in the outer scope of main, 12 00:01:07,160 --> 00:01:13,650 we're creating the list in an inner scope and attempting to return the reference out of that scope to the first_two variable. 13 00:01:14,640 --> 00:01:19,920 The lifetime of the list value starts where list is created and ends at the end of the inner scope 14 00:01:19,930 --> 00:01:23,210 where list gets cleaned up. As written, 15 00:01:23,220 --> 00:01:25,090 we're trying to use the reference to list, 16 00:01:25,100 --> 00:01:29,050 starting from the creation of the first_two variable through the end of main. 17 00:01:30,110 --> 00:01:34,180 The lifetime of the reference is longer than the lifetime of the value it's referencing, 18 00:01:34,260 --> 00:01:35,350 which isn't allowed. 19 00:01:36,440 --> 00:01:38,250 Let's look at the compiler error we get. 20 00:01:38,690 --> 00:01:41,480 It says `list` does not live long enough, 21 00:01:41,730 --> 00:01:49,650 then it points to the reference to the first two items of the list that we try to return from the inner scope, and says that the borrowed value does not live long enough. 22 00:01:50,200 --> 00:01:51,730 At the end of the inner scope, 23 00:01:51,830 --> 00:01:52,999 it says list has dropped 24 00:01:53,000 --> 00:01:53,980 while still borrowed, 25 00:01:54,130 --> 00:01:57,250 and it says the borrowed value needs to live until the end of main. 26 00:01:58,340 --> 00:02:04,390 The compiler is drawing the same conclusions that we drew with the lines, just explaining it in a slightly different way. 27 00:02:04,945 --> 00:02:08,455 Fixing this problem depends on what we're ultimately trying to do. 28 00:02:10,045 --> 00:02:15,805 We could move all uses of the reference into the inner scope rather than returning the reference from the inner scope, 29 00:02:15,815 --> 00:02:18,605 which would make the reference's lifetime shorter than the values. 30 00:02:19,545 --> 00:02:22,505 Or, we can move the list creation out of the inner scope, 31 00:02:22,515 --> 00:02:25,355 which makes the value's lifetime longer than the references. 32 00:02:26,945 --> 00:02:27,565 Next, 33 00:02:27,635 --> 00:02:31,055 let's see what happens when we try to return a reference out of a function. 34 00:02:32,745 --> 00:02:36,255 This example is similar to one we looked at in the Borrowing module in unit 35 00:02:36,265 --> 00:02:36,725 2. 36 00:02:36,735 --> 00:02:43,205 But this time, we're going to look at it in the context of lifetimes. Rather than returning a reference from an inner scope, 37 00:02:43,215 --> 00:02:44,725 as we did in the first example, 38 00:02:44,955 --> 00:02:49,855 we're instead returning a reference out of a function that points to a value created in that function. 39 00:02:50,925 --> 00:02:58,755 The list value's lifetime starts in the return_first_two function where it's created and ends at the end of the function where it goes out of scope. 40 00:02:59,345 --> 00:03:02,025 This code is trying to create a reference to list, 41 00:03:02,165 --> 00:03:05,005 return it from the function, and use it in main, 42 00:03:05,085 --> 00:03:08,555 from where the first_two variable is created, until the end of main. 43 00:03:10,065 --> 00:03:13,725 That would make the reference's lifetime longer than the value it's referring to, 44 00:03:13,735 --> 00:03:15,455 which, again, would be invalid. 45 00:03:17,345 --> 00:03:19,455 Looking at the compiler error for this code, 46 00:03:19,535 --> 00:03:21,124 it says there's a missing lifetime 47 00:03:21,125 --> 00:03:24,795 specifier and points to a spot where it expected a lifetime parameter. 48 00:03:25,345 --> 00:03:33,484 The help text explains that there isn't a value for the borrowed parameter to be borrowed from because there isn't a value passed in. 49 00:03:33,485 --> 00:03:36,305 The compiler can tell we must be trying to borrow a value we create in the function, 50 00:03:36,315 --> 00:03:37,395 which isn't valid. 51 00:03:37,945 --> 00:03:42,355 It then goes on to suggest giving the return type a 'static lifetime. 52 00:03:43,345 --> 00:03:44,654 We'll be talking about lifetime 53 00:03:44,655 --> 00:03:48,945 specifiers, lifetime parameters, and the 'static lifetime in future videos, 54 00:03:49,245 --> 00:03:54,254 along with whether that suggestion would fix this problem or not. 55 00:03:54,255 --> 00:03:55,735 A fix not suggested by the compiler, 56 00:03:55,745 --> 00:03:58,075 is to move the owned list out of the function, 57 00:03:58,185 --> 00:04:02,055 so the return type is a vector of i32 rather than a slice. 58 00:04:02,645 --> 00:04:04,955 Then, we would create the reference in main instead, 59 00:04:05,115 --> 00:04:08,955 which again makes the lifetime of the reference shorter than that of the value. 60 00:04:10,498 --> 00:04:11,708 For the next example, 61 00:04:11,828 --> 00:04:16,437 let's try creating a reference to a value that gets moved. 62 00:04:16,438 --> 00:04:20,148 In this code, we'll create a vector and bind it to the variable 63 00:04:20,158 --> 00:04:20,708 list_a. 64 00:04:21,258 --> 00:04:24,128 Then, we'll transfer ownership to the variable list_b. 65 00:04:24,138 --> 00:04:30,848 After moving the vector out of list_a, we'll attempt to take a reference to the last two elements in list_a and 66 00:04:30,858 --> 00:04:31,608 print them out. 67 00:04:32,698 --> 00:04:38,608 The lifetime of list_a ends when we transfer ownership to list_b because the value is moved. 68 00:04:39,138 --> 00:04:42,208 Its lifetime in that memory location is over. 69 00:04:42,798 --> 00:04:47,698 The lifetime of the reference in first_two is entirely disjointed from the lifetime of list_a. 70 00:04:47,708 --> 00:04:50,457 So, this example isn't valid because a reference's 71 00:04:50,458 --> 00:04:55,108 lifetime must be completely contained within the lifetime of the value being referenced. 72 00:04:56,698 --> 00:05:00,408 The compiler error tells us we can't use a value after we've moved it. 73 00:05:01,928 --> 00:05:08,988 If we swap the lines that move the vector and take a reference so that we try to hold on to and use a reference after we've moved its value, 74 00:05:09,148 --> 00:05:10,508 this would also be invalid. 75 00:05:11,558 --> 00:05:14,918 The lifetime of the reference overlaps that of the value now, 76 00:05:14,928 --> 00:05:18,308 but it still isn't completely contained within the value's lifetime. 77 00:05:19,898 --> 00:05:23,408 The compiler now says list_a can't be moved because it's borrowed. 78 00:05:24,498 --> 00:05:29,598 A fix that works in Rust 1.24.1 is to move the vector into list_b 79 00:05:29,608 --> 00:05:40,178 last, and create and use the reference in an inner scope that ends before we transfer ownership. In Rust 1.31 and up, when using the Rust 2018 edition, 80 00:05:40,488 --> 00:05:45,508 the borrow checker has learned to see when you're done with references, so the inner scope is no longer needed. 81 00:05:46,998 --> 00:05:52,908 Next, let's take a look at why the compiler won't allow a struct to hold a reference that points to a part of itself. 82 00:05:53,398 --> 00:05:54,498 In the second example, 83 00:05:54,508 --> 00:05:56,548 when we tried to return a reference from a function, 84 00:05:56,728 --> 00:06:01,808 a solution you may have been considering is returning both the vector and the reference together in one type. 85 00:06:02,398 --> 00:06:10,737 Let's try that now. We'll define a struct named ListAndRef that stores both a vector and a slice that references the vector's 86 00:06:10,738 --> 00:06:18,549 first_two items. In the return_list_and_first_two function, we'll create a vector and bind it to the variable list_to_use. 87 00:06:19,139 --> 00:06:25,578 Then, we'll create an instance of the ListAndRef struct with the list_to_use vector in the list field of the struct in a slice 88 00:06:25,579 --> 00:06:28,999 referencing the first two elements of list_to_use in the first_two field. 89 00:06:29,529 --> 00:06:31,259 Then, we'll return the instance. 90 00:06:32,349 --> 00:06:34,699 There are multiple reasons that this is a problem. 91 00:06:35,689 --> 00:06:36,529 First of all, 92 00:06:36,539 --> 00:06:38,899 the struct owns the data in the list field. 93 00:06:39,149 --> 00:06:48,999 So, when we assign list_to_use, ownership is moved to the struct. We're then not allowed to create a slice referencing list_to_use because that would be a borrow after a move. 94 00:06:50,089 --> 00:06:52,339 Even if we could create the slice reference, 95 00:06:52,349 --> 00:06:53,499 we'd have another problem. 96 00:06:53,989 --> 00:06:56,539 Ownership is moved when a value is returned from a function. 97 00:06:57,549 --> 00:07:06,199 The fields in memory, before the end of the function, would hold the list and the reference to the list. Moving a value copies values on the stack. 98 00:07:06,689 --> 00:07:09,339 When the struct instance is moved to a different location, 99 00:07:09,429 --> 00:07:12,799 the reference would still point to the old location of the list field. 100 00:07:13,889 --> 00:07:16,059 That location would now be invalid. 101 00:07:17,089 --> 00:07:20,869 If Rust automatically fixed up references like this on a move, 102 00:07:20,879 --> 00:07:23,659 moving would no longer be the quick operation it is today. 103 00:07:24,189 --> 00:07:31,829 Just imagine a struct with lots of self references. If you have a situation where you need to have a struct hold a reference to itself, 104 00:07:31,839 --> 00:07:34,309 check out the rental or owning-ref crates, 105 00:07:34,559 --> 00:07:39,299 which use unsafe code but expose safe wrappers to manage self-referential structs. 106 00:07:40,889 --> 00:07:41,529 Finally, 107 00:07:41,589 --> 00:07:45,199 let's look at some problems that can come up with HashMaps and references. 108 00:07:46,789 --> 00:07:51,399 This program will count the number of times words are used in text provided by user input. 109 00:07:52,389 --> 00:07:56,199 We create a HashMap to store the words and the number of times we've seen them. 110 00:07:56,749 --> 00:07:58,058 Then, we create a String to hold 111 00:07:58,059 --> 00:07:59,249 the user input, 112 00:07:59,489 --> 00:08:00,899 and we read a line of input. 113 00:08:01,429 --> 00:08:02,059 Next, 114 00:08:02,069 --> 00:08:05,249 we split the user input on whitespace and count the words. 115 00:08:05,569 --> 00:08:07,319 If the word isn't in the HashMap yet, 116 00:08:07,509 --> 00:08:09,389 we insert it with a 0 value, 117 00:08:09,489 --> 00:08:10,899 then add 1 to the value. 118 00:08:11,389 --> 00:08:12,049 Finally, 119 00:08:12,059 --> 00:08:13,199 we print out the HashMap. 120 00:08:14,294 --> 00:08:21,244 The lifetime of the counts HashMap starts where we create a new HashMap and ends at the end of main, annotated here in blue. 121 00:08:21,814 --> 00:08:28,464 The lifetime of the input string starts where we create the new String and ends at the end of main just before counts is dropped, 122 00:08:28,474 --> 00:08:29,904 annotated here in orange. 123 00:08:30,964 --> 00:08:36,004 The split_whitespace method returns slices that reference the string owned by the input variable. 124 00:08:36,594 --> 00:08:39,184 When we insert the word slices into the HashMap, 125 00:08:39,414 --> 00:08:42,634 those references then need to live as long as the HashMap does, 126 00:08:42,644 --> 00:08:44,984 which would mean that they would outlive the input string, 127 00:08:45,254 --> 00:08:46,504 so this isn't allowed. 128 00:08:47,544 --> 00:08:53,314 This is really subtle, and actually works with Rust 2018 because the Rust borrow checker learned some new tricks. 129 00:08:53,664 --> 00:08:58,304 So, let's make it more obvious and make it not work in any Rust version by adding an inner scope. 130 00:08:58,794 --> 00:09:02,604 Now it's easier to see that input gets cleaned up at the end of the inner scope, 131 00:09:02,744 --> 00:09:05,804 but we need it to be valid to print out the counts in the inner scope. 132 00:09:06,854 --> 00:09:09,094 Rust 1.24 explains this 133 00:09:09,104 --> 00:09:11,204 as 'input' does not live long enough. 134 00:09:12,254 --> 00:09:13,564 In this particular example, 135 00:09:13,614 --> 00:09:17,604 we can fix the problem by swapping the order in which we declare counts and input. 136 00:09:18,094 --> 00:09:19,614 If we declare input first, 137 00:09:19,724 --> 00:09:26,094 it is dropped after counts is. Then, the lifetimes of the references don't need to last longer than input lives 138 00:09:26,204 --> 00:09:27,374 and this code is valid. 139 00:09:28,394 --> 00:09:29,124 However, 140 00:09:29,194 --> 00:09:34,404 what if we want to accept multiple lines of input in a loop and keep a running count of word frequencies? 141 00:09:34,994 --> 00:09:35,824 In this case, 142 00:09:35,894 --> 00:09:39,334 the input string goes out of scope at the end of each loop iteration, 143 00:09:39,674 --> 00:09:43,274 but we would need the word slices to be valid until the end of main, 144 00:09:43,284 --> 00:09:44,504 where counts is valid. 145 00:09:45,494 --> 00:09:52,104 We can't fix this program in the same way we did the variation without the loop by moving the input declaration above the declaration of counts. 146 00:09:52,654 --> 00:09:53,414 Instead, 147 00:09:53,494 --> 00:09:59,004 the fix is to copy each word in order to store owned strings rather than string slices in the HashMap. 148 00:09:59,524 --> 00:10:04,974 This gets rid of all of the reference problems. 149 00:10:04,984 --> 00:10:05,803 In the future, when you get error 150 00:10:05,804 --> 00:10:07,774 messages like the ones we saw in this module, 151 00:10:07,884 --> 00:10:08,883 such as borrowed value 152 00:10:08,884 --> 00:10:10,114 does not live long enough, 153 00:10:10,124 --> 00:10:11,323 remember that it means Rust 154 00:10:11,324 --> 00:10:14,104 can't prove that all of your references will always be valid. 155 00:10:15,108 --> 00:10:18,718 Try looking at where the lifetimes of the values and the references are 156 00:10:18,758 --> 00:10:18,887 to 157 00:10:18,888 --> 00:10:22,848 understand what code needs to be rearranged. In this unit, 158 00:10:22,858 --> 00:10:27,248 we looked at the concrete lifetimes in five examples with references that would be invalid. 159 00:10:28,238 --> 00:10:31,218 We saw that we can't return a reference out of the scope 160 00:10:31,228 --> 00:10:34,948 its value is created in or out of the function its value was created in. 161 00:10:35,438 --> 00:10:41,648 We saw that the lifetime of a value ends when the value is moved, so references can't outlive a move either. 162 00:10:42,138 --> 00:10:45,067 We discussed how self-referential structs aren't something safe 163 00:10:45,068 --> 00:10:47,848 Rust allows because the references would become invalid. 164 00:10:48,438 --> 00:10:52,008 And we covered problems that might arise when storing references in a HashMap, 165 00:10:52,438 --> 00:10:53,737 when the HashMap outlives the values referenced. 166 00:10:53,738 --> 00:11:00,678 The experience you've gained with concrete lifetimes will be useful to understand generic lifetimes, the next module's topic.