Implementing Rust's Vec From Scratch

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
all right welcome everybody to another stream today is going to be a little bit different than previous weeks because today we are going to do something that's more on the advanced side of rust um this hopefully will be useful for those who are in the kind of intermediate going on to advanced phase of rust but also for those who are more in the beginning side of things as well what we're going to be doing today is not necessarily something that you're going to do in many or most or even you know any of the rust projects that you'll end up writing but because of the nature of what we're doing you have to really have a good understanding of how rust thinks about memory in other words rust's memory model so what are we going to be doing today well let me switch on over to the screen here today we're going to be building our own version of vector um and what we're going to be doing in order to do that is really touching a lot of different things and rust including a lot of unsafe because we're not going to be using you know obviously the easiest way to do this is to just kind of write a new type a wrapper around a standard vector and then we would be done very quickly but we're not going to be using vector we're not going to be using vec in the standard library we're not going to be using box we're not going to be using anything like that we're going to be using kind of the plain allocation api that rust comes with that you normally don't have to use at all um and uh touching a bunch of unsafe types in order to build up a safe vector abstraction we're going to be looking a little bit about um it's probably not going to be nearly as efficient as the standard library vector there's a lot of different things that have gone into vec over the years in order to make it super efficient and also compile fast and be efficient also in disk space in addition to runtime performance we're going to be doing this roughly as as easily as you can and doing some tests to make sure that our vector implementation actually works properly all right um let me know if you have any questions as we go through this um we will be taking this slow but um again this is uh definitely an advanced topic so there are some things um that uh you know probably will not make sense to to some uh in in chat today watching today and in the future on youtube also uh i almost guarantee that we'll end up making some sort of mistake today that uh and exposing some kind of undefined behavior that does that our vector should work um for at least the test cases that we're using and there shouldn't be hopefully anything obviously wrong with it but unsafe can at times be be quite difficult to get exactly right um and um i'm you know especially when people are watching me i don't doubt that i'm gonna end up making some mistake uh so keep me honest chat today we'll we'll try and get through this um also looking over besides the documentation for vec which will come in handy for us to know how we can kind of uh build up the same api that uh that vec has i also have open here um the implementation for back in the standard library and if we have time or interest we can go through and look at that and along with that is also the real bones of a vec which is raw vec vec itself is really just a small wrapper around raw vec which does kind of this is not a public api this is just an implementation detail but raw vec uh is the the bones of vec and also um decvec as well um so we will probably end up taking a look at that um as well all right i've been blabbering too much let's go ahead and um get started real quick um so the the first thing uh to do and let me go ahead and make this a bit bigger here uh for everybody um the first thing to do is start a a project so we're just going to go cargo new and then i'm going to call it my back for now and i'm going to make it a lib and then i'm just going to open this up in nvs code for now all right let's bring this down a little bit and let's see if i can make this a tiny bit bigger just so everybody can can see the screen properly and let me know if i should make that larger or anything like that we can we can change things as we go along and in fact we can just close that because we will only be doing one file today mostly all right cool yes welcome welcome everybody very excited to be here hello hello great okay so let's go ahead and get started by writing a test um the the first thing that i want to do is write a test that works for the regular standard library vector um and so we can go ahead and say let veck equals and then we'll do vec new oops here and then we can do oh and i forgot to make it mutable we got to make it mutable because we're going to be pushing something onto the vector and we can say uh vec push here for instance and let's just go ahead and push on um and let's go ahead and make these new sizes so push on uh three numbers here and then i want to go ahead and assert equal that vex capacity is going to be equal to 4 and of course this is a macro it's gonna have that nice exclamation point there um and vex len is equal to three and we'll just do this for now we're gonna be adding to this test as we as we go along let's go ahead and run this real quick with a cargo test and hopefully it passes and it does cool so this is the first thing that we need to know about vector if you've used vector at all you might have come across this before that there that vector has a difference between its capacity and its length the length of the vector is as you would expect the number of elements that has the side of it and so we have three elements um in here and i'm just gonna change this to two and three and we can get rid of these as well just to have different elements in there um the the length is the number of elements in here in this case three while the capacity is the number of elements that could fit on the heap allocation that vector has made without causing a reallocation and so uh presumably we can go ahead and push one more item onto this vector before a new heap allocation is made and in fact let's go ahead and test that real quick um we'll push on four and then we'll push on five and what i i believe will happen is that vector's capacity will go up to eight because i believe the way that currently it works is that every time you re-allocate um onto the vector it doubles the uh the size um and the length will be five here so let's go ahead and run that real quick and it passes as well so so we've confirmed here that um every time you uh go over the capacity of the vector it will double its size or i don't know if we've confirmed every time but i believe that's how it works and that's how our vector will will work today there's question from chat the first push element type implies the type of the overall vector exactly so we have explicitly said for this first element that that one is of type u size and that has made here um that our vector is a vector of u sizes and of course if we go ahead and change that to eye size you can see immediately right here that that updated to a vector of i size as well and because these numbers down here don't have explicit type annotations on them they kind of just go along for the ride and end up becoming that the right type depending on whatever they need to be in order for this thing to compile correctly cool and then there's a question of whether we'll benchmark this uh implementation that could be fun as well let's see if we have enough time um i i have at most two hours today we'll see we'll see how far we get along today um i imagine that my implementation will be much much slower than um you know i'm not i'm not i wonder if it will be slower um it it may end in the cases that we would test for may actually end up being roughly the same because vec is actually pretty simple at the end of the day we'll we'll see that could be very interesting um all right cool so let's uh let's go ahead and um start implementing it um so i'll go up here and say we're gonna have a pub struct let me go ahead and close the close the terminal there and i'm gonna call it my vec um here and then we have to decide what uh myvec will contain and i'm gonna base it off of the implementation of the standard library it's basically going to have three fields on it a pointer here and this pointer is going to be a pointer to the first element in the vector all right and how do we want to um model this this pointer i'm going to model it as a non-null raw pointer essentially which we can get from use standard pointer non-null here [Music] so this is going to be whoops not null of t and so my back is going to be a my pack of t any type t here let's uh pop on over real quick to the uh to the documentation here and take a look at non-null um because this is important so um most of the time we're not dealing with raw pointers you only end up dealing with raw pointers if you need to use unsafe for kind of building these low-level abstractions non-null is like a raw mutable pointer this star mute t but it's non-zero and it's covariant um the first one is important in that um it can never be zero so we can never this thing can never be null um and it's covariant um what covariance means uh in this case is not necessarily important um the the most important part is actually in the documentation here usually it's not necessary to go against the um the covariant nature of non-null um covariance is correct for most safe attractions such as box rc arc and vec so we we don't have to worry about that um there is a great article um on or it's not even an article um let me see if i can find it uh i'll post it afterwards it's by rain um and they made an article about covariance that does a really great job of explaining what covariance is and contravariance and stuff like that in terms of rust and in terms of rust lifetimes as well which is really great so i'll link to that uh after this um cool uh chad is uh asking is it possible to use uh box slice tea instead of raw pointers and an end it's probably um possible to use box slice tea um i explicitly don't want to use slices uh for the implementation um i you would so box slice t is not possible because you need some of the elements to not actually be initialized so it would have to be box slice maybe um uninit tea oh i see that you you actually wrote that underneath um it would definitely have to at least be that that probably uh could work although you have no way of actually allocating that space so you would first have to allocate i am i'm not sure if if it's possible i haven't i haven't tried it myself maybe it is but i'm not sure um it's definitely probably going to be easier with raw pointers but we'll but yes you you end up having uh to um yeah you end up having to use unsafe then instead yeah and then chad is also talking about what covariance means how covariance deals with uh inheritance uh often times um most of the time russ does have an inheritance but inheritance of lifetimes so normally this comes into uh into scope when you're talking about lifetimes and stuff like that but that's a little bit out of scope here because we can just assume that um unsafe will do what we want uh sorry uh non-null we'll do what we want all right back to the code so we're going to have a pointer and then we're going to have a length which is going to be of type u size and we're going to have a capacity and we already talked about what these are um again length is the number of elements that are initialized and in the vectoring capacity is the space on the heap that is kind of allocated there for um for use and once we pass the capacity we'll have to resize the vector instead great um so then this part becomes pretty easy we can go ahead and do empel my vector here and implement the new function and new is going to return self here um and this is all pretty straightforward obviously len is going to be zero and capacity is also going to be zero this is how um how the vector in in standard rest works when you create a vector with nothing in it it does not allocate so the capacity is zero meaning there is no space allocated on the heap for this vector it's at this point completely kind of stack allocated with no space for it for any elements at all um but then that lends the question of what will pointer be how do we go ahead and get a non-null uh pointer here now if we were using raw pointers we could probably just go ahead and stick in um a null pointer here um or with non-null the great thing that you can do is you can uh if you want to kind of mimic um a nullable pointers with non-null in a somewhat more verbose way um you can do option on null and this has the advantage obviously of um if you have a null pointer where option is none then you can't accidentally treat it as if it were an initialized pointer so this this can also be useful even if the pointer can be null to still model it as option non-null t um and uh but we are explicitly modeling it as this in that it can never uh be nulled um luckily if we go over here to the um documentation the very first uh uh function for non-null here is dangling which creates a non-null pointer that is dangling so it's not null but it's dangling um and and it's funny enough it actually mentions veknew as the use case for this so uh you know that's pretty easy um we're we're implementing vector here um this is you know it's useful for initializing types which are lazily allocated so we have some pointer um it's it it doesn't currently actually point to anything it's kind of dangling out there but it is not null so it's not been zero initialized it's effectively set to random um great so we can go ahead and do non-null dangling here and what this means is we have our first kind of invariant that we have to keep in mind is that when capacity is zero the pointer is going to be dangling and we shouldn't use it so non-null is allowed to be dangling that's totally fine but it means that we can't use it unless we first initialize it to something actually real and we know when capacity is zero that non-null will be dangling so we need to always keep that in mind is capacity zero if so don't use pointer um that's that's kind of rule number one that we'll have to establish um and we can go ahead and make this pub here so now we have a way to initialize our vector and in fact if we come down here to our test um and in fact let's go ahead real quick and um color these in here um and say use super so we're bringing in my back from from up top here this obviously doesn't compile because we don't have capacity and length so let's go ahead and create those real quick uh capacity takes in itself and returns a you size here this should be pretty easy we can go ahead and just return the capacity field and our length function is very much the same in that all we have to do is return back on the linfield cool um great this still complains because it can't actually infer the type for for t so for now let's go ahead and do um and in fact let us go ahead and yeah we can i'm gonna do nah let's not do that nevermind forget about what i just said um we'll just give it an explicit type to say that it's going to be in my deck of view size here um and it's complaining that uh you know it's marked as mute even though we're never using it immutably that's fine it will still run and of course our assertions are completely wrong this capacity should be zero and this length should be zero so if we run that and let me go ahead and whoops terminal here then everything is passing cool um and chat was asking is it possible to do this syntax which it is real quick we can just go ahead and do view size like this that might be nicer so we can um uh oh sorry no we can't we can't do that um the reason is is because new does not take um a uh a generic parameter it's the type itself um so we can do this uh use size that works fine then we can get rid of this type of annotation but it's the type itself that takes the generic parameter not the new function um so putting it here would be the generic parameters on the new function itself which it's not putting it here means my vac itself is um uh takes a generic parameter cool um so this is all working this is uh pretty pretty straightforward hello welcome um hopefully this makes sense to to everybody what's going on here we're not actually doing anything that interesting but now we're going to go ahead and implement the push um uh function uh method real quick and that's where things really get uh get going all right so let's go ahead and do pub fn push and it's going to take a mute itself [Music] and return nothing all right and the first thing that we said before was that we have an invariant already and the invariant is that when capacity is zero we have a dangling pointer so we should probably first say okay if self.capacity and we can we don't need to use the function equals zero then let's go ahead and initialize this thing first um so how can we go ahead and do that how can we uh initialize and actually create a uh a heap allocated piece of memory for use in our vector let's go back to the documentation here and take a look at the alec module here so standard alec is a bunch of uh memory allocation um uh utilities um and in fact we have standard alec here um we can go ahead and just straight use the alec crate instead um for those that are not familiar by default every rust program comes with the standard library but you can opt out of that and if you opt out of it then you opt out of everything including any heap usage whatsoever but you can re-opt into heap usage through the alec crate and then you can use it all of this stuff and so we could effectively make myvac completely non-standard so that people who are just using the allocation crate but not standard itself would be able to use our our vector i don't think we're going to do that today but that's uh an interesting exercise for the reader um cool so chad is asking how are you how are you going to make one with dynamic size um i think what you're asking is how are we going to allocate dynamic memory perhaps and that's exactly what we're talking about right now is uh allocating dynamic memory through these allocation functions and in fact if we look down here the functions are really what we care about and the first one is is alec here so what alec does is it takes a layout which we'll talk about layout in just a second and returns a mutable pointer to uh what essentially means it's returning back a pointer to bytes uh the question chat you got you've got a capacity field how are you going to change the size if it's not static uh yes we're going to change capacity as we go along as we allocate more memory capacity will change so capacity and in fact you can see here we have immutable reference to self we're going to change capacity um in this we're not quite there yet though um all right so this is how we allocate memory and you can read more about this how this goes through the global alec alec method so when you're using the alex trait you can register an allocator because maybe you want to go ahead and use a different allocator than what comes by default i think by default the the global allocator is kind of the system allocator for your system so you can go ahead and um you know if you're on on linux then it will use the linux um you know the the the whatever uh allocator comes with your system that you're using um um and you can change that if you want to use jmalik for instance uh and and opt into a different type of allocator then you go through this uh global allocator attribute um and then uh the alec trade has actually implemented um this uh this global alec alec and i should we should look at this this global alec trait for that uh for that allocator and so we'll go through through that and use the the system allocator in order to allocate our memory so last thing we need to understand about that is layout and basically what layout is a is a piece of data that describes the layout and memory of what we want to go ahead and allocate and so we can look at that real quick layout describes a an instance um or a block of memory you basically build as it says you build up a layout as input to allocator and it describes the size and the alignment of your your piece of uh memory and there's a whole bunch of ways to to allocate these things um i think we'll see that um there's more than one way to get the right answer with layout layout is kind of very um very open uh for use and we'll we'll see how exactly we want to to use it in just a second so we know that we need to allocate some memory we need to call standard um and let me go ahead and up here use standard alec so we can just call it alec so we want to call alec alec with some layout of memory so we need a layout and we can call layout and what do we want to do here well we act we um just like the the standard vector we want to allocate space for four uh items on the heap why four because someone i don't know somewhere hopefully did some kind of reasonable guesstimation of what a good kind of first allocation for a vector is and four was landed upon y4 i'm not exactly sure why not 8 who knows it being a power of 2 is always always good a power of 2 means that you're probably always going to end up inside of a page uh boundary then in in memory but we're going to follow the same kind of first allocation and also allocate a space for four of our items uh inside of it um and maybe you know later on we can decide to to change that instead um so layout comes with a really uh great uh kind of constructor function called array i believe um and what array takes is the number of elements in this uh array so we can just go ahead and say four and of course we need uh layout and it's inside of alex so we can just say alex layout and let's go ahead and look at array real quick inside of layout to make sure that that's exactly what we want um so it creates a layout describing the record for a t comma and so an array of elements of type t and n number of them so whatever we've passed in here and it says that on arithmetic overflow it returns a layout error what it means is that if there's kind of too much um if you pass in an n that's too large and it will overflow um the the size um of that layout um because you're passing in n and n gets multiplied by the size of t to just to describe the number of bytes that that this layout will take up if that ends up overflowing then this thing will return an error and so what we can go ahead and do is just go ahead and say expect could not allocate so we're going to take the easy way out for now um this is roughly what the standard library does this push obviously doesn't um it doesn't return a result or anything like that we could um change that and and return a result on any kind of allocation error return back an error saying hey we couldn't allocate this if you want to do something when there's you know maybe there's no memory it left or something like that then you can do that but for now we'll we'll mimic the standard library's uh behavior and just crash the program uh or panic the program when uh we can't allocate anymore um and then there's a question in chat so what if we increase the capacity and there's no continuous memory available will layout take care of moving the whole piece of heap somewhere else um well we're allocating for the very first time here right so there's there's no moving anywhere else we are for the very first time uh asking the allocator to give us a chunk of memory and there's only really two things that can happen there the allocator can say i did it here's a pointer to memory or say you're out of memory i don't have your system literally has no memory left i can't give you any memory anymore and um we haven't done that yet but we'll see that we'll actually end up uh panicking on when that happens all right so we have the the hopefully proper layout um we probably want to provide t here so that it knows explicitly what t is and we're not relying on any kind of type inference or something like that and i imagine there wouldn't be enough type inference for this to work so we're just going to go ahead and say we want an array of t explicitly here and and notice that we're passing in four here not four times size of t um because the array does that uh arithmetic as we as we already described it will decide how many bytes we actually need um based on the size of the array and how big our t is cool this is complaining i believe because it's uns um so it's complaining because we're not returning the proper type i'm going to go ahead and just write the to do macro here to get some of it to shut up this won't help in this case but that's always nice at the end of a function that you're creating or a method that you're creating to put to do at the end which kind of hides some of the um error messages that uh you don't necessarily care about um right now cool um so what does what does alec return to us again it returns back a result right there we go oh alec come on oh it doesn't return back a result so alec returns back every single time a we're all pointer um and so we're going to go ahead and get back our pointer and um we probably want to go ahead and cast that as a pointer to a t so we know that this will be it gives back a pointer to um to u8 um which is fine it's just giving x a pointer to to bytes but we know that alec returns back like a a properly sized and well-defined um and well-aligned pointer to um to our chunk of memory and our chunk of memory holds ts um inside of it so this is totally fine um still complaining because we're using unsafe without the unsafe keyword um one last thing that i think will go ahead now let's go ahead and do unsafe here and in fact we can just do this so this acknowledges right here that um alec is unsafe um and uh you know we can read about the safety here um well we have to let's go into alec alec and for safety you read about safety so this function is unsafe because undefined behavior can result if the caller does not ensure that the layout has non zero size so we know layout does not have non-zero size because we're getting a layout from a ray um and passing in a non-zero to it an array when you don't pass in zero will give you back a layout with a size bigger than zero assuming that t that you pass to array is also non-zero size so let me just explain that one more time alec expects that layout is describing a piece of memory that's bigger than zero bytes and we are guaranteeing that doesn't happen in fact let's go ahead and write this here and say safety the layout is hard coded to be um four times uh size of size of t now this is a very good question the first question that i've not thought of before if t here is zero size then what will happen so if we pass in uh for instance as t unit then unit size is zero um and so we end up with uh a layout here that describes no memory and alec explicitly says that that's not okay um let us for now punt on that and say this this this is curious i'm wondering exactly what um vec does here let's see if we can find this and raw vec and i believe let me go ahead and bump this up here and and of course i'm using github's new um dark mode here which is you know what everybody is raving about um let's see inside of vec push so we'll call reserve um and this calls reserve on raw vec and this calls try reserve and this needs to grow i'm wondering if that perhaps nope that's that doesn't care about zero size types um at least i believe so grow amortized oh and grow amortized this is not interesting grow amortized okay there we go so if mem's size of t equals zero they interesting interesting so what they're doing here is when um when probably i'm guessing on instantiation perhaps um is there an fm new interesting somewhere they are when the t that's being passed in is zero size they put the capacity at in that infinite basically at eyesight at u size max and interesting so um we can go ahead for now i'm just gonna do this uh if um size of we gotta do standard mem we're just gonna punt on this uh for for right now um we're gonna ask if t if it's size in memory is zero then we're going to panic no zero size types um and now we can say and size of t is greater than um yeah and chad is saying why don't i just use assert uh assert not equal mem standard size of t here no zero size types any questions about this why why we're doing this here um this basically just eliminates one of the invariants that we have to hold up we know that t is not zero sized it has to have a size and memory all right so now this is good we have our safety invariant we're meeting it the layout is hard coded to be four times size of t and size is a size of t is definitely greater than zero and so we have met that invariant which means we should be good now we've got a a we have a mutable pointer to t now um alec if we go back here and read about alec um here we say returning a null pointer indicates that either memory is exhausted or layout does not meet this allocator size or alignment constraints um and so we can we can get an error if uh if alec returns to us a null pointer but luckily we are um modeling our pointer as as a non-null pointer here and so we can do is just wrap our pointer and let me go ahead and do that and say left pointer so i'm shadowing pointer here and just saying that it's going to be equal to non-null new pointer so this um returns back an optional non-null and it first checks and says okay is the pointer null if so return none and if it's not null then go ahead and return back the the non-null um the non-null pointer which gives us a very easy way to to check whether our pointer was null and we can just go ahead and like everything else we're doing so far crash if it doesn't uh meet our expectations so if we get back in null pointer from allocating memory we'll just we'll panic with could not allocate memory any questions about this great so looks like everybody's on the same page here um we now have a non-null t that means we've got a niche like a really initialized pointer to a t and so far the invariant that we now hold on to is that that well initialized pointer actually points to a heap allocation of size 4 times the size of t all right and so we can go ahead now and say self.pointer equals a pointer here and then we can say self.capacity equals four and of course we have to update len to be um one all right so we've set pointer we've set capacity to four because we've allocated enough space for four things and we set len to be equal to um to one and we can go ahead and get rid of our to-do here and we should be able to push um one item onto vec what is complaining about here oh of course we're not done we haven't taken uh we don't take an item actually here so great we've allocated uh memory that's not actually what we want to do um i mean it is what we want to do we want to allocate memory but we also want to write to that memory um we now have a um enough space on the heap for our items but there's nothing there we need to write our item to um to that space and so in order to do that what we can do is pointer uh and this is kind of weird pointer as pointer so we're taking the non-null pointer and turning it back into a raw pointer real quick and then we can call right on it and give it our item and we have to wrap that and unsafe as well and we probably want to go ahead and write some safety tips here why this is okay um so pointer is non-null that's good and we have just allocated enough space for this item and three more so let's take a look at pointer right real quick to see any more invariants that are written down there so we can go to point to right um and uh point to write says it overwrites memory location with a given value without reading or dropping the old values so it's not going to so we're writing to destination here what we want to make sure of is that we don't read destination because it's completely it is completely um uninitialized um and so if we end up like reading destination and putting it onto the stack for instance and then it gets dropped or something like that we would end up running the destructor for t when t is actually uninitialized and that's undefined behavior that's not good we don't want to do that so this is appropriate for initializing uninitialized memory which is exactly what we want or overriding memory that's previously been read so we've we've read a value we don't want to go ahead and read it ever again we've already read it we already have a known value if we were to read it again we would end up possibly with a double free which we don't want to do chad is asking about if i added a format debug implementation um we have not added debug yet but we can we can certainly go ahead and do that um but we'll leave that for the end so this seems to be good like um now you might be wondering like um you know why why write and not something like um not something like this um pointer pointer as pointer what if we did this instead and i believe this is incorrect because this ends up reading the value at pointer so when we go ahead and use this kind of star assignment syntax the um to go ahead and assign to a raw pointer here we will actually end up reading the pointer which is exactly what we don't want to do so this is out of the question we don't want to do this we want to do this instead we can say here the memory previously add any questions about this any questions about the invariance that we're trying to uphold here now this code might not yet be fully correct we're going to talk about that um oh we could talk about that now we can talk about that now um a big thing that we have to worry about with unsafe code is panic safety as well to make sure that if our code panics that we're always in a good state um and so for instance if we go ahead and uh panic on this assert not equal here is our vector in a in a state that does not allow for any of its memory safety invariants to be violated and the answer is right here everything is good if we panic right here um our length and our capacity is always going to be set to what they should be set and and we're not going to allow any outsiders to read from memory except for the memory that is fully initialized in our vector so that's fine same thing with layout here we haven't really done anything to change our vectors so we're fine and same thing with non-uh non-null here if we panic right here after we've allocated we know that we would only panic if um if pointer returned null to us and presumably it would only return no to us if that memory was not actually allocated and so um you know there's no chance even here of leaking memory or anything like that so pointer has pointer right that cannot even panic it doesn't look like it can panic or it doesn't at least is not documented as being able to panic um so so this should be fine as well um now it's an interesting thing are we you know we first set pointer here and then capacity and then length is this fine to do in this order um we should be fine because nothing should panic uh in here to where we've set the pointer and then there's a panic here um where the pointer is set but the capacity is still wrong or or vice versa so this should be fine as well so we should also be panic safe we could panic in any place that is where it is possible to panic in in this code and no invariance will be violated and chad is saying more case is needed and generally you must offset the pointer into the right position so pointer is always going to be pointing to the beginning of our of our vector so we don't need to offset the pointer in in this case we're simply allocating the pointer setting it to the beginning of of our memory allocation and then our length and our capacity are what set um the bounds of where our memory is initialized and the bounds of where our memory is actually allocated and then you're saying this is push not new with val can you explain what you mean with that i'm not exactly sure um if you let me know what you mean then uh this i believe this is correct but if there it's 100 possible that i've written something that is completely wrong um while chat uh figures out what the what the issue is we can we can go on now we've only handled the case where capacity is zero obviously if we push more items onto our vector they'll simply be dropped are you are you saying we're not handling the case where capacity is not zero that's totally true like we have we have more to do here else to do this is this is probably more correct yeah absolutely we're not done we're definitely not done we are done though with the capacity uh equals zero case for sure cool so now we need there's basically two cases left as far as i can tell there's the case where we're pushing an item the capacity is not zero but the capacity is enough to push on a new item so for instance we push one item onto our vector the capacity goes up all the way to four but the length is one right we've only pushed one item but we've allocated enough enough space for four when we go ahead and push another item onto our vector we don't need to allocate any more memory enough memory has been allocated we simply need to write that value into the the right slot in our allocated memory and then bump up length and that's it um so basically what we can check for here is if uh self dot len is less than or equal to self dot capacity and then we can go ahead and keep our else case here with a to do so this is the case where the length of r of our vector is less than the then the space that we have for it and i think this is strictly less than yeah if we have a capacity 4 and a length of 4 that means we've filled up our entire vector so far so we strictly want to look for is the length less than the the capacity of our vector and in this case all that we need to do is to go ahead and write our item to the uh the pointer or to our memory allocation at the right spot so this is definitely going to be unsafe and what we can do is say self.pointer we know that uh pointer is um is correctly uh um is is pointing at at good memory we've done that we've checked that in the capacity case once capacity is no longer zero we know we are keeping the invariant on our self that self.pointer is always pointing to valid memory and what we can do is uh turn that non-null pointer into a raw pointer and then call offset on it or actually we'll just call add and what add does is it calculates the offset from a pointer and it's important to note here that count is in units of t so if we pass n one we are going to not skip ahead one byte but rather one times the size of t in bytes so if t is 10 bytes big or 12 bytes big let's say we'll we'll skip ahead 12 bytes from from from our pointer um so where where do we want to skip ahead to well we want to skip ahead to self.len here so um imagine um the length is one for instance we have our pointer we have one element in there and so we we jump ahead one element into the array and now we're pointing at the first uninitialized item uh in our allocated memory and here then we can call right again and pass that knight and then all we need to do now is update lynn to uh go ahead and update len to be plus equals one now i think this code is mostly good no we don't need to update capacity right we haven't allocated any new memory or anything like that so capacity stays the same this code is mostly good there is one case though that we're not handling that could go horribly wrong um well let's let's read safety real quick to make sure that we're doing this so both the starting and resulting pointer must be either in bounds or one byte past the end of the same allocated object which is which is good so we have our allocated heap memory and we know that um even if we go even if lin if lin is less than capacity we can we can point up all the way to um the end of our um uh of our allocated memory this is an interesting one the computed offset and bytes cannot overflow and and eye size this is interesting so think about this when lin becomes very large let's say it's millions or whatever billions we have a huge vector and our let's say we're storing t's that are also i don't know um 128 bytes big or something like that it could be conceivable that multiplying our length times the size of the item that we're storing actually causes the the computed offset to wrap around and that is a bug for sure um so we can go ahead yeah so and also the the offset being inbounds cannot rely on wrapping around the address space as well and and what will happen will sagful but again we're creating a safe rust rapper we cannot segfault that is um and in fact you know what will happen is most likely it will sag fault but it's not guaranteed we're actually in the realm of undefined behavior here so what will happen is we don't really know probably we'll segfault but we don't know and we can't rely on that behavior at all so what do we need to do here well we could do something like assert self dot blend times self dot len times size of t is less than i size max we gotta wrap this in parentheses here oh but this could also overflow hmm we could then do instead of asserting here we can do self.land checked mole so we're going to do a multiplication here and what checked mole does is return back as you can see here as you cannot see here because i'm missing a semicolon so you can see here uh is there no check mall on you size you size check mall did i spell it wrong checkmole yes i did spell it wrong cool so we're gonna do this multiplication here it returns back um an option use size and we can once again go ahead and panic in this case so effectively like we we now i don't think this is possible because and i have to do some thinking about this and i want to get a point across here that like writing these uh writing unsafe requires a lot of thought and the nice thing about russ having the unsafe keyword is that at least when you know when you write unsafe you have to like go through the checklist and see every single thing that could go wrong um i'm not necessarily convinced that this check is strictly necessary because i'm not sure len can get big enough for us to get into this situation but i'm going to leave it in here for now because this is definitely while it might be wrong it's definitely safe and leaving it out might be right but it also might be unsafe so let's stay on the on the safe side here and we can work on later uh convincing ourselves that this this check is actually not uh necessary but i think that this satisfies all the safety requirements that we have so we know that offset cannot wrap around um and pointer is initialized uh pointer is pointing to valid memory and writing to and offset at self.len is valid uh it's supposed to be less than i size and bytes ah that's true i think this also works um truncating you so when you convert i use size to an eye size this itself might wrap around as well i believe um this is why i don't like using uh i don't like using as conversions because it's kind of hard to remember exactly what happens when you convert um an a in a lossy way so i'm trying to see here if we look for um or in fact if we look at eye size and look at try from view size this i believe i think what we can do here is let's do this this might not be the most efficient way there might be a way that's a lot more efficient than this but this is the easiest one for me to remember or for me to implement right now while everybody's staring at me and it is we know we know that this offset in the usa space is has not overflowed and then we can check that assert that offset is greater is less than um this should work so we do the the multiplication in the u size uh space and then we check that that the u size that we get back is not larger than the maximum eye size that we have there's probably there's definitely a better way to do it than this but this this uh definitely works so we'll leave it at that all right so i'm fairly convinced that we're in a good place here that we have handled the point where we um that capacity is zero where length is less than capacity and the final thing that we need to handle now is where there is not enough capacity this else case here so here we need to essentially reallocate memory and and get more of it and luckily what we have inside of alec here is realic and what realic allows us to do is take in a pointer and reallocate it with a new size that we have um we can read about realic here um so it can shrink or grow a block of memory to the new size um and the block of memory is described by pointer and layout that we passed to it the layout here so it says if this returns a null known pointer then ownership of the memory block reference by pointer has been transferred to this allocator now importantly this means either you know might mean that the previous memory was completely deallocated and that the memory allocator has kind of moved this entire thing somewhere else or it can simply say hey there was some space after here and i'll just go ahead and um you know allocate that space to you so there's it makes no assumptions about um whether the pointer that we get back is the same pointer or not um that we had uh that we passed in and we can't make any assumptions about that either and we know that if the method returns null then the ownership has not been uh transferred to this allocator and the the contents of the memory block are unaltered um but obviously uh it has not uh reallocated that memory so we're kind of dead in the water then and this is important like pointer must be currently allocated via the allocator so we should be good there layout must be the same layout that was used to allocate that block of memory new size must be greater than zero and new size when rounded up to the nearest multiple of layout to the line must not overflow so essentially new size here um you know it cannot go b it cannot overwrap um of course so it's a little bit bigger than new size potentially if new size returns back a number that is not a is not a multiple of the alignment of the type it will check against the alignment of the type so we need to we need to do that as well so let's let's go ahead and get into this we'll go ahead and say unsafe and then we have alec realic and we need to pass in the pointer so it's self.pointer as pointer it's going from the um the non-null pointer to a raw pointer which is what real expects then we need to pass in layout here and layout is going to be equal to [Music] and i believe what we can do here is layout from from size align unchecked this needs to go inside of the unsafe block um let's look at from size align unchecked on layout here because this is another unsafe uh so from size align unchecked takes in a size and a line it creates it doesn't do any checks um but um i uh safety yes so the function is unsafe does not verify the preconditions from from size lines so as long as we meet these conditions and we know that we meet them we can call this and we don't need from sizeline to do the check for us so the line must not be zero we're going to call a line on um we're going to call stand mem align t and we know t is not zero sized and so a line must be non-zero and in fact i think even zero size types have an alignment that's non-zero um a line must be a power of two stand mem align of will it this is interest this is an interesting one a line must be a power of two um oh i guess one is a power of two right yeah uh because two to the zero is is one so anything that we get back from standard stand member line should be a power of two that's fine and size when rounded up to the nearest multiple line must not overflow this is going to be the hardest one to check but let's go ahead and and um call this here we're going to pass in stand stood mem size of and uh i'm off the edge here i hope everybody can see this i'm gonna pass in step in size of t and for a line this is a line of t i don't know if we talked about alignment if you're not familiar with alignment um what alignment is is that every type has a place uh has an off has um memory addresses where it it needs to be because the processor expects um certain chunks of memory to be at certain offsets in memory and so if you have a type that has an alignment of two for instance it must always start at a memory address that is a power of um that can be divided evenly by two if you have a type that has an alignment of 16 then that means if you that that type must always be at a memory address that is evenly divisible by 16. and this stood mema line of what it just returns back is what the alignment of our type is and so whatever t's alignment is we'll get that back here so this should be fine the only thing that we're not accounting for here because the line will not be zero and a line will be a power of two size um cannot overflow when rounded up to the nearest multiple of a line this is this is an interesting one and given our non-success of um that we had before and this is wrong too because this is the size of uh the size of memory which is not stood mem's size of t but rather sydney's size of t times self dot capacity here so really what we need to do here and this is this is always i'm always the worst at arithmetic so i'm it's the thing that i i struggle with the most this is size and what we need to verify here then is um that size um and then we need to uh what was it we need to round up to the nearest multiple um of a line um what is the easiest way to do that um we need to make sure that when we add something to size that and take the modulo of a line that it ends up being zero so we'll need a line here as well um there's a there's a question in here that i um i tried to make clear at the beginning but i it demands um being said again why are we building a custom type a custom vec if you can get it from github do not do this in real code this is purely a learning exercise never use this code i will publish it to github with a big old uh indicator saying do never never ever use this this is purely for learning now the lessons that we're learning here should hopefully be applicable to other situations where you um don't have a ready-made safe type like vec but this is totally just a learning exercise completely just learning exercise um cool so let align is this and so now we um is there a like there's not like a round two or something like that that can take care of this um so we need to uh checked add um size modulo align oops um i believe this should do it right um we if our size is 10 and the alignment is 4 then 10 modulo 4 is 2 and when we add 2 to 10 we get 12 which is a multiple of a line um like i said i'm really bad about about these types of things especially when people are watching me so chat makes check my homework there to make sure that my thinking is correct um assuming my thinking is correct then we can just go ahead and do this and say um because we have uh we have wrapped around trying to um trying to get new memory um chad is wanting us to note the interesting relationship between physical memory addresses that the processor can load from and virtual addresses that the os allocator hands us yes so in a sense the os allocator or the allocator it doesn't have to be an os allocator the allocator is passing the buck to us to make sure that we are always dealing i mean it's not passing the buck it helps us with this but make sure that we're always dealing with um with memory addresses that have correct alignment um so that i can very easily um kind of pass those over and translate them over into uh into physical addresses because it's really just the processor at the end of the day that really cares about this [Applause] um cool what is this complaining about oh yeah it takes a line as well so i'm 90 percent confident that we're doing things right here um that we are checking all the invariants that we need to we're passing in the correct layout and the last thing that we need to pass in is the new size so let me size here um and new size here should be in bytes because what we're doing is dealing with with bytes here what what new size is going to be is going to be stood m size of t here times whatever new capacity that we have and so we want to up here say a new capacity is going to be equal to self dot capacity times two and just to make sure let's go ahead and do checked mole here as well that our new capacity doesn't wrap around because i don't want to even have to think about what could go wrong if capacity wraps around now just to reiterate all a lot of these expects are not necessarily necessary um so homework to you is to go through the expects make sure that we have we are we're you know doing the right thing but also it's potentially possible to remove some of them because we won't actually end up um we're not actually saving ourselves from um vitally violating any memory invariants that we might have this is us being very cautious about how things are going all right um so really gives us back a pointer and what does this complain about oh yeah that it expects the pointer as not as a raw pointer to t but rather a raw pointer to i'm gonna make this a tiny is it okay if i make this just like a tiny smudge bigger i hope that's fine um cool so now we have potentially reallocated memory realic will take care of if it can if it does get new memory it will move the memory that we previously had over to the um you know over to the new memory allocation that it's creating and so we really need to just check the pointer here and make sure that it's non-null so we can go ahead and say non-null new not any new and pass and pointer as pointer to t here and we'll just we'll again once it if we can't really we'll just go ahead and explode could not reallocate now this is interesting though if it um if we go back to the documentation on realic here inside of here then if the method returns null then the ownership of the memory block has not been transferred so let's assume that this that calling react returns null we will panic in that case here um and is that fine that should be fine we will leak we won't leak memory we will panic um well currently we will leak memory but we we still have not implemented the drop uh um trait on this so but but we should be in a good state because we haven't updated capacity and we haven't updated length we haven't updated pointer the pointer that we passed in here is still valid in that case the capacity is correct the length is correct so we should be totally fine to panic here totally cool great now if we didn't panic here we know that we have successfully reallocated memory and we now have a new pointer and so we should be able to say um well first thing we need to do is go ahead and do pointer as pointer right and then um sorry not right first we need to add self dot len yeah self.len just like we did before and we're going to write an item here so this is fine so we we've now written at length here we probably want to debug assert here that self.len equals self.capacity right if we reach this case we should always we should never ever ever have len be more than capacity we've already checked the case where len is less than capacity and so in this case we should always have len equal to capacity and i'm doing debug assert here because unless we've made a bug in our implementation the debug discern is fine defunct assert will only assert on and debug builds and and release builds will just that code will just fade away all right so we've written our item here um importantly there's probably the same um the same check that we did up here we should do down here but i believe that these checks here that we've done in order to reallocate the memory means that that um we don't need to to redo the check here so we should be fine again let me know if you think that that's wrong but our our check that we did up here um should be met no matter what when we get here because of the checks that we did up here so we've written our our new item at length offset and then we can do self.pointer equals pointer self.len equals is plus equal one and self.capacity is uh equal to new capacity and in fact we can move this out of the unsafe block um now i am curious if self.len plus equals one is guaranteed to always work if this overflows which should not be past possible because we have checked that we have enough capacity that capacity doesn't overflow and if len is equal to capacity then we've also checked that len won't overflow when doubled and so adding one shouldn't double so this should be fine to do this it's important to really think about whether things can panic for instance because if we were to panic right here without capacity set to the new capacity i think that would be fine but we would leak memory for sure in our drop implementation which we've not yet done and in fact drop is not necessary because all that drop is going to do is free the memory but that's fine like we don't need free memory there's nothing about memory safety that says you can't have memory leaks and so this here should work now i want to say so what is our capacity going to be it should be 8 and our length should be 5 here let's run this hopefully my computer does not explode awesome now we could check with valgrind to make to to see how we're doing with uh memory allocations um so i'm going to do cargo build dash release and i never remember valgrind how it works but i want to say we should just be able to do like this target release um where is my target release um oh yeah we have we are building a library here um we could we could run valgrind on our test binary as well the thing with that is i always worry about uh the test harness um kind of muddying up the um the output and stuff like that so um what you can do instead here is just create a binary like this and i'm just going to go ahead and steal our test here oops and i want to say that we need to do what do we call this thing my back my back my mac my back and this should be fine and now if we do cargo build then release um i'm sorry you gotta pass in oops pass in the name of the binary here and that should give us this um yeah there it is a nice executable for us to use that only has this code again we could have used the the test code i think that's inside of depths i want to say so target uh it's like a debug real quick debug depths this uh this right here is the binary for our tests but it also includes the test runner and all of the the code that um gets bundled in when you actually run tests that prints out you know test pass and stuff like that and that's that's a lot of extra noise so we just got a plane binary now that we can work with and we can call valgrind on valgrind is a great tool for looking at memory allocations and and learning more about them and so target release buyback um so we have leaked memory which is fine we had 11 allocations with 10 freeze the 10 allocations that were freed i would imagine are coming from the um from rust kind of the rust run time the initialization that happens um which is basically the sea runtime and the 11th uh allocation that was not freed um is is our allocation that we made um although it's interesting no it's it is free so sorry nine of them are probably from the rust run time because two we've done two allocations remember right we've done allocation right here and allocation right here we've done alec and realic um but when we reallocated um potentially um and i'm this this might not be the case it might be that there was there that the allocator simply increased the size of our and in fact it probably was this the allocator just took the allocation that we had before and you know probably behind the scenes like has a whole memory page that it uh that it has at its disposable and kind of marked for for our use but only said like hey you can only use this first part um and when we reallocated it just said okay like you can have a little bit more of that page that i allocated for you that's most likely what happened behind the scenes um so we're definitely leaking leaking memory and in fact i think we can use this right here um go away you're not fun what did i i think i have to do this before oh that's annoying we're running with leak check full here um see did it uh to get even more information about um yeah so it's going to give us a let's close this give us a little bit of a stack trace here to look at and you can see um in realic here which is the lipsy realic we are uh that was where we actually allocated um that was that was lost we never um deallocated the memory that we got from realic so very very useful information very obvious in this case but valgrind is great for weaning this information when you don't necessarily have it at your disposal all right so how can we go ahead and fix that well let's go ahead and implement drop and drop of course is what gets what gets run when our type gets dropped um ot of course we need this to be t oh there's a new question that we've already answered but i am happy to repeat this once again for sure because this is it uh i am writing my own vec for educational purposes do not use this code do not write this code take this and use it where you don't have no worries totally get it no it was already asked and i'm always happy to repeat it because like i don't want anybody to get the impression that implementing your own vector is a good idea uh because it's it's not um that the reason for that is because the the standard um uh libraries vector is very good uh so there's really no reason to implement your own other than for educational purposes um but a lot of this i just want to repeat again a lot of this information can be used in situations where you need to dip down into unsafe goodness um not to build your own back but for some other data structure or something like that it does happen every once in a while um particularly in very advanced use cases and so this is this is probably good knowledge to know all right so we're going to implement drop here and we've done we've done our best to try and make sure here that if we enter into drop here that we're in a good state so it's up to this code up here the unsafe code here to make sure that when we get drop here that everything is in in a good state then so what we need to do is first drop all of the items in our vector so our vector itself has memory that it's allocated it's allocated heap memory but there's also items in that heap allocation that might themselves have other publications let's imagine a vector of strings for instance a vector of own strings you have the heap allocation that we that we allocated for our vector and then each of those are strings which have pointers inside of them to other heap allocations and we need to signal to those strings for them to um that they're okay to be freed for them to run their own drop implementations um and there's a great way to to do that with standard pointer drop in place um and if we look at drop in place here so this executes the the destructor of any of the pointed to value as it says it's semantically equivalent to calling point to read and then disregarding the result but it has some advantages over that um it can work with unsized types because on-site types cannot be read to the stack um and it simply doesn't um need uh to bring those items onto the stack so it's friendlier to the optimizer um the you know it doesn't need to prove anything that it's that it's safe to um allied copying those values to the stack because the the optimizer will try and say ah you don't actually need to copy this to the stack if you use drop and place it means don't you definitely don't need to um so let's look at our safety rules here so it's undefined if any of these conditions are uh are violated to to drop must be valid for both reads and writes okay two drop must be properly aligned that should be fine and the value to drop points to must be valid for dropping which we should be fine here and there's kind of a cheater way that we can go ahead and do this that i think just because we're reaching the the end now i'm i'm gonna go ahead and do there's a question in chat is this only good for own types no we're going to we're going to call drop on if we if t happens to be borrowed we will drop the borrowed values and dropping a borrow does essentially nothing it doesn't call drop on the thing that the borrower is referring to it simply just goes away so this this should work fine for everything so what we should be able to do is to call stood slice from from raw point uh from our parts mutt here and i i said i wasn't gonna use slice here but this is the cheating part here we're gonna we're gonna use slice in the in this case um i guess the other way to do it is iterate through and drop drop each item one by one so we're going to turn our pointer and our length into a slice and that slice will then be dropped um so all we need to do here is self dot sorry self dot pointer dot as pointer and len is self dotlin and this needs to be inside of unsafe like this so with this call we are effectively dropping all of the items in our vector which is half the battle now what we need to do is deallocate the heap allocated our heap allocated memory the heap allocated memory just for my vec um and as you probably imagined inside of the alec module in here down below there is dialic right here and d alec does what you would expect it goes ahead and deallocates the memory according to to the layout that we have so we can just call alec dialic passing in self.pointer passpointer here um and we can pass in layout here let layout um and this we can just use uh our old friend from before layout from size align unchecked and these should all be fine we've kind of already done the check and and push um to make sure that uh that we're not gonna violate any of these uh invariants so this is going to be stood the size of stood men size of t times self dot capacity like this and sorry that this is not this is really not uh make it one size smaller hopefully you can still see this this is not formatting as i would hope it would hopefully in just a second it does and the align is going to be stood them a line and there we go and what's it complaining about here oh yeah it takes we're passing in a raw pointer to a t but it just wants a raw pointer to bytes we can do this um casting raw pointers also works like this you can just say figure it out cast to whatever you need um that's fine i like leaving it like this just so everybody knows what we're doing and there's not any kind of magic there and so now we should be good we're dropping all of the elements in place and then after we're done with that we go ahead and call d alec to out deallocate the memory that we have specifically allocated for myvec so if we go ahead and open this back up let's run it let's run valgrind once again and see if our grind is happier with us so we can do build so we've built the myback binary and then we can run valgrind again hey and everything looks good we have 11 allocations and 11 freeze and it doesn't look like we've leaked any memory whatsoever and this is this is interesting too it says that 2073 bytes were allocated let us inside of here replace this not with with the standard library vector instead and go ahead and build that and then run and it looks like it's also allocated 2073 bytes so we are from a memory perspective at least for this example um just as memory efficient as the standard library's vect implementation which is good all right we're coming to the end here just to make sure that our vector actually works as we expect we're going to go ahead and implement git here and what git does is it takes an index and returns back an optional reference to d so um how do we go ahead and implement this well the first thing we need to do is check that index is is good right so if index is greater than or equal to self.len then return uh none this is the case where we're out of bounds essentially and now we don't have to worry about the out of bounds case now what we can simply do is um do self we need this in unsafe because we're going to be reading from our pointer our raw pointer whoops unsafe self dot pointer let's scroll down here as pointer add the index and then we should be able to read and borrow here and we need to wrap all this in some and what's it complaining about here so this is what this is returning to us is a pointer to t right so we need to first yeah um de-reference that and then get a reference to it like this and what's complaining about here t cannot be dereferenced ah this is returning back our a t here so this is actually not what we want here we're reading the pointer um and actually uh actually getting the value getting ownership of the value so i think what we'll do instead is just instead of using read this should be fine here we're just getting the raw pointer from add then dereferencing it and getting a getting reference from that so if we go ahead and run cargo run everything looks good but we should probably in here assert equal that vec dot get let's get the third item here should be sum three cargo run oh and of course it's not three this is zero one two three so this should actually be four and there we go and in fact what we could do is for nn0.len here we can go ahead and get n here and check that it is equal to n plus one and that works oh my gosh thank you chat you are right we were using vector before okay all right good so we just uh verified that standard vec is uh is works which is good it would be very surprising if we found a bug in standard vec in this very small exercise but we've also confirmed that my back also works which is good now i'm interested to see just to make sure like um structs uh drops and if we implement drop for this funky little drop struck here and just say print line dropping and instead of this being a uh myvek of view size we said this is my back of dropped and we just pass in dropped here well let's do two of them why not um let's complain about here too many p's and we go ahead and print uh end of main here um and let's do vec dot get one let's go ahead and insert eek here that there should be some dropped and i guess we'll have to implement um partial eek here so we can compare what is it complaining about now debug also whoops in order to use a circle it has to be debug and what i want to test here is that we should push these on get one everything's fine and then here we print end of main and then we should see drop in twice after end of main if we see it before then um ah this is a zero-sized truck good point um let's go ahead and throw a you size in there um and then this should be so this should work oh what looks like we've got a bug then um this is right or maybe ah of course this capacity is now four and the length is two and we do have a bug all right we are calling drop in probably inside of get here and notice here like this is this is membranes we have a memory safety issue and the reason is we are not upholding an invariant here um so let us say that now let's go back whoops save here what did i do i screwed this up yeah let's go into uh get here just got i'm going to get and my assumption is that we shall see going to get dropping just got going to get dropped and just got exactly so we have the same issue that we talked about before where when we uh dereference the pointer here we're actually reading t um instead of um and we're reading t and it's like being put on the stack and then we're dropping it at the end so we get a reference to it and then but we're also dropping it the correct solution to this can we do oops and unfortunately we can't we can't do this um where we cast first to a constant pointer and then cast to uh to a reference um and now i'm forgetting exactly what the right way to solve this is um we want to turn the value into a reference without dropping it um oops and we can't do that here because if we dereference uh this pointer then it will end up being dropped which is exactly what we don't want hmm that's a good question chad any uh any ideas what we might do in order to to fix this bug there's been i think several discussions about this about how to how to create a raw reference so like a reference from a raw pointer directly without running into these um running into these issues um we can go to google here and see uh draw pointer to reference without dropping we don't want to modify it we want to read it um and unfortunately i'm not exactly sure what the best way to to solve this issue is um so we'll we'll leave this as as homework for everyone um and it shouldn't be too hard um but the the goal here is to fix this bug that we have um oh you know what you are totally right you are totally right it's not a bug we're doing the right thing thank you i was i was losing my mind there thinking there is no way if we do this we should now see the right thing oops gotta go back to uh boom and we gotta make the reference here um yeah yeah yeah okay creating a temporary it takes a lot to prove that we're we're doing the right thing here um third dropped you are totally right here there we go you're right thank you very much so that's that that is uh one last lesson um about uh about unsafe is that it can be um a real doozy there all right well that's all i have for for you today we went a bit longer actually than i was hoping to but um we we fixed all the bugs we've got it working i can go ahead and push this up to github for everybody to to take a look at um [Music] as long as there are no remaining questions this will be up on youtube afterwards i hope everybody enjoyed it again don't use this code don't implement vec on your own except as a learning exercise like we did here please let me know what you you thought make sure to subscribe on twitch if you can that really helps me also on youtube and twitter that you can find me i'm at ryan underscore levick on twitter and hopefully we'll be doing this soon i'll probably be back next week with a new beginner friendly tutorial since this was more of an advanced one so thank you very much i appreciate everybody's time and i hope everybody has a good weekend then all right thank you
Info
Channel: Ryan Levick
Views: 11,737
Rating: 4.97995 out of 5
Keywords:
Id: 3OL95gZgPWA
Channel Id: undefined
Length: 124min 2sec (7442 seconds)
Published: Sun Dec 13 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.