Statistics and Excel Poisson Distribution Roller Coaster Line Example Part Number 2 Get ready, taking a deep breath, holding it in for 10 seconds, looking forward to a smooth soothing Excel. First, a word from our sponsor. Yeah, actually, we're sponsoring ourselves on this one because apparently the merchandisers, they don't want to be seen with us.  Like our Accounting Rocks product line. If you're not crunching cords using Excel, you're doing it wrong. A must-have product, because the fact as everyone knows of accounting being one of the highest forms of artistic expression means accountants have a requirement, the obligation, a duty to share the tools necessary to properly channel the creative muse. And the muse, she rarely speaks more clearly than through the beautiful symmetry of spreadsheets. So get the shirt, because the creative muse, she could use a new pair of shoes. If you would like a commercial-free experience, consider subscribing to our website at accountinginstruction.com or accountinginstruction.thinkific.com. Here we are in Excel. If you don't have access to this workbook, that's okay, because we basically built this from a blank worksheet. However, we started in a prior presentation, so you could go back there, start with a blank worksheet. Or you'd probably be okay to start at this point with a blank worksheet as well. If you do have access to this workbook, three tabs down below. Example, practice blank. Example, in essence, answer key, practice tab, having pre-formatted cells. So you could get right to the heart of the practice problem. Blank tab is where we started with a blank worksheet. And now we're continuing with it at this point in time. Quick recap of the scenario. We're looking at a Poisson distribution situation, which often has to do with line weighting situations. As it does here, lines related to a roller coaster ride in this case. Last time, we imagined that we wanted to figure out how many people arrive in general during like a busy time, for example, in each one minute time period. So we went out with our stopwatch and we looked at 1,001 minute time intervals and we counted how many people arrive in each of those one minute time intervals. So for example, in one one minute time interval, we had four and then in the next one minute time interval, three people arrived in the next one minute time interval, two people arrived and so on and so forth. And we imagined doing that a thousand times. Obviously, we didn't do that in practice a thousand times, but instead used the random generator function in Excel, which is in the data tab analytics group. If you don't have that analytics groups, you can add it in the file options that we looked at in a prior presentation. So this simulates us actually kind of gathering the data, which might be useful then to then think about, does it conform as many line situations do to a Poisson type distribution? So we then said, okay, let me count up using our buckets and bins and my frequency formula, how many we have counted in our 1000 intervals that had one person arrive versus zero versus one versus two versus three and so on. And then we can look at the percents. So in all of our one minute time frames, 6.2% of the time zero people arrived 17.10% of the time one person arrived and so on and so forth. So now that we had our data, we can then plot it and say, if I plot my data, does it look like possibly a Poisson distribution? That would be one indication. Does it conform to the conditions of a Poisson distribution, which we talked about in a prior presentation many line situations do? That would be another indication that we might be able to use a Poisson distribution and oftentimes the variance will be close to the mean if we have a Poisson type of relationship, which we have a pretty close relationship here. So now we're thinking, okay, then I might be able to predict what's going to happen in the future with regards to line waiting situations by instead of trying to extract the actual data using a smooth curve or function, the Poisson distribution, that's what we're going to do now. So if I go to the right and we say, all right, what if I just plotted this with a smooth curve? And by the way, I started on cell two. So I'm just going to delete cell one and hopefully that doesn't delete anything or row one. I'm going to put my cursor on row one, right click and delete. And so to bring everything up to the top because I started on the wrong column, hopefully that doesn't confuse anything too much. We're in column Y. Why? Because that's just how it happened. That's just how things happened. So once again, I'm just going to say equals and bring over my same data from the left. So I'm going to say equals this amount so I can just bring in over that same data. And then when I calculate the mean, I'm going to say that the mean now equals and notice the original mean that I used to calculate the data set was 2.75. When I actually look at the data, I calculated the data, it came out to a mean of 2.73. So if we were actually looking at our data, we would be using the 2.73 as the mean because that's the mean of our data. So let's just pick that one up. I'll say the mean is 2.73. That's what's in our data set. We're going to add some decimals, home tab, number group, add a couple decimals. Let's make column Y a little bit longer. Why? It's a long why. All right, so then we're going to say, all right, now let's do our data in accordance with a Poisson distribution. Let's just build a table, build a graph, and see how it compares to the actual data that we have. So this is the ideal data curve according to a Poisson distribution. We'll call it P of X Poisson function. Let's just call it P of S X for the Poisson. And I'm going to say then let's copy these two and go to the home tab, font group, black, white. Let's center it. And there we have it. And then I'm just going to take the X's down to what did we say 29. Remember that the Poisson distribution, I mean, it could go on forever. That's why it's skewed to the right or positively skewed, because you could imagine in one minute interval, infinite people arrive, but that's not likely to happen. So we're just going to cap it at 29. So I'm going to say one, two, select those two, put my cursor on the fill handle, drag it down to 29. 29. Bam. All right. And then I'm just going to do my Poisson dot dist function, putting my cursor in AC2 equals Poisson dot dist. And so we need the X value. I'm going to pick up this X value, comma, we need the mean. I'm going to pick up the mean here from our data set. I want that to be the same as I copy it down. Select F4. Notice you could use spills and arrays, but I think in this case, I kind of like using the absolute values, because if I wanted to put a table around it or something, I would rather do that typically. So I will switch back and forth from arrays in the areas that I think are most useful for the arrays versus not arrays. And then I'm going to say that we could have cumulative. We'll give us the cumulative down if I put true or I can put false for the probability mass of one. We want the probability mass of one because we're looking at each outcome here. So and I believe that is a zero. So I could put zero or false for probability mass function. That's what we want. Close it up, enter. And then I can put my cursor on at home tab, number group, percentify it, add a couple decimals. And then I'm going to double click on the fill handle to drag it down. Boom. So it copies it on down. So so there we have it. And that looks good. Okay, so let's take the difference between that and my actual data. So the difference just to get an idea. Home tab font group, black, white, wrap center. So my actual data that I came up with over here said 6.2% minus what I come up with a Poisson distribution 6.52. So let's add some deaths and percentify at some decimal. So you can see it's pretty close. My Poisson distribution, if I copy that down for each of these items is coming pretty close to the percent likelihood that we came up with our actual data. So so in other words, if I look at this, we're going to say for each one minute time interval, the likelihood that zero people show up is 6.52. According to the Poisson distribution, which is within 0.32% of what our actual data set, the likelihood that one person is going to show up just one, not cumulative, not zero or one. In which case we can sum the two up right to 24% that zero or one shows up. But no, just one person shows up likelihood 17.8%. Just two people show up in the one minute time period 24.3%. What's the likelihood that one to zero one or two people show up? Well then we can say, well, if I can sum those up and it's not going to mess up with calculus to have a curve, then it's going to be 48.63% that we have one zero one or two people show up. That's the idea of the curve. Now if I plot this, then I can plot this curve and say let's just select this area here. And I'm going to say insert and let's make it a chart. Let's go to a bar item and plot that. So I'm going to bring that I should have brought it up. So there it is. And then I'll go into my data. So let's say my data, this data is good. But for here, I want to make sure to pick up my own numbers from zero to 29. And so there we have it. So I could plot this data and compare it then to the data set that I have, the actual data set that we calculated. So I could add another one and say add another data set. And this one, hold on a second. I don't want to edit. I want to add a data set. And this one is going to be for this and the data is going to be here. Hold control shift down shift up so I don't pick up the total enter. And so let's see if it does what I was hoping it did not. What happened to that second data set? Okay. So let's what happened? Let's do another. Let's do it again. Let's say add data set. I want it to be equal to this. And then the series needs to be from hold on a second. It needs to be from here down shift up. Okay. The percentages closing that up. And this should be I'm going to say the percent of the total. Right. Isn't that right? Let's try it. Let's try it again. Okay. So there we have it. So you can see there, you know, pretty, pretty closely lined up. I've also added the legend so you can see the two different colors with the plus button and then the legend. I should probably adjust the labels, but we'll keep that for now. Now we might also represent this with a line if we have two items on the same chart because it's a little less messy. So I could select the same data. Put in my cursor on AC one control shift down and then insert the charts hitting the drop down. We want a line. So now we've got the line with markers. So let's pick that up. And if I go into the chart design data and the data. So here's our first data set. I'm going to add another one and I'm going to go to the right to do that. And I'm going to pick up this percent of total. I'm going to delete this bit. I'm going to hit this little thing to make sure I'm picking up the. It's going to sum this up, putting my cursor on L two control shift down shift up so I don't pick up the total. And there it is. Okay. And then I'm going to edit my horizontal, my horizontals to be equal to. I'm going to hit this to make sure it picks it up from here. Control shift down to 29. And there we have it. And okay. And okay. So you can see it's quite close when we when we plot them in this format. So I could add the legend again. So I could say add a legend. And then we could, you know, make one of these transparent or something like that. If we wanted to make it more clear. But those are the couple charts that we can put together and say, okay, that's a pretty close relationship. All right. So remember what we can do with this data. I'm going to pull this to the right now is we can start. We can start asking questions about the data in terms of how many people might arrive. Right. So let's just first let's calculate the mean and the variance. So I could calculate the mean of this data. And one way to calculate the mean is to basically we could take this, all of this times this. And that and that will be another way we can calculate the mean. So in other words, when we calculated the mean over here, what we did is we we just took the average of all of the results. If we look at this, then let's just I'll show you in a couple different ways. I'm going to pull this to the right. I could say, let's say this is going to be X times P times P of X brackets.  And so I could say this is going to be then this times this. But I'm going to copy that all the way down. So and then let's say that this is going to add. Let's add some decimals here. So basically we can say in the one minute time interval, right, there's a likelihood that one person arrives. We have 17.8%. It's going to be like 0.18% of a person, right? If we were to calculate this out to try to get the mean, right? And if the likelihood in one minute time of two people arrive is going to be the 24.3%. So we can say that'd be like 0.49% of a person. So then if I multiply that out and then I add up this, it equals the sum of this, control shift down, and I add a decimal, home tab, number group, adding some decimals. There's our mean. So we can also calculate, so you can see that's the mean that we came up to over here. So we can also calculate that using a function, which is a good time to introduce. We can use the sum product. So if I want to do that this way, I can say this equals the sum product brackets, and then I've got my two arrays because I want to multiply then this array times this array. So I'm going to select this entire array, control shift down, and then say comma, the next argument or the next array is going to be this, control shift down, and enter. So now I should get the same thing, home tab, number, couple decimals. Now if we wanted to get to the variance, we could do that a couple different ways. I'm going to pull this over. I'm going to pick these two up. I'm going to control X and put them over here, control V, and then I'll add another column here. This column is going to be then X minus the mean, and I'll pick this up and say home tab and clipboard. And so now I'm going to say this is going to be X, this number minus the mean, which we've calculated a few different ways. Let's pull it over here.  And then I can add a couple decimals, number group, adding a couple decimals, fill handle, copy that down. So I'm going to take that and then I'm going to square it. So I'm going to say this is going to be squared. And I'll format paint this, home tab, paint brush squared to do that. I'm going to say equals the cell to the left, carrot, shift, six, two to get to the power of two or squared, entered. I can add a couple decimals there. Double click on the fill handle, bringing it down. And now I'm going to take that column times the P of X times P of X. And I'll put my cursor here, paint brush here. And then this is going to be equal to this. That we just calculated times the P of X percent. And let's add some decimals. Decimalizing it, double click, bringing that down. And so to get to the variance, one way we can do that variance, is I'm going to say equals the sum of these items, control, shift, down, enter. And if I add some decimals, we get to the same 2.73, because that's part of the relationship of a Poisson distribution. And this is the perfect Poisson distribution. Now you can also do that variance calculation just to practice the sum product function. So we'll say this equals sum product. This is an array formula. I want another bracket because I'm going to take it to the power of two or square it. Put my cursor on cell AB2, control, shift, down on the keyboard. So I can go down to the bottom. I'm holding control backspace so I can go back up top with the keyboard. And then I want to take each of those minus the mean, which I'll pick up here in AK. So I'm going to pull this down. And so we have that closing the brackets, taking it to the power of two. So I'll say shift six carat to the power of two square in it. And then the second bit, comma, the second array is now going to be here AC2, control, shift, down. And then I'm going to hold control backspace to go back up to the top and close up the brackets and enter. So there we have it.  And once again, we have the variance should be the same as the mean. So just a little bit of practice with the Excel formulas there. Now some other questions that we might ask from like a from like a practical standpoint is we might say we might ask what's the likelihood that we have equal to or less than three arrivals within a one minute time period. These are the kind of questions we can ask. Now if I plotted everything out over here, I could say, well, these, this is the likelihood that zero people arrive, exactly one people arrive, exactly two people, three people. And I could sum those up. I could say, OK, this is equal to the sum of these. Hold on a sec. Some of these and enter and make that a percent home tab number percentify adding some decimals, or I can use the poisson cumulative function so I can say this is the poisson dot dist. And this I'm going to take the X is going to hard code is going to be three and then comma the mean 2.73. But this time instead of taking it at one point in time having just three people arrive, I want to go from zero up to three people. So that's going to be the cumulative distribution where I could type either true or a one. So I'm going to type a one, close it up. And then I'm going to say that this is a percent adding some decimals. So there we have it. So it gets a little bit more complex. If you were to say, for example, you want between between, let's say between two and five people between two and five. So I could once again go over here and say, OK, well, if I'm going to say this equals the sum of between two and five. Now if you're not including two or five, then I would say that would be either three or four. So you got to be kind of careful on where the starting point is. Are we saying equal to and including two and five or between, you know, the two and the five. So make that a percent. And then we could do that with a formula. And if I did that with a formula, notice what I kind of have to do is I've got to take the Poisson distribution for the upper limit, which in this case, the upper limit being five. But note, I'm not including five in this case. So I'm actually going up to and including four and then subtracting out that lower amount, which is going to be up to and including the two. And that will leave me with just the three and the four, the middle points, which is what we're looking at. So it looks something like this equals Poisson dot dist and that pick up the X. So the first X is going to be the upper limit, not five, but four because that's what's going to be included in it because it has to be below five comma. Then we'll pick up the mean 2.73 again. And then comma, it has to be cumulative up to that point. So then I can subtract out the lower bit. So we're going to say it's going to be cumulative, which is either type in true or put a one and then I'll close that up and then subtract out another Poisson dot dist.  So up to and including two is what I got to subtract out this bit over here and then comma. The mean is still 2.73 comma. This also needs to be cumulative. And therefore I can type in true or I can put a number one close up the brackets, enter, check it out, home tab, number group, percentify, adding some decimals. I could add decimals here if I wanted to. And so those are the ways that we can calculate. So those are the kinds of questions that we can then ask ourselves. What's the likelihood that amount, how many people will arrive within a certain timeframe, either one absolute number, two people arriving within, in this case, the one minute time frame or some combination such as zero, you know, zero to five people or what's the likelihood that, you know, from six to 10 people are going to arrive or sit over six people are going to arrive. Remember down below it's unlikely that you're going to have anything that's going to be way down here, although we're skewed to the right on this curve. So it's theoretically possible that infinity people arrive in a one minute time period, but it's quite unlikely. Right. So if you go six and above, you can kind of sum up everything down here and say, well, what's the likelihood we just get, you know, swamped over something over the six and you can, those are kind of the questions that you can get. And obviously those can be useful for planning purposes. Let's clean it up a little bit. I'm going to then, I'm going to add some blue to our data home tab, font group bucket. I'm going to make this blue and bordered. I'll do that here too. I'll select these control shift down and I'm going to make that border drop down. If you don't have that blue, it's right here in the fill color. I'm going to do that here too. Scroll into the right control shift down. I'm going to make that blue and bordered. I'll make this blue and bordered just to color it the way we normally do. We could actually make these a little skinnier and make column D a little skinnier and maybe I'll select column D home tab format paint it to column I format paint it to column M possibly. And then over here, I might format paint it to column X for skinny X. And then here, let's make this blue and bordered home tab font group border blue. Let's take the skinny X home tab format painter skinny AA to take this data, make that blue selecting these items control shift down. I'm going to make that blue and bordered here. And so we have that by the way, if I total this up equals the sum of this or I can do it this way. Alt equals. Let's do that again. Alt equals. It should come out to one or 100%. That's kind of a check number that we can have. Let's make that percent here. Let's put this as a total. Let's put an underline here, home tab font group underline. And then on the right, let's put some blue around this stuff going to home tab font group border blue. Let's take skinny AA home tab paint brush skinny AI put a skinny AI there. And might as well check the spelling while we're here spelling checkie. And it's good enough. All right.


something, this equals one, then return error E, otherwise continue, right? So this now means that we can keep retrying but for a limited number of times and we can still use the question mark syntax, which is pretty neat. Now in this particular case, we know that it's always the connect that will fail. We don't actually wanna retry like a handshake because we know that if it failed the first time, it's gonna fail again. So the connect is the only thing we really care about. So in this case, I'm actually not gonna do this. I'm gonna do it this way instead, just to make the code a little bit simpler. So we're gonna do TCP is loop this. If it's okay, then we just return, then we break with an S. If it's error with anything and I is less than or equal to five, three, then I equals equals one. Otherwise, if it's an error, then return GZLA works. So this is now, we're gonna keep trying to connect to the given address. And if we connect, then we're just gonna break out of this loop and we're gonna give the stream that we got out. If we get an error, then if we've only gotten a few errors, then we're gonna just keep trying. Like if I is less than three, then we're just gonna increment I and not actually do anything. So we'll go back to the top of loop and try to connect again. However, once we're beyond three errors, then we'll return error from the outer function. And now this does not need to be a loop anymore, but I've messed something up here somewhere. Right, so in theory, this is now gonna retry connecting three times and if it fails, then we get sad. I do think we want to also put a timeout on that connect, maybe. Unix connect. I don't remember whether it's the read timeout or the write timeout for connecting. An unspecified timeout, well then. So it sounds like I can't actually do that. Well, that's fine. All right, so we're just gonna do it this way.  Great. Huh, value moved here in a previous iteration of the loop. Totally true. I'll let you do that. I think I'm allowed to do that. Two socket. All right, let's see. All right, we got some instances. And now of course it's, now we're in the same state as we were before and now it's all these instances have booted up or are booting up. So if we refresh this, it should be two instances here. Ooh, and two old instances. Where did those come from? That's a good question. I'm gonna go with these are the old ones. Terminate. Okay, so these are the machines that are starting up. And so in theory it's now waiting for those to boot and then trying to SSH into them. I guess we can sort of check manually that we're actually able to SSH into them. Hey, how about that? Hostname on the first. So that was one machine set. Date of the second, that's the other machine set. Private IP of both, that's the final closure. How about that? This means that our entire example now all works. And so in theory, of course, we could do things like with multiple servers or with multiple clients. And then this, we should now see date show up three times and hostname show up one time. And then of course, then we would want to print, say, four C in VMs client. And C dot private IP. And of course, this has access to, we do also here have C dot SSH or we could issue SSH commands as well. We're gonna nod through that for now. Well, let's just double check. We're gonna get rid of some of these print lines because technically we could turn these into logging statements. And so there's a great logging crate called sLog that we might even wanna use here. I'm gonna just not do that for now. I guess arguably we should make a git commit as well just to show that we like not lose all our progress. Piles. Yeah, and then terminate all these, this is great. All right, let's make a git up repo for this. So we're gonna make a new repository and we're gonna call it first. It's gonna be a Rust crate for spawning short-lived clusters of EC2 spot instances. So if you wanna see the code, I guess you can go there. We can also do some more like meta stuff about cargo. So sort of what things to put in your cargo Toml use for crates for like managing readmes. Of course, one of the other big things that were, ooh, failed to look up address information. Name or service not known. Yeah, it's gonna be on the connect to. This is when connecting to the clients. So this is DNS lookup taking too long. This is why like all this unwrapped stuff is something we're gonna have to deal with and why many of these operations sort of need to be redone because our transient errors so they're not actually gonna persist with us. So I guess now we have a choice. Let's make a, so things that we wanna do. Failure for getting with scope guard for tearing tear down even during panic. We want to deal with transient, for example, if the API is unreliable or DNS lookups, et cetera. We probably want a nicer SSH interface. So we want to the SH object that we pass to the closures to be like easier to use because currently is a little bit of a pain on cargo infrastructure. So we want Travis, obviously we want cargo and we want all the meta information and we want documentation. Now all of these are pretty important. I think realistically given that we're at like three and a half hours, I'll probably only do some of them this time. And then maybe we'll do like a second video where we go over some of the others. What would be most interesting to do for the people who are here? Oh, that's right, Rayon. Yeah, yeah, yeah, yeah, that's a good point. Rayon for parallel, that's a good call. So I'm happy to deal with any of these and I think all of them are things that we're gonna sort of essentially have to do for this crate. The question is just which ones we want to do first. Looking at the time, I think realistically we'll do one or two of these this time around. Sure, we can do failure first. Failure is a sort of nice self-contained thing. I mean, we can do, okay, so how about we do failure and cargo infrastructure then? The cargo infrastructure is very straightforward in terms of like setting up things up with Travis. If we also want code coverage, it's a little bit more involved, but we can do that too. At least one thing I can point you to is a cargo chart calling. So this is a re-implementation of K-Cov, entirely in Rust and it's very easy to set up with cargo so they give you, especially you copy paste this as your Travis YML file and then it will give you full coverage using either Coveralls or CodeCov depending on which one you want. You can also, if you wanna see an example of a repository that has this, this one for example does, except it's currently zero because Travis is being stupid. Specifically Travis has this bug where you can no longer do code profiling on Travis unless you set pseudo required and this is as a result of their meltdown and spectra mitigations. It's really painful. We also have this on factory RS, I think I added this too. Yeah, so factory RS is the other crate you can look at. Here this has a lot of infrastructure as well for running code coverage. So if you're curious, you could sort of look into that. All right, let's play with failure. Failure is failure. Failure, great. I'm gonna close some other stuff here. All right, so failure, let's add the to do and also let me kill these instances just so we don't end up, ooh. What? It's lying to me. Oh no, there's already a crate named burst? Oh, that's terrible. Well, do we have a better name? I'm happy to rename it. It's not like it's, wait, what is the current crate? Oh, there is a crate named burst. What does it do? A disassembler. Well, that's very different. That's fine. Let's call this something else then. Let's call it, let's see, what does it do? ECT spot blocks for short jobs and clusters. I mean, we could call it clusterfuck, but that would be maybe a bit on the nose.  Oh, tsunami or tide related is good. What's the name of that storm, El Nino? I think that's what it's called. Yeah, but it's not really a storm in and of itself. I mean, to be honest, I'm fine with calling it tsunami. I think tsunami is a good name. Great, let's do it. Tsunami, so you spell it, right? Tsunami, great, set. And I guess we will move burst to be called tsunami. I'm gonna cd to that, and then we're gonna, ooh, I did not like that at all. Normally I'd organize my get-to-mits better than this, but oh yeah, naming things is great. I'm very happy with many of my names, but that's a topic for another day. All right, Tastic. All right, so we're doing failure, huh? Seems appropriate. Okay, so the failure crate, where do we have the failure crate? Here. So we're gonna need cargo tunnel. We're gonna need failure as version 0.1.1. Extreme crate failure. Then we're gonna, okay. So, contrary to what the old error chain crate did, failure does not actually require you to declare your own error type. The idea is essentially that, because errors should be relatively rare, errors are generally just abstracted through this failure error type. So you very rarely have to do like, set up a big enum where you're gonna add stuff to. Now, there was a blog post recently where he suggested some changes. But in general, we're gonna have one enum that's gonna talk about the kind of errors that we can get, and then we're gonna take everything else that comes from other places and just have them be error, right? So in our case, let's, this is getting pretty long, but it's actually not that bad. Okay, but we're gonna have a, let's not have an error mod for now. Enum. We're gonna have an enum called error. I guess this is no longer called burst, is it? So, and burst. So this is gonna be tsunami builder. That sounds pretty, pretty severe, but okay. Why not? I guess the example is gonna be nami builder. Let's see. So, we're gonna have a enum tsunami error. Tsunami does make everything sound a lot more extreme. We're gonna macro use failure. So, there aren't actually that many errors that we want to, that we might provide, because in theory, we could just propagate up the risotto errors. That's a good question. All right, let's hold off on this for now. Because essentially, the problem here is that the underlying crate we're using, the risotto crate, earthquake is also, that's a great, great name for the builder. But see, the problem is getting too carried away with the names to the point where you don't know what anything does anymore. As a users of your library, you have to like read all the documentation in order to understand they can no longer guess based on the names of the types. But earthquake is a great name for that builder. All right, so what we're gonna do is all the things that currently return errors, or I guess none of them return errors, because we just don't wrap everywhere, but run in particular, sort of our main method. And if you look at what, if you look at here, the error type of failure functions, which accumulate many kinds of errors, should return this type. Great, we're gonna do that, gonna return error. And now the other interesting in this observation is that basically everything, like failure implements into error for most kinds of things. And so this means that we can use the question mark operator for most of these. So default TLS client, you can probably do that. Now, it doesn't really encourage you to do this because if you just put question marks, it means that you don't give an explanation for why this error happened. So your caller is just gonna immediately be given this particular error and not a causal chain for why that error happened in the first place. Like why were we trying to create one? And so the way you do this is, well, there are a couple of different ways. You can use context. Yeah, so if you see here, if you have a lower level error, such as IO error, you can provide additional context about what that error means, the context of your function. Right? Six, eight type. Yeah, that's fine. So from memory, I have to, it's like make life a little bit easier for ourselves. Is it better to use expect if possible? So expect is basically unwrap. The only difference with expect is that if the unwrap fails, you're given a more helpful message than was none. So we don't actually want to use expect either because it's still panics, right? The whole goal is that we want this to not panic. I wonder if they've given an example of this. Yes, you see, if you return a result that returns a failure error, then you can just use question marks everywhere. But at the same time, that's not really what we want to do because we want to provide this context, right? So this is why I want, I think what we have to do is map error from. So this maps it into a failure error. And then we can say dot map error, which takes the E. Right, yeah. And then with that E, we can now do E.context and then we can do a question mark. So notice that the code does become a little bit more verbose, but keep in mind that we could do this, right? That is totally legal, but it means that the error is not particularly helpful to our caller because they're just gonna get like, couldn't create default TLS client with no reason for why. Whereas this way, so context takes anything that implements display. So we can like give it a static string that should be fine. And now we can tell it, do they want this to be active voice? It's not terribly important, but fail to create TLS session or EC2 API client. And now let's just do a cargo check. So cargo check is a much faster way of compiling that just gives you the errors and does not actually compile your program. And of course we can keep doing this down. So where else do we have unwraps? This to do, we're also really gonna have to do it because otherwise it's not gonna run on anyone's machine but my own. Create, yeah, good bunch. Good catch. Yeah, because currently this will only work on my setup. So that's, we should have that to do. Construct VPC for cluster set of new key. This one's actually pretty important because otherwise only I can run this great. But you know, it's fine for now. I would have been faster just to release compile actually. The check in general is a lot faster. It's just in this particular case it had to recompile all the dependencies and that's not super happy. Let's see unwrap. Yeah, so here I guess we should think about how can this fail? So the, actually maybe the AWS API. Let's see what it says. EC2 API reference, fantastic. List of actions by function. Aphabetical list of API actions, sounds great. Request, thanks Amazon, super helpful. Yeah, so sorry, the video gets laggy whenever I compile a lot because it takes up all my cores. I thought I'd fix this, but apparently not. In theory it shouldn't be using all my cores but it looks like it is. So yeah, it's just whenever I do a big compile. Let's see. Oh, right. Run, we can't actually, we don't actually want it to return a failure error. We want it to result nothing failure error because it could also not crash. All right, so let's see what happens if I do this. Now it's gonna say, right. So this function, of course, if everything goes according to plan, it returns an okay. Great, so see how this code compiled? So notice that the mapping of errors we have to do error is a little bit painful, but it's not like, this is basically exactly the extra context we need to give it. Now I think, just from memory, I don't know if this is actually true, but the macros that provides yeah is this thing, format error. Yeah, let's just, if you wanna construct an error from scratch, it doesn't actually help me chain them. So I guess, ooh, that's a good question. Yeah, so context implements fail, right, yep. Okay, I think we're just gonna keep it this way. Right, so we're looking for unwraps. So that's why we were looking to error codes. So these are all the ways in which Amazon things can fail. Makes me a little bit sad. Cause realistically, most of these we're expecting not to see, right. Cause we're using a library that should format the API request the right way. There are some that can fail like off failure, which because of the way the API structure, we're not actually given an error here when we create the client. We would be given an error on every single API request. So one way you could imagine this happening is like, if you have a user that's authorized to do some actions, but not others, that's why you can't just do it on connect. I don't think there are any of these that, specifically I don't think there are any we wanna catch here. So the only ones would be like, the things we're looking for are transient errors, right. If you would find a transient error, we don't want the entire run function to exit. But it's probably fine here. So this one we're gonna do, I guess we should get used to just keeping this around. This really should have a, right. So could not, I guess we could do format. Could be even nicer to the user and say for this group. Where else do we have unwrap? Okay, so this unwrap is different. So here, this is the response for, which is this request spot instance. Quests, spot instance, instances. Yeah, so this method, the thing that's returned, notice how it has an option VEC of spot instance requests. If we look at the Amazon API for that method, notice how the response says one or more spot instance requests. So not only will it always be some, it will always be a vector of length one or greater. So in fact, here, this unwrap is fine, but as was pointed out elsewhere, like it's better for us than you'd expect and say request spot instances should always return or something. Sect phrasing is not super important. Oh, I missed a, and then we're essentially just gonna walk looking for unwraps. So here, we sort of already handle errors. So this one is a little bit special. So remember, this is where we want to retry in the particular case of it being the race message. So this error is, if we try to describe a spot instance request that we just issued but that Amazon hasn't registered yet, then we don't actually want to return an error straight away. So here, what we're gonna do is we're gonna do this, continue and else we're gonna return error from E and then map it with a complex. And in fact, no, we don't even have to do that. We can just do this and then we can just do, right? To see what this is doing. So if we have to do this so that we can do string matching on the result because the error type of result are not well-formed. And then we do some matching. And if it's not one of those, then we reuse the original error we were given and propagate that up with some context of fail to, fail to, sure, why not? Here, we have a slightly different issue which is that this loop could loop forever because what if these enter a failure mode, right? So remember how the state, if we look at describe, spot instance requests. Yeah, so the response we get back is responses and request object and the state is one of these, right? So currently, we're just waiting for them all to become active, but it could be that they never enter the active state. And if that's the case, then of course it's not good for us. So what we're gonna do here is we're gonna say that if any of them failed, I think it's the way to do this. Yeah, well, okay, so first of all, this unwrap is safe, right? Because we already checked for the error case above. So this, we can just leave that way, that's fine. This unwrap is safe because describe, if we look back at the describe here, this also always returns one or more spot instances requests. So describe always returns at least one spot instance. This is okay because this, ooh, required no. What does that mean? Oh, this isn't the thing we sent to the server. So I think this means that we can expect this to always be some coming back from the server. Like Amazon will always tell us the state of something we asked it to describe. But if we send a spot instance request to them, it doesn't have to contain a state, obviously. So I think this is just gonna be a spot request that did not have state specified, which like should never happen. So I guess really what we want here is, we wanna know if they're all active. If they're not all active, first of all, I'm gonna take the suggestion of someone else from further up in the thread.  But also we're gonna see whether any of them failed. So I guess we're gonna do, we have to do this again. That's a little bit sad. But here, it's actually okay for us to use unwrap because if that unwrap would have failed, it would have failed with the expect further up, right? So these unwraps are fine. And then we wanna see whether any of them are failed. And I wanna see whether state is equal to failed or state is equal to canceled. Because if any of them are failed or canceled, then of course there's no point in us continuing any bad. And so if any of them are bad, there's no reason for us to continue any of these so we can break it, right? So I don't know if you remember, this is a long time ago, but this instance is VEC that is actually empty and unassigned to. And that's okay because when we break from this loop, remember how we know that at this break, which was the only break in the loop, instances would be defined. And so therefore it's okay for us to have this not be mutable. This means that down here, we are required to set instances before breaking, which has its own set of problems because I think what we're gonna run into here, what we're gonna run into here is that imagine that we issued two spot requests, one for the servers and one for the clients, right? And the server starts up fine and the client fails to start. If that's the case, then we still wanna terminate the server when we give up, right? So I think this will actually be wrong. I think really what we're looking for is any pending. So if any of them are open, which is funny, because this is going back to what we originally had. So if any of them are open, specifically what we want here is if the state, only if the state is active, do we record this instance as something we need to deal with? Right? If it's not active, then there is no instance to terminate. So notice the filter map here, right? So now what this is gonna do is it's gonna keep looping through this loop of describing all of the spot requests until all of them are either in a case of active or in the case of having been failed or canceled. All the ones that are active will have collected the instance IDs for and all the ones that are not active, we will not have done so with. And so this means that the one thing we do have to track is whether any of them failed. Cause if they did, there's no reason for us to continue. Like there's no reason for us to do any of the actual machine setup, right? We should just go straight to terminating. So what we're gonna do here is, I'm gonna have a variable. It's gonna be, let me, all active. All active is true. And if any of them are not active, then all active is false, right? And then what we sort of wanna do is put all of step four and step five, the things where we actually execute the closures, put like all of that inside an if, but that's gonna make our code go far to the right and that's sad. So instead, I wonder, should we just do spot guard while we're at it? No, I'm gonna not do that. In fact, we can still do four. It's just a little bit silly. So we're just gonna say, if all active we do these, there are cleaner ways to do this, but they require a bit more code, so I'm just not gonna do them. So now notice that only if all of the machines came up correctly, only then do we actually do this like SSH business. Otherwise, we just like terminate them once they're all ready. It does mean that we sort of, we wait for them to come up, but this process is fairly inefficient. It's already fairly fast, so it doesn't really matter. So now we're gonna keep hunting for unwraps. I'm just gonna bring with us these two lines. Okay, so this unwrap we know is okay, because it's the same unwrap as above there. This unwrap we know is okay, because it's the same unwrap as above. So unwraps, okay, because they are the same as expects above. So here, this unwrap spot request must have spot request ID. Seems pretty reasonable to assume. This is a good point. We guaranteed that it has an instance ID. I'm not sure. That's a very good question. So this is one of the things that, why I'm happy that rust, like discourages unwrap, because once you start walking through these, you're like, I don't actually know why this unwrap is okay. So let's divide these up. So this one is okay, because every spot request is ID is Regis is made for some machine set, right? Machine set, right? So this is in the ID to name mapping, we know that we mapped every request that we made to a name. So we know that we're gonna get one out when we look up any given request ID. But this one is really tricky. This is trying to find the instance ID of this particular machine. So the question is, why is that unwrap okay? So this is, we're making a request to the AWS API. We told it to boot up a machine, or we gave it a spot request. It came back to us and say that spot request is now active. Are we guaranteed in the API response we get that that thing that says that it's active includes an instance ID? I think we are, but I'm not sure. It's a very good question. I think what we're gonna do we're gonna consider anything where it's active and the instance ID is not yet set as pending as well, which is gonna give us this behavior for free, right? So any where it's open or where it's active, let's say it's a map this to sir.state.as I expect. Now this is the state, now this is. So if the state is open, then of course it's pending or if the state is active or the state is active and the instance ID is none, then it's also pending, right? So now we're gonna keep issuing this until either, either, or for all of the spot request, either they are fulfilled and all of them have instance IDs or they've failed in some way. And now we can say here that now we know that if it is active, then this must be some, okay? Because active implies instance ID.isSum because not any pending. That make sense? It's a little bit elaborate, but this is what you end up doing if you try to like fix all the unwraps in your program. Here, this unwrap is okay. So this is just we're reusing the spot instance requests from the previous requests that we made so that we don't have to clone all of them. And that should be fine because we're the only people who can modify that request. So we set this to sum above. So that just can't fail. Cancel spot instance request though can fail. And this one is a little bit trickier. I guess we can probably just do this. But this is one of those things where if there's a transient failure here, it makes us really sad, right? Because if that call fails, we won't cancel the request that we made. It should be unfortunate. But all right, we're just gonna keep that as a cancel for now. Fail to cancel. It's also like if we fail to do this, maybe we want to keep running the experiment. But I'm gonna ignore that for now. I'm also gonna add it to do here where this is where we create the scope guard. So if we wanted to have a thing that if we ever panicked, we would terminate all the instances. At this point, all the instances have been started.  That would essentially be all the code that's in six would become the code that goes inside the scope guard and is executed when the scope guard goes out of scope. Where else do we have unwraps? Describe instances. Is the compiler smart enough to lie jumps in some of these unwrap cases? If you can work out that it won't ever panic, then I assume in many cases the compiler can do so too. I'm not sure. I know that there's some discussion about introducing a new macro for this. You could also imagine that you did something like, essentially an unwrap is if some, then return the thing, otherwise unreachable. The problem is that unreachable is actually a bunch of code. Unreachable does a print line and then terminates your program or does stack unwinding.  I don't know whether the compiler is smart enough to realize that that particular path can never be taken. In, for example, in the cases we have of these sums, yeah, I don't know, it doesn't sound unreasonable because I don't think this is particularly hidden information. Some of them are a little subtle. Like the things that depend on like the exact semantics of the AWS API. If it doesn't, that sounds like something that ideally should be fixed. Let's see, fail to cancel spot instances. This might seem redundant, by the way, giving this context, but it's because the error that you get back from the EC2 API, so the describe instances error, is not guaranteed to say anything about it being related to canceling spot instances. Now this unwrap here, so this is in describe instances, I think that's returned from this as an array of reservation. It's a very good question. So here's another one, right? If we describe instances, what we get back is a option VEC of reservations. Can that option ever be none?  So this is a little bit tricky, this is basically saying that if I got no reservations, then just like skip this one. Cause it's saying like allocate a new empty vector and then iterate over it. So this means that we don't have to deal with this unwrap case. And then I can actually do the same thing here. Like if it is none, we think that's weird or if the vector is empty, we think that's weird, but it's not really an error per se. The one thing we might want to do though, is if, let's see. So I wanna keep track of, pretty silly. Let's do it this way maybe. No, it's not worth it. So we sort of wanna assert here that number of machines in each group, number of instances in each set is the same as requested, right? Because it could very well be that if the API here is being stupid and like we request three instances and we get back one, then there's nothing that really checks that in our code currently, right? Cause we just iterate over the things we're giving back and like put them into a vector somewhere. But we sort of want this to do here cause otherwise like if the user requested 12 clients for a benchmark and we give them two, that seems bad. All right, here. Oh, this is gonna be all sorts of painful, but actually not though. So here, this is we try, ooh, we're gonna do this though. Failed to SSH, this machine. See how helpful we can be? Do this, machine.public.ds. That's kind of nice, huh? All right, so now we're gonna try to SSH to all the machines. And if we fail to connect to one, we're gonna print an error saying we failed to connect with. Now here, this is a little bit tricky because if something fails here, we really want to run this code down here. But again, that's gonna be fixed by the scope guard. But it also means that we're not gonna run the closure for any of the other machines, which is maybe fine. So I guess returning an error here is okay. And then this is the user's closure. So remember how we tell the user to give us something that can return an error? What we really want to say is that the user can give us anything that returns an error, right? And in fact, the same for this and the same for, actually, no, it's not even error. That's a good question. If I take a closure, does it want me to use fail? Cause that's what I think it does. Yeah, ideally we should use like impole fail here, but because we're trying to be all work on stable, we're gonna use error. So this is saying that the closure the user gives us is allowed to return any kind of error as defined by the failure error type. We could here also make it slightly easier to write, I guess this should really be result nothing error. We could be nicer here and do something like allow the user to have anything that implements into error. Because that way you return like an error. There's just a string. Ooh, in fact, but then it would have to be generic. I want it to be generic. Yeah, we're just gonna leave it this way for now. This could be made more ergonomic. Like this could be impole error or impole into error. Which is like, give me any closure that returns something whose error type implements into error. Cause that's really what we want to say, right? Like anything that, your error can be anything that we can turn into an error, which includes things like strings. But we're gonna keep it simple for now. So this means that what this gives us back is a result error, which of course means that if it is an error, then we don't even need to map the error, but we do want to add a context to it. So we're gonna map, we're gonna map error and then question mark. And the context here is gonna be a setup procedure for this machine, or for this type of machine failed. And then we're gonna give the name of that group. And then of course we get to the master closure the one that determines the whole, like what to run at the end of the benchmark. And that thing is now gonna be a closure that returns a result also error, if there are any more. And here we're gonna do main procedure failed. And I guess we could say tsunami failed. But and termination can fail. Yeah, that's true. I'll be really sad. Yeah, so here we're gonna do the same thing as we did further up where we're gonna retry if the stream was disconnected. Otherwise, we're gonna return, yeah, we're gonna return an error of error from E.context. This don't need format there. You're totally right. Yeah, cool. Let's see. So here this is just gonna be fail to terminate instances. Tsunami instances. And this unwrap is okay because set to some further up. So this is the same argument that we're reusing the instance IDs from the description request. And that description request that we made up here, we set to some and it's never modified below. Yeah, thanks.  All right, there are now that unwrap is okay. That unwrap is okay. That unwrap is okay. This is not really an unwrap. This is not really an unwrap. This is the same unwrap as above. And so there are no more unwraps. Great. And then I guess we had a few in here that we're gonna get rid of. So use failure error, result error. Here we're gonna return error from e.context. And the context here is gonna be fail to connect to SSH port. And remember here we're gonna get this really nice chain of errors, right? Where the TCP stream connect is gonna return as an IO error. We're gonna wrap that in a message that says fail to connect to SSH port, which again is gonna be wrapped in an error to say, fail to SSH to this particular machine, right? So we get this back trace of errors, which is one of the really nice things that failure gives you. I still don't know why session returns this, but I guess we can do a, and then we say to not available. I have no idea why new would fail there. Oh, I guess we also don't need, can I find value source, 149. Oh, sir. Oh, that's giving me a lot of 192. There's no name. Fail to cancel spot instances, which means no format. If you're arriving now, you're a little bit late to the game, but at least the whole video is here. I'll also upload the video on like YouTube or something later. People who wanna watch the whole thing. My guess is we'll end up doing a second part to this. Cause like realistically, I'm not gonna finish the rest of this today. We'll probably do a part two where we go through like, essentially the things in to do, which is gonna include things like, rayon for parallelization, a bit more cargo infrastructure, those kinds of things. 205, reservation, forgot a question mark. 271, forgot a question mark. SSH 19, forgot a question mark. Oh, this map error no longer has to be there. Now that's gonna turn into our other magical map error location. This thing over here, this little monster. So this is not gonna say fail to establish SSH, fail to perform SSH. So this is gonna be fail to authenticate SSH session. Yeah, so this to do we're gonna have to deal with too. Although that will come under this point actually. I guess we should remove that one for now, given that we're dealing with it. Command. Yeah, command is gonna return this in an error. I guess here we could be really nice and do a four command. If we want it to be nice status, it's like maybe we do, I guess. The fail to execute command. This is gonna be never, and I guess to do check channel exit status. How about that compiler? Take that. Ooh, no more IO. Three. Oh, this is where the bail thing comes in. Is that channel session supposed to be after? No, okay, so the way SSH works is you first establish a session, and that session has multiple channels, and on each channel you can execute one command. Or you can spawn a shell in which case you can execute multiple commands. But so we create a new channel for the command we're about to execute, and then we execute a command on that channel. Then we read the response of that command, which I should also have here. And then we wait for the command to finish. Right, yeah, so we were up here. So the question is why does error from, I think it's a compile error. Compile error. Where? Oh, that thing, yay, I pasted too many lines. You're totally right. Okay, so it's not liking this error from. Interesting. Can I just like make up a cause? Apparently not. Can I make up a context? Great. We're just gonna do context new. Let's see, 132. That's not what line 132 is. Oh, right. It doesn't realize that the question mark forces the method to return. We'll do this, which is the same thing. Oh, my side of the error, let me do that. Great. And then 120, never read. All active is never read. That's interesting. Oh, that's true because all active is only assigned later. Yeah, okay, so that's fine. We can just not assign this of all you. I like the structure of response. Yeah, I agree with you actually. I think, I do think the structure, so think of this, for example, where we sort of started everything just in one file and it mostly is still in one file, but it just like has a nice flow to it, I think. Right, so this all ran. So now let's see if we can run our example and how that still works. Oh, it's compiling all the extra dependencies for the failure grade. For some errors, I think the AWS API is gonna give us play and give errors just from observing it so far. I don't think we need to invent our own. Watch it like crash on the first line or something silly. Our example is now gonna, so the one thing that's a little bit awkward about this is that now nothing is ever gonna crash. We actually need to do an unwrap here. That's the only point at which errors will be exposed because nothing else is gonna panic, right? They'll just keep property getting errors. This is why in normal Rust programs, if you only use the question mark operator, it's actually really annoying to figure out where that error started from because they just keep like re-throwing the error but you don't keep track of a back trace whereas adding this context sort of forces the context of forces that step of the back trace to be kept, which is really nice. So this unwrap will in theory actually tell us where the error occurred. Well, let's see what happens. Slowly, but surely. This ended up being longer than I anticipated but at the same time, it's fun. Come on, compiler. You think we can implement scope guard while it's compiling? We'll try, I think we can do it. Let's see. Okay, so scope guard is basically you make a scope guard. You give it a closure and that closure will be called when that scope guard goes out of scope even if there's a panic, which is really nice. So we're gonna, over here, we're gonna do scope guard new. Probably a good idea to not do anything while compiling. Ah, it's fine. It's just compiling dependencies. Compiling this crate does not actually really a problem. Oh, I see what you mean. Yeah, that's fair. I really wanted it to, what's the thing for making it not use cores? Let's see if I can, here. Let's see if this helps. Dash C03. Ooh. How's this? Is that, is the stream now still laggy? Yeah, probably. So in theory, this should like only run cargo on two cores. So maybe that's better. Okay, it's a little bit weird. I'm not entirely sure why it's better because it still seems to be trying to use all my cores. But, yeah, that seems to be better. Why is it spawning up multiple instances of cargo? That seems wrong. All right, great. We'll try with that command and see if that makes everything happier. Twitch is very confused about the quality of my stream. All right, I'll try to resist the programming. Is the audio fine though? Cause then I can sort of talk through what the plan is to do. That's fine. I just really want to implement scope guard, you see, but I'll resist, that's fine. The audio has always been fine. Okay, that's good. I also wonder whether, so I'm also recording the stream. So I wonder whether the recording will also be laggy. I guess we'll find out. Why is it recompiling risotto? That does not make a lot of sense. So the idea with scope guard at least is to use the scope guard defer, which, yeah, I think this helped in scoping the video stream. So the defer keyword for scope guard is essentially very similar to what you get for the defer keyword in go. So you can do this. You can declare a scope. I think there's example here somewhere, which might be more helpful to watch. Yeah, so you declare a scope and then inside that scope, you declare something that's gonna happen at the end. And in our case, really what we want to happen is this entire thing, right? Like if everything ever ends, then we want to terminate all of these instances. We want to stop the spot request immediately, right? So here we want to terminate all instances whenever we exit, even on panic, right? Oh, I guess that's running now. That's, well, that's fine. It'll do something. It's fine. You must CTL cargo check. Yeah, this is gonna get sad, isn't it? So I think we're gonna, basically what happens is we need a separate instance of the instance IDs for termination to use now. So it used to reuse the ones from the describe. So we're just gonna do, well, that's promising. That looks like it's SSHing. Here we're gonna do this even. So I think I've read about unwrapped too. How about that? So this is with one server, three clients. And notice we get one server that prints its hostname, three clients to print the date. And then we get the private IP of the server and the private IP of each of the clients. No errors though. So we didn't actually get to show any of the work that we did on errors, but hey, it's fine. Let's see. So this is probably gonna be awkward for a couple of reasons. I don't actually use the scope card. Oh, that's right. I can't, mm. Yeah, I'm just gonna not do the scope card stuff now because it's gonna distract us. Okay, so we're gonna do this and we're gonna do cargo, we're gonna do that. All right, but this is a pretty good result. Let's run it again and see if it's still, whether we might get it to give us an error. So that'd be nice. But notice how painless it was to add all the failure stuff. It's really just walking your crate and looking for unwraps and then turning them into this like, I mean, normally you can just like add a question mark. In reality, what you usually want to do is you want to add some context to it, but it's not technically required. But I think this puts us in a pretty good spot. Like now we have context for all our errors. So in theory, even if something goes wrong, we'll get useful error output, right? It is true that we still want to do this like deferred scope for execution because currently if something goes bad, I have to manually go into this list and kill the servers. But in practice, like we have pretty decent error handling now, I think. So let's just see what happens if it runs one more time. Let's see. Oh, that's the other thing we wanted to add for it to do logging. So just to remind all of us, this is the client program that we're trying to write. So it spins up a server that's gonna run from one AMI on an M5 large. So we're gonna run one of them and we're gonna just print the host name. And then we're gonna run another machine set that's gonna be sort of named the clients. We're gonna run three of those. We're gonna run on the same AMI with the same machine setup and they're gonna print the date. Very, very fancy. And then that's just gonna be the setup for those different classes of machines. Once all of the classes of machines have been set up, then we're gonna run this closure. It's gonna be given access to all of the machines from all of the types and specifically they have access to a .ssh on a machine and .ssh is an SSH connection that's already been established. Hey, it worked again. There are no errors anymore. It's pretty frustrating. And you'll be able to SSH in and like do things on all of these machines. And then once this run closure returns, then we're all done. Any ideas when you will stream next time? I'm not entirely sure. My guess is the next stream will also be a decent amount shorter just because now we have most of the infrastructure in place. So most of these are sort of more about how do you polish a crate? Which may be more interesting actually for those of you who have more experience with writing crates already. But my guess would be in like a week's time probably. I'll post on Twitter again when I've determined when the time will be. But hopefully in like not too long. I think that's like all I wanted to do for this time round. Let's just check that like everything's cleaned up. Thanks to you all for like joining me and having fun. I hope this was useful in some way. And then I'll post somewhere and like an ounce when I decide to do another stream. Also, ooh, that's right. I will push this all of the error. All right, I will also post a... Oh, sorry, yeah. My Twitter handle is johnhoo, like so. I will also post the recording of this on YouTube for those of you who like missed the beginning. I'll probably archive the Twitch stream as well, which I think should also save the video. But that way we'll sort of have both. And also if you have any feedback about today, like I would really like to hear it whether you post it here or whether you post it like to me on Twitter or like however you wanna get it to me, that's fine. This is like the first time I'm doing a live code stream. So if you feel like something could be better or it could be different or something you would like to see or something you feel like I did too much, feel free to like let me know. Yeah, cool. Thanks for hanging out with me and writing code. It was fun. So long, farewell.