1 00:00:06,540 --> 00:00:07,830 - Let's take a quick recap 2 00:00:07,830 --> 00:00:12,240 of how to define and use static immutable variables. 3 00:00:12,240 --> 00:00:15,960 Here's a static immutable variable, message. 4 00:00:15,960 --> 00:00:19,560 I've initialized it with a compile time constant, hello. 5 00:00:19,560 --> 00:00:23,520 So that initialization would happen at compile time, 6 00:00:23,520 --> 00:00:26,430 and message is not mutable 7 00:00:26,430 --> 00:00:28,593 so the message will always be, hello. 8 00:00:29,580 --> 00:00:34,230 Okay, I've got another immutable variable here, timestamp. 9 00:00:34,230 --> 00:00:37,710 The value isn't initialized at compile time, 10 00:00:37,710 --> 00:00:42,240 the value is initialized lazily at run-time on first usage. 11 00:00:42,240 --> 00:00:44,280 The first time you access timestamp 12 00:00:44,280 --> 00:00:48,330 the code in here will be lazily evaluated, as we've seen, 13 00:00:48,330 --> 00:00:50,430 and assigned the timestamp. 14 00:00:50,430 --> 00:00:53,160 Thereafter timestamp is immutable. 15 00:00:53,160 --> 00:00:55,050 So in both of these cases 16 00:00:55,050 --> 00:00:59,760 message is initialized at compiled time and is immutable, 17 00:00:59,760 --> 00:01:02,070 timestamp is initialized there at run-time 18 00:01:02,070 --> 00:01:04,320 but is thereafter also immutable, 19 00:01:04,320 --> 00:01:07,350 so once it's initialized it's fixed. 20 00:01:07,350 --> 00:01:10,590 Okay, well, what about having mutable statics? 21 00:01:10,590 --> 00:01:13,170 You can define mutable statics, 22 00:01:13,170 --> 00:01:16,953 you give it the initial value but it can change afterwards. 23 00:01:17,880 --> 00:01:21,390 Right, so that sounds easy. 24 00:01:21,390 --> 00:01:24,990 It isn't, because thread-safety kicks in. 25 00:01:24,990 --> 00:01:28,500 The Rust compiler enforces thread-safety, 26 00:01:28,500 --> 00:01:32,550 it doesn't let you just change a static like this. 27 00:01:32,550 --> 00:01:34,260 You could try it, you get an error. 28 00:01:34,260 --> 00:01:36,030 What I tried to do here 29 00:01:36,030 --> 00:01:38,760 is I've tried to define a static local, 30 00:01:38,760 --> 00:01:39,750 as it happens to be local, 31 00:01:39,750 --> 00:01:42,210 it could have been global, happens to be local. 32 00:01:42,210 --> 00:01:44,490 I've tried to define a static variable 33 00:01:44,490 --> 00:01:47,400 as mutable, local count, 34 00:01:47,400 --> 00:01:52,173 so initialize it to zero initially and then increment it. 35 00:01:53,130 --> 00:01:54,870 Every time you call a function 36 00:01:54,870 --> 00:01:56,460 the value would be incremented. 37 00:01:56,460 --> 00:02:00,150 That won't compile, you get an error on the statement. 38 00:02:00,150 --> 00:02:02,070 The code isn't thread-safe, 39 00:02:02,070 --> 00:02:04,200 mutable threads might be trying to access 40 00:02:04,200 --> 00:02:06,570 that particular instruction at the same time, 41 00:02:06,570 --> 00:02:09,120 and they could, well, coincide with each other 42 00:02:09,120 --> 00:02:10,710 and then you've got chaos. 43 00:02:10,710 --> 00:02:13,830 So Rust doesn't allow you to... 44 00:02:13,830 --> 00:02:16,380 I mean, you can declare a mutable static 45 00:02:16,380 --> 00:02:18,030 but you can only update it 46 00:02:18,030 --> 00:02:20,310 if you can promise to be thread-safe, 47 00:02:20,310 --> 00:02:21,903 and this isn't thread-safe. 48 00:02:23,130 --> 00:02:26,010 So how do you do it? 49 00:02:26,010 --> 00:02:29,490 Well, one approach to achieve thread-safety 50 00:02:29,490 --> 00:02:33,469 would be to lock the variable using a mutex. 51 00:02:33,469 --> 00:02:36,093 A mutex is a mutual exclusion lock, 52 00:02:37,050 --> 00:02:40,320 so you write your code so that it locks a variable, 53 00:02:40,320 --> 00:02:43,080 updates it, and then unlocks the variable. 54 00:02:43,080 --> 00:02:46,170 So you have a mutex that protects access, 55 00:02:46,170 --> 00:02:47,850 it locks access to the variable, 56 00:02:47,850 --> 00:02:51,990 only one thread is allowed to update the variable at a time. 57 00:02:51,990 --> 00:02:55,923 Okay, so that's the idea of a mutual exclusion lock, 58 00:02:57,090 --> 00:03:00,327 similar to what you'd have in C++, you can a mutex. 59 00:03:00,327 --> 00:03:03,540 And in Java you can have the lock keyword 60 00:03:03,540 --> 00:03:06,870 to synchronize access to a variable. 61 00:03:06,870 --> 00:03:10,020 So other threads block until the lock is released. 62 00:03:10,020 --> 00:03:12,660 Thread number one locks the mutex 63 00:03:12,660 --> 00:03:16,320 and has access to the object that the mutex is protecting. 64 00:03:16,320 --> 00:03:17,910 If thread number two comes along 65 00:03:17,910 --> 00:03:19,350 and tries to lock the mutex, 66 00:03:19,350 --> 00:03:22,470 it can't because thread one already has it. 67 00:03:22,470 --> 00:03:24,750 Thread two blocks and waits 68 00:03:24,750 --> 00:03:27,480 until thread one finishes and releases the lock, 69 00:03:27,480 --> 00:03:29,250 and then thread two can kick in. 70 00:03:29,250 --> 00:03:30,690 It's like having a room 71 00:03:30,690 --> 00:03:32,970 where only one person is allowed in at a time, 72 00:03:32,970 --> 00:03:34,770 you have to wait for the other person to leave 73 00:03:34,770 --> 00:03:36,510 before you can enter. 74 00:03:36,510 --> 00:03:38,100 So we'll discuss thread-safety 75 00:03:38,100 --> 00:03:41,340 in mutexes later in the course. 76 00:03:41,340 --> 00:03:43,950 What about this? This is one, 77 00:03:43,950 --> 00:03:45,450 bit of a hack, really, 78 00:03:45,450 --> 00:03:49,530 but sometimes, you know, it can be useful. 79 00:03:49,530 --> 00:03:52,560 Rust has the concept of unsafe code, 80 00:03:52,560 --> 00:03:55,080 you can mark a function as unsafe 81 00:03:55,080 --> 00:03:59,520 and Rust will allow you to break its protection rules. 82 00:03:59,520 --> 00:04:02,010 If you have an unsafe function, 83 00:04:02,010 --> 00:04:06,570 then thread-safety is not enforced by Rust any longer, 84 00:04:06,570 --> 00:04:10,470 it allows you to get away with it, really. 85 00:04:10,470 --> 00:04:13,020 So this would be a little bit of a cheat 86 00:04:13,020 --> 00:04:13,890 but you can do it. 87 00:04:13,890 --> 00:04:15,543 So this unsafe code here, 88 00:04:16,560 --> 00:04:18,660 because it's unsafe, that's a key word, 89 00:04:18,660 --> 00:04:20,400 Rust will allow me to do things 90 00:04:20,400 --> 00:04:22,410 which would normally be unsafe. 91 00:04:22,410 --> 00:04:24,720 It assumes that I know what I'm doing, 92 00:04:24,720 --> 00:04:25,920 could be a risky strategy 93 00:04:25,920 --> 00:04:28,773 but Rust assumes that I know what I'm doing. 94 00:04:29,940 --> 00:04:32,070 The thing is, if you have an unsafe function 95 00:04:32,070 --> 00:04:35,340 you can only call that from another unsafe function, 96 00:04:35,340 --> 00:04:37,980 so somewhere in your application 97 00:04:37,980 --> 00:04:41,520 you've gotta take thread-safety into account. 98 00:04:41,520 --> 00:04:44,160 So I've got some higher level function here 99 00:04:44,160 --> 00:04:46,650 which calls some unsafe function, 100 00:04:46,650 --> 00:04:49,680 this block also has to be marked as unsafe. 101 00:04:49,680 --> 00:04:52,350 You can have an unsafe function 102 00:04:52,350 --> 00:04:55,290 where the whole function is kind of up to you, 103 00:04:55,290 --> 00:04:57,420 and you can also have an unsafe block. 104 00:04:57,420 --> 00:05:01,350 An unsafe block can call an unsafe function, 105 00:05:01,350 --> 00:05:02,550 like I've done here. 106 00:05:02,550 --> 00:05:04,980 So this would be the the easiest way 107 00:05:04,980 --> 00:05:07,380 to be able to update a static, 108 00:05:07,380 --> 00:05:09,540 to have it mutable and to have it updated. 109 00:05:09,540 --> 00:05:11,940 Declaring it as mutable wasn't the problem, 110 00:05:11,940 --> 00:05:14,730 it's when you then try to update it that it's not safe. 111 00:05:14,730 --> 00:05:16,627 So you have to explicitly say to the compiler, 112 00:05:16,627 --> 00:05:18,900 "Let me do it, I know what I'm doing." 113 00:05:18,900 --> 00:05:20,640 And then when you call that function, 114 00:05:20,640 --> 00:05:24,300 the call itself must be enclosed in an unsafe wrapper. 115 00:05:24,300 --> 00:05:26,430 This is not the recommended approach, 116 00:05:26,430 --> 00:05:28,740 it's the easiest way for now. 117 00:05:28,740 --> 00:05:32,133 Thread safety done properly, we'll discuss later. 118 00:05:33,480 --> 00:05:36,060 There is another approach, depending on what you are doing. 119 00:05:36,060 --> 00:05:37,260 If all you want to do 120 00:05:37,260 --> 00:05:41,820 is to just kind of increment an integer safely, 121 00:05:41,820 --> 00:05:44,940 Rust has a bunch of atomic data types. 122 00:05:44,940 --> 00:05:47,640 Atomic data types perform operations 123 00:05:47,640 --> 00:05:49,320 in a thread-safe manner, 124 00:05:49,320 --> 00:05:54,047 so for the primitive types like I8 and U8, I16, I32, bool, 125 00:05:55,980 --> 00:05:58,500 there are thread-safe wrapper classes 126 00:05:58,500 --> 00:06:01,020 which, when you try to change the value 127 00:06:01,020 --> 00:06:02,520 of an atomic integer, 128 00:06:02,520 --> 00:06:05,190 it automatically wraps it in the mutex, 129 00:06:05,190 --> 00:06:07,590 effectively, to give you thread-safety. 130 00:06:07,590 --> 00:06:10,080 So we have classes or structures, I should say, 131 00:06:10,080 --> 00:06:14,250 such as AtomicI8, a thread-safe wrapper 132 00:06:14,250 --> 00:06:16,350 around an 8-bit integer. 133 00:06:16,350 --> 00:06:20,850 So AtomicI8 has methods that are thread-safe 134 00:06:20,850 --> 00:06:23,730 which you can use to increment the value, 135 00:06:23,730 --> 00:06:25,140 decrement the value, 136 00:06:25,140 --> 00:06:27,420 and perform lots of other operations as well 137 00:06:27,420 --> 00:06:31,140 on 8-bit integers or 8-bit unsigned integers, 138 00:06:31,140 --> 00:06:33,990 or Booleans, and so on. 139 00:06:33,990 --> 00:06:37,200 So atomic data types give you the ability 140 00:06:37,200 --> 00:06:39,960 to modify effectively a primitive, 141 00:06:39,960 --> 00:06:41,520 but in a type safe fashion. 142 00:06:41,520 --> 00:06:44,580 Atomically in other words, so you can perform the operation 143 00:06:44,580 --> 00:06:46,863 without being interrupted by another thread. 144 00:06:47,700 --> 00:06:50,460 So atomic types lock the value implicitly. 145 00:06:50,460 --> 00:06:52,623 When you create an AtomicI8, 146 00:06:53,503 --> 00:06:57,570 it effectively wraps with the mutex, the underlying integer. 147 00:06:57,570 --> 00:07:01,380 Whenever you try to do anything to the atomic value, 148 00:07:01,380 --> 00:07:03,750 it ensures that the value is locked 149 00:07:03,750 --> 00:07:06,150 to give you exclusive access. 150 00:07:06,150 --> 00:07:08,610 So thread-safety out-of-the-box, 151 00:07:08,610 --> 00:07:11,400 it means you don't have to write any mutex code yourself, 152 00:07:11,400 --> 00:07:14,733 just using these data types are implicitly thread-safe. 153 00:07:15,600 --> 00:07:16,890 So let's see an example 154 00:07:16,890 --> 00:07:19,203 of how these atomic types might work. 155 00:07:20,130 --> 00:07:23,520 So here's an example that's gonna use atomics 156 00:07:23,520 --> 00:07:25,863 in conjunction with static variables. 157 00:07:26,910 --> 00:07:29,010 Quite a few things I need to explain here. 158 00:07:29,926 --> 00:07:34,200 AtomicI32 and all the other atomic data types 159 00:07:34,200 --> 00:07:38,400 are located in the atomic module, 160 00:07:38,400 --> 00:07:40,890 which is in the sync parent module 161 00:07:40,890 --> 00:07:43,410 which is in the standard crate, okay? 162 00:07:43,410 --> 00:07:46,740 And we also have another type called ordering, 163 00:07:46,740 --> 00:07:49,320 which we'll see coming into play a bit here. 164 00:07:49,320 --> 00:07:51,180 That turns out to be a bit tricky 165 00:07:51,180 --> 00:07:53,610 so let me come onto that in a moment. 166 00:07:53,610 --> 00:07:56,640 This function here isn't unsafe, okay, 167 00:07:56,640 --> 00:07:58,683 I did not declare it as unsafe. 168 00:07:59,520 --> 00:08:01,653 I've got an atomic integer, 169 00:08:02,790 --> 00:08:05,490 the atomic integer itself doesn't change 170 00:08:05,490 --> 00:08:07,350 but the value inside it will. 171 00:08:07,350 --> 00:08:11,970 So I didn't declare the atomic integer, local count, 172 00:08:11,970 --> 00:08:14,550 I didn't declare the variable as mutable 173 00:08:14,550 --> 00:08:16,980 but the value that it contains inside it, 174 00:08:16,980 --> 00:08:18,900 you can think of an atomic integer 175 00:08:18,900 --> 00:08:23,580 as being like a wrapper over an I32, okay? 176 00:08:23,580 --> 00:08:25,530 And the object itself, 177 00:08:25,530 --> 00:08:28,770 the atomic object itself isn't mutable 178 00:08:28,770 --> 00:08:32,667 but the value inside it is, okay, 179 00:08:32,667 --> 00:08:36,840 and the atomic type I32, AtomicI32, 180 00:08:36,840 --> 00:08:39,270 has various different methods you can call 181 00:08:39,270 --> 00:08:43,290 to change the value inside the wrapper. 182 00:08:43,290 --> 00:08:46,530 Okay, so it's like a type safe, a thread-safe wrapper 183 00:08:46,530 --> 00:08:48,420 over a 32-bit integer. 184 00:08:48,420 --> 00:08:51,333 So I give it the value zero, initially, 185 00:08:52,170 --> 00:08:55,080 and then upon this atomic integer 186 00:08:55,080 --> 00:08:58,590 there are various methods you can call basically fetch_add. 187 00:08:58,590 --> 00:09:02,070 It fetches the value, which is currently zero, 188 00:09:02,070 --> 00:09:04,110 and it adds one to it, 189 00:09:04,110 --> 00:09:06,450 and then it does it in a relaxed order. 190 00:09:06,450 --> 00:09:07,680 I'll like say a little bit more 191 00:09:07,680 --> 00:09:10,623 about this ordering parameter in a minute. 192 00:09:11,760 --> 00:09:13,890 When you have thread-safety 193 00:09:13,890 --> 00:09:17,460 an atomic operations turns out to be quite complicated 194 00:09:17,460 --> 00:09:19,770 because there are lots of optimizations 195 00:09:19,770 --> 00:09:22,710 that the compiler could do, or the hardware could do, 196 00:09:22,710 --> 00:09:27,030 kind of reordering statements inside your code. 197 00:09:27,030 --> 00:09:29,790 So this flag here, 198 00:09:29,790 --> 00:09:32,100 it specifies what type of reordering 199 00:09:32,100 --> 00:09:34,140 you've allowed to happen. 200 00:09:34,140 --> 00:09:35,730 So let's just ignore that for now, 201 00:09:35,730 --> 00:09:38,310 and I'll say a few more words about it in a minute. 202 00:09:38,310 --> 00:09:40,770 The net effect is that I had a local counter 203 00:09:40,770 --> 00:09:43,290 that was zero, initially, 204 00:09:43,290 --> 00:09:44,760 and here I basically said, 205 00:09:44,760 --> 00:09:48,660 it would be like saying I + = 1 206 00:09:48,660 --> 00:09:50,760 but in a thread-safe fashion, 207 00:09:50,760 --> 00:09:53,190 and then I output the value of local count. 208 00:09:53,190 --> 00:09:56,400 Atomics support debug formatting, 209 00:09:56,400 --> 00:09:57,250 so you can say :? 210 00:09:58,200 --> 00:10:01,833 and it'll output the value of the atomic integer. 211 00:10:02,850 --> 00:10:06,570 So let's take a look at the documentation. 212 00:10:06,570 --> 00:10:10,320 If you go to the documentation, it'll give you information 213 00:10:10,320 --> 00:10:14,250 about all of these standard synchronized atomic types 214 00:10:14,250 --> 00:10:17,520 from the standard sync atomic module. 215 00:10:17,520 --> 00:10:20,010 Let's have a look at the documentation. 216 00:10:20,010 --> 00:10:23,340 Okay, so here's the documentation for the atomic module 217 00:10:23,340 --> 00:10:27,270 and the sync module, and the stud crate, 218 00:10:27,270 --> 00:10:30,340 and there's some kind of general explanation here 219 00:10:31,320 --> 00:10:32,190 which you can read. 220 00:10:32,190 --> 00:10:35,220 I wouldn't recommend that you don't have to read this, 221 00:10:35,220 --> 00:10:37,410 you know, it's quite dry, really quite technical. 222 00:10:37,410 --> 00:10:40,200 Let's have a look at the structures, though. 223 00:10:40,200 --> 00:10:41,790 These are the atomic structures, 224 00:10:41,790 --> 00:10:46,790 wrappers around Boolean I8, I16, I32, I64, Isize, 225 00:10:49,107 --> 00:10:52,950 Ptr we'll talk about later, unsigned equivalents like that. 226 00:10:52,950 --> 00:10:55,050 Let's have a look at AtomicI32, 227 00:10:55,050 --> 00:10:56,873 that's what we were using, wasn't it. 228 00:10:58,230 --> 00:11:00,450 So an integer type 229 00:11:00,450 --> 00:11:02,700 which can be safely shared between threads, 230 00:11:02,700 --> 00:11:06,570 a thread-safe 32-bit integer. 231 00:11:06,570 --> 00:11:11,127 These are all the methods you can call upon an AtomicI32, 232 00:11:12,720 --> 00:11:16,950 okay, so you can fetch the value from memory 233 00:11:16,950 --> 00:11:19,740 and add a value to it, that's the function we looked at. 234 00:11:19,740 --> 00:11:23,700 You can subtract, you can do kind of Boolean logic, 235 00:11:23,700 --> 00:11:27,120 or and and, and not, 236 00:11:27,120 --> 00:11:32,120 so let's have a look at the fetch and add method. 237 00:11:35,220 --> 00:11:37,560 Let's ignore the self parameter just for now. 238 00:11:37,560 --> 00:11:40,080 It takes the value that you want to add, 239 00:11:40,080 --> 00:11:44,250 in our example, we added one. 240 00:11:44,250 --> 00:11:46,650 The second parameter is an ordering. 241 00:11:46,650 --> 00:11:50,610 Ordering is quite tricky, 242 00:11:50,610 --> 00:11:53,951 let's have a look at the documentation for ordering. 243 00:11:53,951 --> 00:11:55,230 (mouse clicks) 244 00:11:55,230 --> 00:11:56,613 Ordering is an enum. 245 00:11:57,480 --> 00:11:59,010 I dunno if you were gonna guess that. 246 00:11:59,010 --> 00:12:02,760 You can specify how are you going to allow the compiler 247 00:12:02,760 --> 00:12:04,650 and the hardware to order 248 00:12:04,650 --> 00:12:07,050 the kind of statements that need to be executed. 249 00:12:08,220 --> 00:12:10,710 All right, now I'm not gonna get into the theory of this 250 00:12:10,710 --> 00:12:12,570 because it's kind of out of scope. 251 00:12:12,570 --> 00:12:15,330 What I will say is that the way that Rust 252 00:12:15,330 --> 00:12:19,260 manages memory ordering is basically the same as in C++20. 253 00:12:19,260 --> 00:12:22,290 I'm not sure if that helps, but it's the same model. 254 00:12:22,290 --> 00:12:24,720 And if you want to kind of get a bit more insight 255 00:12:24,720 --> 00:12:28,260 into what this actually means, there's a link here. 256 00:12:28,260 --> 00:12:33,260 You can go to the nomicon, the Rustonomicon, 257 00:12:33,600 --> 00:12:34,860 easy for you to say. 258 00:12:34,860 --> 00:12:38,190 And this is quite detailed information, really, 259 00:12:38,190 --> 00:12:40,710 about how Atomics work in Rust 260 00:12:40,710 --> 00:12:42,090 and how the compiler is allowed 261 00:12:42,090 --> 00:12:45,330 to reorder statements to optimize, 262 00:12:45,330 --> 00:12:46,590 and how the hardware 263 00:12:46,590 --> 00:12:49,920 is allowed to reorder statements, as well, to optimize. 264 00:12:49,920 --> 00:12:52,710 And it's quite a good job they've actually done 265 00:12:52,710 --> 00:12:54,150 of explaining this. 266 00:12:54,150 --> 00:12:56,400 At the end it talks about the difference 267 00:12:56,400 --> 00:12:58,890 between all the different types of reordering allowed, 268 00:12:58,890 --> 00:13:03,890 sequentially consistent, release acquire, and then relaxed. 269 00:13:04,920 --> 00:13:08,010 All right, so if you really want to get into this in detail, 270 00:13:08,010 --> 00:13:10,080 this is the place to go. 271 00:13:10,080 --> 00:13:12,030 In most cases, you can just get away 272 00:13:12,030 --> 00:13:15,180 with the relaxed ordering, and that would be fine. 273 00:13:15,180 --> 00:13:18,360 So then we have a static, 274 00:13:18,360 --> 00:13:20,970 which is, although not declared mutable here, 275 00:13:20,970 --> 00:13:24,120 the value inside the atomic can be mutated. 276 00:13:24,120 --> 00:13:27,900 So I can use the atomic API to fetch the value, 277 00:13:27,900 --> 00:13:30,720 add one to it, and then store the value back again, 278 00:13:30,720 --> 00:13:32,643 and then output the value like so.