 All right. Good afternoon. The teaching station is broken yet again So I can't read discord so you'll have to yell at me if anyone comments on it. So yeah, that's great All right, so again, this is not new content. This is just fun stuff That's kind of related to the course So I will not test you on rust or anything like that, but we're going to See a conversion with our bank account. I'm not that good at rust so I can't do the conversion on the fly But we can screw around with it. It's really easy to screw around with and Who here has like used rust before or even heard of it? We got like a few few people. Okay So we'll quickly go into what is this lecture might be a complete crap show because I'm not great at it So if you want to feel better about yourself for not knowing something then you can watch me flounder here But we'll make tons of errors and see what can happen But basically rust is supposed to be billed as a new systems programming language So it's actually kind of integrated into the Linux kernel now So new drivers you can write them in rust and it's meant to just be a saner See that has some kind of more C++ like features But isn't as quite insane as C++ with all its stupid Mac or templating and all that kind of stuff So it also has a few nice goals over C++ where it aims to statically verify some properties of your program So that if you know it compiles and sometimes you will fight with it to get it to compile But if you know it compiles and you don't Try and get around its rules Then you're guaranteed some nice properties like you won't have a use after free So you can't use memory after you free it which would have saved some of you on lab six that have fixed your own errors Probably was saved you on all the other labs while you're developing stuff you can't double free something so I can't call free on the same pointer twice and statically it verifies that you do not have any data races Which is probably more applicable for you guys because concurrent programming is kind of hard and There's a unsafe keyword So it won't let you do a lot of things especially it doesn't really like you casting pointers and doing all that fun stuff You do and see because it's kind of unsafe So if you want to tell the compiler don't worry. I know what I'm doing There's a keyword called unsafe you can use it for like a function or block a code that says yeah Don't bother me. I know what I'm doing and the idea behind that is when you inevitably crash your program or something bad Happens well because the rest of it's verified to be safe. You can just debug looking at your unsafe code But oh, I don't know why there's an at sign there I accidentally screwed that up, but it only verifies that data races or yeah data races don't happen But it does not do anything to prevent deadlocks and trying to get this example not to deadlock Was a complete pain in the butt so we will see that today So it doesn't completely eliminate some of your problems, but it eliminates a large section of them So the way it eliminates data races is one of the things that kind of does is keep track of the lifetime of all your variables and how long they last for and However, many times you take references to them So it also has a keyword called mutable Which you have to declare a variable as mutable if you want to change it Which in other words for us means you want to write to it So one of the properties it will do is if you have references to something You are allowed to have as many read-only references as you want because that could not be a data race But if you have a mutable reference to something you can only have one So if you can only have one we know that even if you have concurrency That's fine because that is not a data race only one we need to concurrent accesses So let's do this and see how this works. So it should be related to what we did So the syntax is somewhat nicer and kind of weird So the main doesn't take a any arguments and it doesn't return an integer it returns a result and This is a kind of Unbelievably nicer way of doing, you know with the standard C functions They return negative one and then set error no and then you have to check it Well a result can be one of two types So it's a result and then two types So if it's successful This is the type it is and if there's an error or something it tells you what the type is of the error And then you can go ahead and handle it and check it without checking if it's negative one or anything So it's kind of a nice way about this Box is like a safe way for a pointer. Don't have to worry about that. It's basically just to check an argument So this will go ahead take all the this is like the same thing where you use the NV pointer but it's a bit different in Rust and we change everything into arguments and just check remember we just take one command line argument that says however many accounts you want to have for our bank simulation if we remember a bank simulation and Then it has some nice things like you can parse a number straight with that and These you'll see in a bunch of Rust code. There's a bunch of question marks So pretty much every function we know that can fail like if you're parsing an integer from a string There's tons of ways for that to fail, right? So in C if it fails What happens and see if you use like was it a to I or something like that? What happens if you give it not a number? Anyone have you used that before hopefully? So if you just give it the string like hello and you say convert that to a number, what will it do? I don't know. I'll probably give you like zero or set error. No or do something But likely in all likelihood you probably never check the error before so Rust forces you to check an error So this parse method. I don't have a good IDE setup for this But basically returns that same result thing where if it successfully Transforms it it gives you the number that it converted to otherwise it gives you an error and This question mark thing is kind of a shortcut to say Okay, if it successfully returned just give me the proper return value And if it's an error, it'll just abort your program and give you a stack trace and say hey It's screwed up at this point so it kind of does what I've tried to enforce on you guys Like check your errors and just fail out so you actually know what's going on And you don't kind of propagate that error and make it harder to debug and then we'll just check the number of accounts So oh god, so it gets really ugly So let's skip this for now. Oh Whoops Sorry, let me go to the version that does not use threads So everyone kind of remembers our bank example right where we have a bunch of accounts and With a bunch of accounts. We just pick a random to one we pick a random from one and then we transfer money between them So anyone want me to revisit that or does everyone kind of remember the transfer function and what it does Because we kind of had a question like that before or yesterday Monday Monday Okay, so It took a while to even get this so that the compiler would not complain at me. So this is just getting two accounts from an array and You want mutable reference to them because you want to change the balance because you're trying to Transfer from one account to another account But even getting that to work was a gigantic pain in the butt and I'll kind of illustrate that here Let me comment this out first So So if you want to define like vector or just like a you can think of a vector is like a dynamic array This is what you would do. So you would say let is how you declare variable So if you want to change the variable after the equal sign, you have to say it's mutable Which is that mute keyword? So it's like let mute Test vector and then you can say vector with capacity. So it's kind of like C++ namespaces So this is just declaring a vector of size 10 and then the for loops also look a bit different They don't let you just do I my as my as and screw up the condition or whatever So they'll have a little shortcut. That's just one dot dot whatever the other number is so this for loop will iterate from one two three four five six all the way to ten and Put that value into I so I just push ten things onto my vector So now my vector has ten things in it one two three four five six seven eight nine then if we were in C and Want to like pass a pointer to another function? So that it could change the value of it, which we've seen a few times you would have to do like You know the address of in C to get the address of well It's the same thing in rust except if I want to change whatever it's pointing to I have to say Ex or ampersand mute to say hey, it's mutable. I'm going to change it So if I do this, I'll say I'll have a reference or essentially a pointer to the zeroth element of the list and a pointer to the first element of the list and then I'm going to modify the zeroth element of the list to two so in C That would compile right everything would be good, and it doesn't have any problems with it But in rust if I go ahead and try and build it so it has its own build system so it will loudly loudly complain at me and Loudly complain at me so one It's complaining that I don't use the variable element one. Okay true, but don't worry about it And then it says oh if this was intentional It gives you a way to silence the warning by putting a prefix in front of it or silencing it So it has a lot of ways to kind of communicate with the compiler which is there's ways to do this in C But people generally don't do it It's also warning me that this variable does not need to be mutable because it's a Actually a mutable so this is a pointer to something I can mutate So I'm not actually changing what I'm pointing to so this mute keyword I didn't need to actually use it so that's it It tries to limit the amount of mutables you use so that you have like the least amount of chance of Writing anything and breaking anything so we could go ahead and fix that where essentially it's just a pointer and The nice thing about this too is those are all variables But you might notice too that we don't declare the types for them at all so another nice thing about rest is it will Infer the types for anything so if you go on to take like a compiler course or like a grad level type system course You will figure out how it does things like that. It is fairly complicated But for us we just get to have the nice benefit of using it So let's go to our next error Variable does not need to be mutable and then this is our error So it says cannot Borrow test vector as mutable more than once at a time So does anyone have an idea what in the blue? Heck it's talking about Yeah, is that what you're gonna say too, but so I have two references to the array, but Technically, this is actually okay, right because I have two references to two different spots in the array that do not overlap So the pointers do not overlap, but they are references within that array. So this is where the thing So if I did something like this, it would be right where they're both pointers to the same index That would definitely be an error where I have two mutable References or you can think of pointers to the same Same element, but in this case this should be okay, and If you're used to see this wouldn't pose any problems no issues whatsoever But the annoying thing is this just won't compile because it it it airs on the side of being conservative So if you just have one reference to the array In general it would need to prove that both the elements are disjoint and in general it can't do that Oh, yeah, was there a question? So that the with what I have now Not no Yeah, so pointer arithmetic unsafe, so it won't let you do that So you have to like force it to do unsafe and then if you use unsafe all bets are off So like pointer arithmetic and stuff it doesn't let you do it strictly discourages you doing that so this is technically safe, but the compiler is If you get to compiler courses, it's called it's trying to be really conservative because in general it would have to prove that those two array elements are disjoint and In general, that's a very very hard thing to do even though the example given right now You just have to prove that zero is not equal to one That's pretty simple, but then they'd have to special case a bunch of stuff Then it would work sometime and it would probably just make your life more of a pain because The general case is that these could just be two unknown variables and they come from who knows and then you have to prove That they don't equal each other which yeah in general is not going to happen so this is kind of one of the first pain points of this is At least for you guys since you know see hopefully you guys are technically smarter than the compiler for some things and The compiler will get in your way, but I'm told eventually you kind of get used to it and it becomes all good so That was like the first issue because When we're even doing the serial version of this in rust our transfer takes here Let's go to their transfer and this is the fixed one So our transfer takes two accounts, but in rust I did I couldn't just take to like if I had Defined transfer is from taking like a mutable reference to an account and then to has a Mutable reference whoops mutable reference to an account Well, the compiler stopped me right there because that's technically the same array and then it would say hey You know you can't have two mutable references to the same array So it won't let you do that so you might notice some other crap on here one thing you recognize so everyone should recognize mutex and The other thing is an arc Anyone have any idea what the hell an arc is? So who how many of you actually like you see plus plus for things? one Okay, so Basically arcs are Pointers and it keeps track of like the number of pointers to a thing But it keeps track of how many pointers there are to it So it's kind of like our i-nodes how it keeps tracks of how many things point to it an arc does essentially the same thing but for pointers and a Pointer if you want to be like nice and Java generally if a pointer actually points to something valid You called a reference and not a pointer so An arc is basically just a reference a Pointer that keeps track of how many things point to it and the a stands for atomic so it's an atomic pointer So when it adjusts the number of things that point to something it does it atomically So it's nice and thread safe and everything So that way we can have multiple pointers to our array and keep track of it that way and you have to like wrap Types in rust it's kind of annoying, but once you figure it out. It's not too bad So Let's go here. So, yeah, this is kind of what our So this is what our transfer function looks like so It fixes a bunch of issues that we had before where we had ran that was really really slow because it shared state But here if you have an art a random number generator or RNG Well, you have to declare it as kind of a variable which is kind of like an object and Then it has its own state So it's nice and thread safe and then this is where we go through the number of transfers so we Come up with a random index to the further from account random index from the to account and Then this is how we create multiple pointers to it So instead this is what you would normally do to access an element of array, but because We're not allowed to take multiple references to an array these are actually pointers and Dot clone on the pointer just makes another copy of the pointer so it's your own pointer and it increments that number that count that's actually referring to it so you can actually keep track of the number of references to something and that's again how to make things safe and That you don't have to call free on it So this keeps track of how many things point to something. It's the same thing as I know So as soon as you have no references to that memory Whenever it goes to zero then it would free the memory for you. You don't have to worry about it. No Yeah, there's no extra onus on you to know when to free it just frees whenever there's no references So this will take two pointers count them give them to transfer oops and In transfer, this is our thread safe one But it basically just does the same thing where we just take 10% from the from account take it from the to account and that's it So any questions about that? So, yeah, it's sorry. It's a bit of a mess because I had to put in the solution because Yeah, I can't remember how to do this again like Look at the type on this thing. It's gross Some of it might be because I have no idea what the hell I'm doing, but hey it works Okay, so let's see the thread safe version Oops, not that one So for the thread safe version or the one with threads Well, you can create an array of threads in a vector Which is just like a dynamically sized array and then we would take our a Pointer to the whole array. So that's this is what this represents. So you give each thread a pointer to the array and then this is instead of p thread run It's called thread spawn and there's this move keyword. So any arguments you give They you can't use them after you give them away. So if I tried to use accounts clone here This would give you an error Because it would say hey you so one of the other things it does it has a Concept called ownership. So it will keep track of who owns what variable so that you're makes And ensures that you only have one owner for each variable So like if it was some dynamically allocated memory, you're the only owner of it So if you make it to the end you can call free on it and no one else uses it so You're fine. You're free to free it There'll be no free after use or anything like that All right any questions about that so We basically have to give a pointer to the whole accounts array for each individual thread That's all counted and the reason for that is because rusts or you in the general case don't know how long a thread lasts and Don't know how long the main function lasts and don't know, you know If the main lasts longer than the threads or the threads last longer than main in general So it makes sure that you transfer it there so that you can always use that vector array because it won't actually free the memory associated with that array Until there's no more references to it. So you're always safe to use it You'll never have a use after free because you never even call free. It only calls free itself whenever there's no references to it but it's also a Bit silly in this case because we know we have a bunch of threads and we join them so this is the way to join them and Unwrap is just to check for errors again and just get the value out of it. Otherwise. It just kind of dies so we know that our Bear or our array actually lives longer than all the threads But unfortunately rest is not quite as smart as us, but we'll live with it All right any questions for this Okay, so let's go in here. So This was more of a pain to get right so for all the locks so By default you are not allowed to try and modify anything or even Read multiple accounts at the same time because it can't argue that that's a safe thing to do So you're only allowed to actually change values if they're protected by a mutex and the way in rust to use a mutex is It's kind of like a type wall fire around an account So if I want to modify the account or get at any data associated with the account I have to go through the mutex first So the only way it will make sure that the only way I can even try and modify the account is if I already Have a lock and acquire lock so this so in rust The from is actually a Lock the arc is just a pointer. It'll get rid of the pointer for you. So you can think of the From account as just being a mutex around an account. So Instead of doing p thread mutex lock or whatever you do a lock on the mutex and then unwrap would check for errors so check, you know Check error node do all that stuff and then just crash the program if something bad happens And then after that our lock is acquired and we can actually modify the from account so If we do something, let's let's do my initial solution. So So if we do this and we whoops wrong build system All right. So if we do this and we run it, do we have any data races? So let's Move up some code. So it acquires both the locks and then here it modifies the accounts So do I have any data races here? hopefully not because I have a lock like right after this I Have acquired the lock from I've acquired the lock to two and it will a nice thing about two is Rust will automatically unlock them when the function is done. So you don't have to worry about calling unlock So at this point, I have both the locks and they'll both be unlocked whenever it reaches the end of the function So I don't have any data races. What problem do I have though? Dead locks. So rust doesn't prevent dead locks. So let's go ahead and see If I try and do run So if I try and run it This might be the first thing you promised about a lot about rust But if I try and run it well, I'm screwed because I have a dead lock And there's nothing I can do about it. So Does anyone remember the two easy ways to avoid doing a dead lock? Yeah, okay. Yeah first one is hey if I always maintain the same order. I won't have a dead lock Doing that in rust is as far as I can tell impossible so Because Here it depends what order to so the account does have an ID. That's unique But there's kind of a catch 22 here if I even want to read the ID Because it's declared like this Even if I want if I want to access anything about the account even to read it I have to acquire the mutex and I've already deadlocked myself at that point, right? I can't do anything about it Or I can't like change the lines or do anything or if you can Actually check the pointers and do it based off the pointers But you can only acquire the locks in the right order and then you forget which accounts associated with what lock and The only way I could think to actually fix that is actually writing two transfer functions depending on the order of them, but that seemed super lame and kind of a buzzkill so Anyone remember the second solution? Yeah While having both locks Yeah, let's assume I want both locks so at the same time So I could do the same thing that's in the exam where I just kind of So unlike the exam this gets a bit different because it takes a percentage in of account So if you like have a partial transfer it gets kind of weird that you probably don't expect that So what if I want to maintain two locks? So one was always acquire them in the same order. What was the other deadlock or another deadlock condition? Yeah Yeah, try lock because one of the conditions is hold and wait, right? One thread has a lock while it's trying to get another one So if I can break that condition then I don't have the possibility of a deadlock. So that's what Essentially I came up with to do and it works the same in rust but kind of looks a bit different so you can it has like weird things where the The thing at the equal sign can be an expression So you can say a variable is equal to a loop as long as it eventually gets to a variable Rust is okay with that and the scope is only within the scope of that which is kind of cool So in the loop I do a try lock to the two So at this point I would have already acquired from So I do a try lock and then it's that result type So you can use a let expression. It does pattern matching. So a Proper result is called okay So if it's okay, that means I acquired the lock and then I just have a break that says return that variable assign Assign to account to to account. So it's actually represents the locked mutex Otherwise, there's drop from account which if we wrote this and see this would be mutex unlock But drop is the rust keyword for kind of any variable that as soon as it goes out of scope Drops automatically called on it So if this was like a malloc thing drop would call free in the case of mutex's drop calls unlock So it's just kind of a more generic term that represents a whole bunch of stuff So this drop from account is the same as unlock from account And then I have a yield and then I would try and acquire the From lock again, which is what we had before And then we go ahead and we just try it over and over again. So if I have this hey It should hopefully prevent a deadlock And yay, it works so That's rust. It's kind of nice. I thought you guys should see it Even though it might not have made that much sense, but it probably needs to It's probably a good thing to know if you start writing concurrent code It will save your it will in some cases make your life a lot easier All right, so any questions about that? If not, let's talk about the exam. Yay Okay, so we can start doing more review I'll take up more questions and then The plan for Thursday is to finish whatever we didn't do today or please ask other questions. So I have something to do otherwise It defaults to open office hours and I'll just stand there and wait for questions Whoops dog okay so This question was says hey given four threads you want to Four threads that are properly created set up to run You want to ensure some ordering between them? So ordering should immediately trigger you to think of semaphores So that's a nice way to ensure order. You could also use condition variables, but If generally if you can semaphores are the easier thing to do And I guess there is a question. I I mean I gave you the front page to the exam There's a question called semaphores. So Probably shouldn't be a question what you actually have to use So this is This was a closed book exam. So I had to tell them what post and weight did But for you guys you have an open book or at least a cheat sheet. So I won't tell you So you should know how semaphores work, but pretty easy. They have an increment decrement Decrement will make sure it won't go negative if it's zero just waits until it gets incremented by something else And then it would eventually decrement it So we have four threads they each call this each call their own thread function And there's some comments between all the functions and you want to ensure the order between them So thread one runs f1 then f2 and then in thread two it only runs f3 And we want to ensure an order that hey F1 only runs after f or f3 only runs after f1 completes f4 only runs after f1 completes And then in thread four we have f5 that should only run after f2 completes And then f6 that should only run after f3 and f4 complete so Let's work on this so First constraint is probably This one we want to work on So anyone have any ideas how I would make this work how I would make sure that f3 only runs after f1 is done So thread two you want So we want to wait So we'll start off with the weight. We'll just use Sema one or something. That's the first center four So we'll set it off to wait first. So everyone agree with that everyone like that Okay, so next thing you need to think of a center four probably is what should the initial value of it be So what should the initial value of that seven four one b? Seven everyone likes seven Yeah thumbs up all right So what will seven do? Yeah Zero no yeah people like zero better Okay, we like zero better. So whoa Geez No one saw that. All right. Let's just use that zero then. Yeah So we can probably use zero So zero makes it so that Hey if Because we don't know the order between any of the threads executing It'll make sure that if thread two executed before thread one for whatever reason that it would hit the weight If the initial value was anything other than zero it would just pass it immediately So if it was seven it would just go from seven to six And then it would call f three and it just called f three before f one happened. So that would be bad So this looks good so far, but we have Still another issue. So we'll in this case will it ever call f three? No, right. So nothing posts. So we should probably work on the other part of it. So where should we place the post? After f one Okay, so we can post All right, everyone agree with that So that looks like that handles that constraint. So now If thread two runs first it would hit the weight the value of the seven four is zero Can't do anything So it would have to wait until eventually the only way to post it is for thread one to start executing Finish f one and then it would post it it'd go from zero to one then thread two at its leisure It'd go from one to zero then call f three So it can only happen after the other only happen after f one gets called. So everyone Good with that all right What about f four? So let's tackle this one So that's that constraint fixed. What about this constraint? So it's the same thing So, yeah Yeah, so we just do the same thing again. So we'll wait So now if thread three gets called first while the initial value zero Can't do anything has to wait for a post. So if we leave it as is well F thread one will execute sometime execute f one and then increment that from a one to a zero and then Either thread one or thread two would pass those weight and Go from one to zero and then the other one would be stuck there forever. So we still need that sem post Some one so now a whole bunch of different orders that can happen, but that constraint Still works So if thread two tries to execute couldn't do anything thread three tries to execute Can't do anything and it would wait for thread one to go Finish f one maybe it only post once And then another one of thread two or three executes and then it passes by Then eventually it will make it back to thread one and it would go from zero to one again And then the other one could pass by or it could just do the two posts in a row go from zero all the way to two Either way works doesn't matter our constraint still good All right, so we like that so hey we can give her so check check What about f five? So this is only runs after f two completes which is This guy here So how am I going to do that? Okay, so use a different center for new center for All right, everyone like the direction that's heading in What do we forget to do? Yeah, we forgot to initialize it. What should its value probably be? Zero right if it's anything other than zero then thread four could go ahead execute thread five and then we're screwed so This looks good and we used another center for so Would it work if I reuse center for one for that? No, because it needs it actually needs to be a different one because you're not actually sure although technically you could So there is a sillier solution because you guys are being really really good and you know making sure that The other threads can start as soon as possible, but I could actually reduce the number of center for as I use and The alternate solution is I could make this center for one and it could still work Anyone guess what else I would have to change for that to still work while technically still being true post after thread two post after f three like here So that wouldn't quite work Whoa Okay, whatever we leave that so What you could do is you could move the Okay, so I want to raise things so it doesn't screw up But you could move these posts to be after f two as well So that all the other threads can't run until thread one's done and technically all the conditions are still true But this solution is actually better because it allows the threads to run as soon as possible And there's a sub question That says that asks What can run in parallel at the same time? So if you gave that solution You essentially make sure nothing can run at the same time as thread one, which is pretty lame So you guys are doing the smarter thing But that was just an aside. Okay, so this is seven two All right, so we're done with that condition. We just got the last one left So f six Only runs after f three and f four complete Any ideas how I would do that Yep Nope Yeah, center fours. You're not allowed to initialize to negative. They have to be unsigned so it's zero up Yep Yeah, okay, so we could Go ahead post A new center four sim three we could call it Only after Only after once after F four and again after F three Oh, it's already there. Oh So we could post sim three and then here we need two weights. I'll move the ampersand some weight So that way it has to wait for both of them to be done before actually executing f six Right, so if only one's done it would make it past one weight not the other so it needs actually both of them So any questions about that one? cool so Last part was just for each function state what functions could run in parallel With it not including itself right none if it can only run by itself So it's just kind of looking at it looking at the dependencies and seeing what could run in parallel at the same time If you had enough cpu cores, so I can leave that one and we can gloss over the rest of them just To let me know on discord what you actually want me Cover more in depth tomorrow or any other topics you want me to cover since you have the front page So for the last five minutes, let's just go through it quick quick quick So memory allocation was buddy allocator should know what that is that Only allocates in size powers of two and here I set it So it needs to be able to handle eight allocations of 10 bytes each So this is just knowing that hey, if they're 10 bytes if I have a buddy allocator would be 16 bytes So how big does my memory block need to be? Well, it would need to be eight times 16 Which is 128 And then just asked about fragmentation Discs so This was just remember all those rape configurations that essentially just ask So, you know, I have a bunch of hard drives. What's my usable space? How many drives can fail? Just pretty much that lecture in a nutshell file system so this was this Is Making sure that even though you didn't write a cp, you know what ln means So this is like using your knowledge to explain the difference between hard links and copying In terms of inodes and blocks Assume you have a file that's called my file that contains 2000 whatever bytes block size is four kilobytes So it fits on a single block as a hint. I said I ran the commands for you and you find the output below and the hint here was that hey If I do a hard link to my file The inodes are the same because they're hard links. So it refers to the same thing But if I do a cp like a copy and create a file That there's a new inode So if there's a new inode that inode also has its own data blocks So they're completely independent and the copying would be it would create As part of the copy it would create a new inode for the copy file Then go in and copy the contents of all the blocks and make an exact copy of it and make sure that the new inode It just created points to those So that's how files normally work if you copy a file, right? If I copy a file and then I modify the original one It doesn't affect the copy of the file I made nothing changes while if I had a hard link They both actually refer to the same data. So if I change a hard link Through like my file and then look at ln file after the changes are in both of those They're not actually copies of each other. They're actually pointing to the same thing Then Yeah, last one was just virtual machines Just general questions about virtual machines. Are they a good idea? What's their benefit? So this is kind of more like These sub questions are more like your short answer questions. So your short answer question would be like either 8a or 8b. So this is kind of what your short answer questions look like So I there's no big virtual machines question, but you can probably guess I Wow, I talked too much, but yeah, I probably made a question about it. So and it probably looks like one of these But Obviously won't ask you the same thing because that's kind of lame All right, so any other questions concerns or whatever for the last and final lecture? Yep, yeah, so Yeah So you could have copy on right on this And but it would behave like you have copy on right on memory, right? If you have copy on right on memory As far as you're concerned, it's an actual copy like it behaves like an actual copy You were just way more clever about it But Even if this this could use copy on right, right? You just wouldn't know about it Just like virtual memory uses copy on right and you have no idea. It still looks like a copy But if it needs to make You know, it just delays during the copying to the latest possible period All right. Yeah, any other questions concerns or whatever? Probably should have written down what I did because that was probably a dead giveaway God, I really need to just shut up All right. Well on that note pulling for you. We're all in this together