 Hello everyone, let's do a mic check first just to see that like sound and picture are working, okay? If you can just like type in the chat that you can hear me Just because we've had some problems with in the past It's annoying to get 15 minutes in and then turns out no one could hear anything. I said perfect. Thank you so much all right, so Today we're gonna do something slightly different. So I am I Asked on Twitter what people would prefer to see and it seemed like the overwhelming response was that you wanted to see Some crate being built start to finish sort of including all those ups in between and so what I've decided to do is This is a sort of neat little wrapper type That you can use for API's that provide services so you want to be able to cancel We can get back to an example with code a little bit later I want to point out that for many of these I as I have a patreon page where I post whenever I have I I have ideas for upcoming streams or if I want to solicit feedback from You who are watching this? so feel free to reach out to me there or on Twitter and I'm happy to like take suggestions for upcoming streams as well All right, so we are going to start a new crate And it's gonna be a library and what are we gonna call it? It's gonna be called Minion and You will hopefully see why and not too long So I'm gonna do the same thing as I've done in past streams of starting up with an example just because I think it's a really useful way to Sort of structure the way your library API is gonna look just by writing the code that would use it in events This is a little bit like test-driven development except we're not actually writing a test It's more that we're writing how we want the code to feel like and then we can flesh out that implementation behind the scenes afterwards So we'll call it basic So the example we're gonna write is going to be fairly simple the general idea that you This library is gonna target is if you want to run Some kind of a service in the background. So usually a service is something that looks like it has an infinite loop and then it like fetch Some work and then do some work and then loops So it just like sits in a loop and just keeps accepting more work and keeps doing it the most traditional example of this of course is that you do something like up here you have a tcp listener or something Listener mind Whatever it's not terribly important what this actually is But it's some kind of TCP screen and if you have some kind of TCP stream you then in a loop do This is gonna be You do a listener accept Probably with a question mark and then you like do some work over the Yeah, you did make it in time. Well done So Yeah, so the idea is that you loop you accept some work and then you do some work over that stream so in this case for example, you might If we wanted a very naive kind of library, it would do something like this Where it just spins off a new thread and that's gonna do some work on that stream Right so Something like this you see pretty commonly in libraries. It doesn't happen just for networks Like you can get this for inside your like accepting work like this is a worker pool or it could be you are like piping a file or You're waiting on you're like a web server that Has long running connections and you want to keep accepting more data from the client now The problem with this particular pattern is what if so imagine that this is some like run Main Or something like it's a function that you have somewhere in your library or in your Program for that matter like you're running a web server and this is like your main handling loop So if you have something like this The issue comes with what if the user presses control C? So control C will terminate or should ideally terminate the current process now You could of course just kill that process But ideally you want some nice way of shutting down Let's say you have to close some database connections or something right so the question is Imagine that in main what we did was we did some setup and then we call run main loop Okay, great, but the problem here is how can we tell the main loop that we wanted to exit? Currently there's no way for us to do that Vim color scheme. This is a base 16 atelier dune my whole Vim config and stuff is on Github under John who's configs so you can see it all there Yes, if you have a program that looks a little bit like that You now don't really have a way of cancelling this main loop Now there are a couple of ways you could get around this for example. You could have like You could have while let's stream is equal to that And then in here you could like pass in the listener here and then do some funky You could like listener dot clone here You spin up this in its own thread and then you like close the stream It becomes really hairy very quickly. We don't really want users to have to deal with this So what we're going to do is we're going to write a wrapper type. That's going to allow people to have a A neat way to cancel long-running functions like this and the basic way it's going to look is We're going to have a trait and that trait is going to be something like Cancelable And if something is cancelable all it has to implement is it has to implement a run method that run method is going to take a Essentially a pointer to a Boolean we are going to have that be an atomic boom. I think in this case Running and it can return any type Any error type And so that's all you should have to implement. I guess this is going to be it's going to have some type error And it has to return self error So the basic idea is that if you wanted to have your service be cancelable You're going to implement this trait and this run method is going to be similar to our run main loop So you'd have something like impulse Cancelable for some food type you would implement run and What you would do in the run method is you take this keep running this atomic and Instead of just having a straight loop keyword. We're going to have something like while keep running sing So while That Boolean is true. So while keep running is true then do what you used to be doing Like so So that's the basic interface like if you want something to be cancelable You'll have to implement this run method and you will have to take this atomic Boole and then in theory what you should be able to do in main is you can either do something like Say we have a foo here And you could either do foo dot Run here say that was the name of one method and what run here would do notice that it takes no arguments run here Internally what our library would do is it would call this? This run method with keep running always set to true Sorry always yeah always set to true so it would never become false and so the return type of So for our trait we're gonna provide a run here method That's going to return something like a result Never and self-error Right, so this would be if you want to run something on the current thread Then you can call this method the exclamation mark here is this method will never return well this This type will never be returned so in this case what we're saying is that this method will only return if there's an error It's a pretty neat pattern So we would use run here something you can use if you want to execute on the current thread and if you're executing on the current thread if you think about that for a second There is no way for you to cancel something that's running on the current thread from the current thread right because the if this the thread that calls this so the the main thread Calls this method enters this loop and while it is in this loop It has no way of also trying to cancel itself, right? So this is why run here would never return and so this method would just sort of end there the other method we want to provide is something like Run in the background now run in background is gonna have to return some kind of handle and We'll figure out what that handle is later but the basic idea would be something like if I did food off run in Background over here the handle that gets returned what this will essentially do is it will spin up a new thread So similar to what we had in the example code earlier Spin up a new thread that will run this this main loop Just normally we'll just call this this function, but then on handle we're gonna have two different methods We're gonna have h.kill or maybe end Unclear what it will be called and then we'll have h.wait and the idea is that h.end is gonna essentially set Keep running to false internally and that's gonna cause the thread that we spun up that is executing this loop It's gonna make that thread eventually exit and wait is going to return result either Nothing if it terminate correctly or self error So the idea here of course would be that in main you would spin up the service that runs in the background Then at some point if you receive a command from the user you would call H.end like you would you would You would call this function to say I want the main loop to terminate and then you would wait for it Or alternatively if you You could for example have one thread that's like waiting for signals to exit So we basically want h to be cloned So if h is cloned it means that we can give one handle to the thread that might Terminate us because the user press control C or it detects that the moon is in the right face And so therefore the program should exit Whereas in mains that would mean that there's some thread spawn Some thread spawn that might call h.end whereas in main We will just call wait. So main is just gonna spin up this this main loop and then it's gonna wait for it Does that roughly make sense? Too bad the never type wasn't stabilized in 126. So the never type is nice But I feel like exclamation mark works just as well at least in this particular case But never is a little bit nicer. I agree Okay, so that's the basic setup this example won't actually compile at the moment and that's fine It's more I want to illustrate What it is that we're gonna what we're targeting Does the use case sort of makes sense just before we start moving on to implementation so Source lib no test yet. So we are going to need this trait There's of course some question of what we're gonna call it Cancelable is a sort of very trait like name. Let's get very rusty trait like name And I think that's what we'll stick with for now There are going to be some requirements of this so Run is just gonna take this atomic pool. That's all we require of it run here runs on the current thread And it's going to be given Oh, that's a good question. I think we're gonna have it take a new self So this could technically consume self But I think we want to mute self because it might be that the user wants to restart the loop if something errors It's also unclear that run here is really the method what we want the method to be called But run here doesn't actually we're gonna have to provide some implementation here Run in background those interesting, so let's call this something else. Let's call this spawn It also takes a mute Spawn in fact has to take a self Because it's gonna move the thing that we're gonna run onto a different thread and we can't move a Mutable reference I'm so used to the camera being there, so I keep looking there to talk However, if you want to spawn then there are a couple of requirements for this right Specifically the Self type has to be send for that to work if self is not send then we can't send self to a different thread We can't give away a reference to self or in this case the ownership of self to the thread that we spin up the main loop on In addition, we also require that Self error is send because otherwise we can't send the error back from the thread And so we will acquire both of those traits What is it complaining about kind of find handle cannot the exclamation type is experimental Really? Oh, I'm not allowed to use it I guess that's what you were referring to Well, we can deal with that later Let's open up a new shell to this cargo check I guess we will Use nightly for now then it's a little bit sad, but I See what you were renewed by the never type so to me exclamation and never sort of different because exclamation is already standardized I just It's only standardized as a normal return volume. So you can do this Like that's fine, but using it as a type in here is what is referred to as a never type So I guess that's the that was the point you were making and you're totally right Right so run here is just going to take a mute self It's gonna never return. Okay, because in theory this this main loop should never return It's unclear whether we want to require that we might want to think about that Okay, so we're gonna need this struct handle it's gonna be a pub struct and it's going to parameterized over the error type Where are sent and so that's gonna contain some fields and on handle And on handle we're going to have two methods so we want weight which Consumes the handle and returns a result that is either Nothing because it because the loop terminated successfully because it was we told it to exit Or it returns the error So that's gonna require some implementation and Here we will also need a I guess terminate we'll want this to be That's probably gonna come back to bite us And terminate is not going to return anything it doesn't even have need to consume self it can just be a self actually probably And it will have to do something So we want the user to be able to call terminate multiple times because who knows there might be multiple user signals or so And calling terminate multiple times should not actually be a problem. It shouldn't even be an error because the Terminate is sort of an item potent operation Right, so if you call it once and then you call it again It has the same meaning the first and the second time and calling it a second time doesn't change anything All right, so that's gonna be the the rough setup of our crate. We will also want the nine missing dogs Because this is a library after all but I will comment that out just now for development and This type will be public. Okay, so the basic idea that we have and that we've Sort of talked about already is that we're going to have this atomic pool And that atomic pool is going to be the way in which the run method gets told when it should exit This means that the handle, so let's see so run here is basically just gonna call self thought run There's very little more to it. We do have to make an atomic pool. So in this case Keep running is comic You It is going to call that keep running This is where we sort of have to Make a choice. So either we could have the result to be the never type like here to say that the The inner run method will never exit I guess we would do this So we could totally have this be the method signature But you can imagine that there are some there are some situations where the service might actually terminate normally as well And there's a question of do we want to support that use case or not? Mmm, and I think the way to go here in part because of the fact that the never type is nightly only and in part because If you have something that is allowed to return then the the callers of the main function that the user writes Could just wrap that in a loop if they wanted to to keep on going even if it exited so I think actually we will just want this Which is nice because it means that we don't need the never type anymore So this is just gonna call run with Keep running. Okay, so run here is very straightforward, right because it's basically just calling the function We happen to know that keep running will never turn false in this case But that's not something run does we just want to have the the user as a main should not need to have to call Run with an argument itself In fact, maybe run here should be given a Better name maybe this should be run and this should be like main loop Or That's another question So one thing we could do is we could also I Heard rust is the future. It's true. It's true. You heard right So what one option here is actually that we do the loop and just have the user give us a Sort of the stuff that's inside of the loop. So if you think of our example Currently, we have the user write this code right We could totally have that that outer loop be something that we specify and then have the user just define What should happen on every iteration of the loop? That's not a bad idea Because that saves a bunch of boilerplate here, right? So this would go into like the Into foo new would contain this line right and then this method would now Just look like this Which is a lot nicer and wouldn't even need to take this so there's no reason to expose the fact that There's no reason to even expose the fact that there's an atomic bull going on here. I like that I think that's a lot nicer Have them pass in a closure. I wasn't even thinking of having them pass in a closure I was thinking of they just defined the The run method on the trait and we call so instead of having this be run We could call this like for each or something. So this we will call Ones for each iteration of the loop So like a loop body Right. It is a little bit weird of an interface just because like people won't be Expecting the code to look like this. Maybe this they sort of like to be able to manage the loop so you could imagine something like I might want my loop to be something like Like I might want to terminate early if I get a particular bite from the client Like if the client sends me a null bite, then I know that they finish and then I want to be able to return. Oh Okay, so here's I think the way to go with this We have Okay, so we have this be something like for each and what we're going to have it return is a loop result and This is going to be parametric over e and it will either be continue or it will be break So this is similar to a pattern that you also see In some other libraries like in the futures libraries, for example, you have this notion of a loop function So if you look at the futures, in fact, we can look at it right now in futures I think it's in the new futures as well. So there is a loop function Which is basically a tail recursive loop So it's pretty similar to what we're doing now actually and if you notice it takes an initial state and a function to call in each time and the function returns a A loop thing which is break or continue And that's a similar thing to what we're going to do except that we don't have to deal with this being asynchronous So for us the loop result is either just going to be continue as I keep looping Or break because there was an error and so now we can have for each return a loop result self error Run is just going to be It's gonna be something like loop Self dot for each so for each is going to be given a self self thought We're going to match on self for each and if it's loop result Continue then we continue I guess we can actually do if let loop result Break with some error E Then we break. Oh, I guess actually the so We could totally write the code this way It has the unfortunate downside that now there's no way for the user to exit without an error So the way we're gonna run now They're basically forced to return an error right because the break is parameterized over the error type and the only options They have or continue So I guess we could have We could have break and error And now we could have a match self thought for each and if we get a loop result Continue then we do nothing if we get a loop result break then we break And if we get a loop result Error with an E then we return Now this is again a result this I Guess we could have break take an okay value do if we wanted to be really nice So you got this take a Output type and an error type Run will return self output or self error if we break then we get some value here Then we return with okay V. Otherwise we return with error E, right? So now semicolon after the Yeah, so the the idea now is the user defines of a function that we're gonna call on each iteration of the loop And they can choose to either continue break with a value or error out with some error value Now This is if you followed sort of internal rust development, you'll see that this is sort of similar to the way generators work In that generators can choose to yield more value values or say that they've finished Hmm, and this is sort of like a it's not quite a generator because we don't yield anything on every iteration We just continue But it's a similar kind of pattern. Okay, so run is just gonna do this It's gonna run the thing a local thread Spawn is where some of the additional complexity is gonna come in so for a spawn We need self to be sent so we can send it to this Look up result missing a parameter now. Yeah, you're right There's also the question of whether output and error should be associated types on cancelable I Think that makes sense I Don't think they should be parameters of cancelable So so that we could do this instead, but I think associated types are the way to go here Let's see Reminds me a bit of norm. Yeah, so this is also a little bit similar to how How you write a parser in norm that you sort of either say that you Successfully got some stuff that you need more bytes or that you failed to parse You will see a lot of there are a lot of programs Not just in rust but in general that end up with a type like this Where you basically want the user to be executing a loop But you are you are the you as the library is the one implementing the loop Let's see All right, so for spawn we need to be able to send self We need to be able to send the error and we now need to be able to send the output And this is going to be self So for spawn this is going to be very very similar we're going to have to do something like get a new handle Actually That's not even true. We're going to have a keep running Which is going to be an atomic ball it is Probably going to have to be under an arc because we need both That's a very long path So we're going to need it to be under an arc because we need both the the thread that calls spawn and the The thread that we spin up to actually run the main loop to both have a reference to this atomic ball One has to be able to write it the other has to be able to read it And so therefore we're going to need an arc around this this And it's going to start out to be true Then we will get a handle to the thread that we're going to join So we are going to need standard thread My rust format doesn't work out. That's a little bit sad And we're going to do a thread spawn move and And Yeah, we're going to need a copy of it. See I really want a good way to clone into the clone into closures because now I need to do this A little bit sad. There's a proposal somewhere Handwriting parsers from scratch. I mean you can do that. You'll probably get it wrong Okay, so in fact, I wonder what happened to that So Speaking of this issue so here notice how I want to I want to keep a I want to send a clone of keep running into thread spawn But that is easier said than done Yeah, this thing Proposals a while ago There's so many cases where you want to do this and there's been a proposal for essentially having Like clone all closures or closures you can say you should clone most of their arguments, but there hasn't really been a good solution yet That's maybe something to watch Anyway, we are going to so I guess we'll do This So we need a handle to the thread that we spawn and the reason for this is Otherwise, we don't have a good way of getting back the return value. So if you look at Threads spawn it returns what's known as a join handle So this join handle business Has a join method and that returns a Thread result so it returns a result where T is the return type of the closure that you Gave to the thread when you started it and so that's the way that we're going to get back the The break or error of the loop now There's also an error to this and this is something that a lot of people don't know and That is if you call join the result you get back the error type of that result is as it says here the Parameter given to panic inside of the thread And we might be able to take advantage of that here. It's a little unclear Yeah, so this part is going to be essentially the same as this So I think this is just going to call self dot run It's not quite this because It actually has to be different And specifically this is the whole point of the cancelable. We want this to be while keep running Download is So we only want to keep keep running while While the user hasn't tried to end the thread And of course the question is then becomes what if the user tells us to exit, what are we going to return? That's actually a good question huh, I think okay, so so the The part that's freaky here is we need to distinguish this is because we added a break value type we need to be able to distinguish between the For each breaking with a value and the loop being terminated because the user asked us to terminate it So I think actually we're going to get rid of the break value For now Because I think it's fairly rare that these things need to return a value and if you wanted to you could just have it Instead of break you could return with an error So this would now break This Okay, and now Now down here if we ever get here we just return with it All right, so we now have a thread to the to the join handle and we're gonna create a new Handle that's gonna have both keep running and the join handle. So it needs keep running so that it can Tell the thread to stop executing And it needs join handle so that it can wait for the result And that is the thing that we are going to return like so So internally in the handle we are then going to have Keep running which is an arc And Let's call it a executor Which is a join handle join handle over result of of Nothing This is going to be that result of nothing and The error type right so the handle has a way to terminate and a way to wait for the result now Let's see wait Actually, let's do terminate first because terminate is a lot easier than wait Terminate is very very straightforward self keep running store false is all we need there so this is This sets keep running to false which means that this loop would eventually finish Which means that this thread would eventually return and so therefore we have now successfully Finished now. There's one thing to note here and we're gonna end up documenting this and that is that if This will only work if for each does not block forever So notice that if for each block forever this We would never get past this this match, which means that we wouldn't get back to check and keep running Which means that we would never terminate. This is sort of similar to what you end up in Tokyo If you were to or any kind of asynchronous IO really if you have a function that blocks You're just like holding up the threads because they have no way of doing sort of cooperative scheduling Which is basically what we're implementing here There are a couple of ways around this so You could have your for each do asynchronous system calls So that way they return if there's nothing to do and you would just loop again The other way to do this would be to have We can maybe play with this later if we have the time is you can send a signal to the To to your own process and sometimes signals can interrupt running or blocking system calls And that would be one way to sort of be a little bit trickier on that I don't really want to implement that work around though because it's not particularly portable We can look that later, but just be aware that that this scheme will only work if we every now and again return from for each specifically when you and we'll document to this on terminate terminate is really The next time you were you wouldn't have fetched more work Return instead It is not like a Terminate what is currently running it turns out that's actually very very difficult to do if you have many threads You can't just like shoot down threads arbitrarily in certainly not in an easy and portable way All right, so wait is a little bit trickier and to see why Remember how handle is clone So join handles are not cloned because if you wait on the thread in one through if you wait on the thread once And then try to wait on it again. The second time wouldn't give you anything useful Mmm, and so here we're going to have to do a little bit of trickery. So either we could have handle Either we could have handle not be clone but that breaks our example right because now Now we can't give away a handle to some thread that's eventually going to call end and call wait in main And so that's a little bit sad If we do implement clone we're gonna have to have something like an atomic option around this and it turns out that in fact there is a There is an atomic option crate whoo Not at all I Can't spell atomic option what Option fine Yeah, so atomic option is is oh You have to do bank rates So Atomic option is pretty nice. It gives you basically exactly what you would you thought you'd get you get a An option that you can let us sink so it only and it only takes ref selves So you can take it or swap it out atomically across multiple threads And so we could totally wrap the joint handle here in an atomic option It would actually have to be an arc atomic option and the joint handle would have to be on the heap Which is a little bit sad The other way around this is to say that if you have a handle You can imagine that we let the user split that handle into a Terminator and a waiter right so on the waiter you can call wait or terminate Oh Here's here's I think what we do So We have a new struct and it's gonna be a It's like a remote handle, but remote handle is a terrible name So bear with me. This is gonna have a keep running but not an executor and on handle You're going to do You're gonna have a method that's gonna be These names are currently terrible this is just a to show you the rough idea of this returns a remote handle And it's really just remote handle Keep running and Then in full key Handle so on remote handle you have a So the reason to do this is now remote handle Which can also be flown, but it's not terribly important now our main would be something like Like exit is H dot get remote and And now we could move Now we can move exit into here right because the exit is sort of devoid from the handle So there's only one handle that you can use to wait and that is the one that you originally used to spawn But there a but you can get additional handles for if you want to be able to terminate separately Mmm But what's a better name for this so ideally I don't really want to call things handle either, but I think This would be something like a canceller Sounds terrible, it's a better name for canceller I can't even think about one. All right fine canceller canceller it is So this would be Executionary that's true Well, so so one of the reasons I don't so I don't really want to use the word terminate or kill here because terminate and kill and executioner have the the sort of Associated meaning of something ends immediately whereas that is not true here right terminate here is really just a signal That we will eventually terminate And this is why I think cancel is a better name because a cancel just means I No longer want you to do what I wanted you to do right whereas execution is like stop right now or terminate So I guess this should actually be called cancel is really the If that makes roughly makes sense So now on handle you can Get a canceller you can cancel and you can wait And now our weight is also pretty straightforward self dot and now this doesn't need to be an atomic option at all Which is nice Self dot executor dot join And here we're gonna have to do a little bit of magic. So if this is okay Then what we really got is are here as a result. So remember Will we get back here from dot join? documentation Back here join returns a this is a thread result of T. So it's one of these guys So the thing we get back from join is a result and our T here our T here is also a result Right, so we get a result of result. So the okay here the R is one of these results Right, so this is just gonna return R and that's all fine The issue is if we get an E What do we do right here? I don't actually have a good answer for you But I think what we want to do here is we want to panic Essentially, we're propagating the panic right we're saying that if you call weight You want to act as if you were the thread that ran the loop and that's basically what this does, right? It Does lose some of the information about where the original panic happened, which is sad, but I don't have a good way of Expressing that basically what we're doing here is we're saying that if the thread we're waiting on panicked Then we will panic in the same way It could be that this should be like Instead just give a message that hey the underlying thread panicked But I think they're really the right thing to do for weight the sort of the semantics of nature weight should be that It's sort of like a poisoned lock, right? If you try to take a lock that was previously held by a thread that that aborted the panic then the Thread the next thread that tries to take the lock will panic because the lock has been poisoned by the The other thread that panic and we're sort of doing the same thing here We're saying that if the if the main if the executor of the main loop panic, then we will also panic Um Okay, let's see. So let's um Let's try to make this Into an example that works. So I think this code does now. I guess this will have to be cancelled Let's run a formal format That's much better It's plenty about Where self is oh right so Traits do not generally require the type that you implement on to be sized But usually because you take receivers that are references those are already sized specifically. They're the size of a reference Here we're saying we're gonna take self by value. So we're gonna consume self But that will only work if self is a as a size type. So imagine here for example, does someone try to implement cancelable for What's a good example of this Implement cancelable for send That's not a meaningful term, right? Like because send is not a type you can construct is not sized And certainly then consuming something Consuming a send doesn't really make sense. So we want to say that you can only call spawn if Self is sized and sent what else is it complaining about and it's complaining that A Handle does not have a new that is true. I'm just gonna construct it manually instead Here Right, so we're spinning up a thread here. So we actually require that the The current type and the error type Basically live forever. So we need them to be static the intuition here is imagine that You try to implement cancelable for something that's on the stack So you you have some local variable in the Okay, what's a better example of this imagine? Do you have a? Something like a you have this and you call this stop spawn Right Where food here is a particular instance of the type food Then if you did this and then this code and spawn is trying to do thread spawn of That that's basically what we're Trying to make it do right however you can't move this reference It's take a right so it refers to something and you don't actually know how long the spawn Method will the the spawned thread will run for it might outlive a Specifically spawn requires that its arguments are static and because both the Error and self are being moved through the thread. We need both of those to be static as well Now what kind of our mutable captured? That's true. So this is this is actually an error. That's been bothering me for a little while It's telling me here that basically I'm not allowed to call a mutable rough mutable method on self. So remember that self for each takes a mute self It's telling me here that self is not mutable and therefore I can't call a mutable method on it it's a little annoying because Really what it should do is to point out that here this needs to be mute That's really what it's trying to tell me, but there's nothing in the error that actually communicates that All right, so we now have code that come piles great So now let's see if we can get the example to work out So we see currently not gonna work This is going to use Yeah, that error isn't great It like the error is fine. It's just not particularly helpful Like I don't think it's confusing because it is true that self was captured and it is immutable and it cannot be borrowed as mutable It's just unhelpful And I think the compiler should be able to do better Right so here we're going to use Minion and we're going to use stdnet Those are probably the biggest things we're going to have a struct Listener which basically just wraps a net tcp listener And we're going to implement Call the service and we're going to implement Minion Cancelable for service So the idea here is some like very straightforward network server that just like accepts connections in a loop and does something with them So for this the only method we're required to implement is for each the other two have default implementations for each takes a new self and returns a Minion loop results and Over e right, so we need an error type here. That's going to be IO error So this is going to return a self lookup results and For each is really just going to be what we used to have down here of Oh, that's a good point. I think we want to implement just to make this a little bit easier to use with So here we want to be able to do something like listener dot accept question mark, right? So we want to be able to have Have people use the question mark operator inside of this Basically have errors turn into a loop result break, right? And the way we can do that is by Resonate called try Yeah, so we basically want to implement Try Where's that here? We want to implement Try for lookup result This is basically what what implementing try means is that users will be able to use the question mark operator on Things that can be turned into loop result or on loop result itself In this case our okay type is blank our error type is Into result is going to be Results you can actually turn into other errors with question mark using from so the reason I can't use from here is because I Want to turn something into loop result. It's not that I want to turn loop result into this So I don't think it's efficient, but I might be wrong. I don't think it's sufficient to do Imple from From generates and into implementation That's true. Yeah, I mean maybe this work we can try it So from our results And we want Okay this To give a I guess the result continue probably See, this is why I think we need try because Well, it might be fine. All right, or it returns a loop result Let's see so yeah, we we're gonna try to accept a stream and then we are going to In this case our for each is very straightforward. It's just going to print little I guess it will write To the stream And then it will close the stream So notice that this will never actually return it's just going to keep accepting connections forever and we want of course impulse service I guess technically we should probably do default, but This is going to do it's going to just like set up a TCP listener of some sort 65 Then we'll turn a service We don't need this anymore Our main is going to be it's going to start a service. You think you saw a small issue in the from implementation Probably. Oh, yeah, this should say error There's a question whether this should say break or continue I think it should probably say break to be semantically similar, but No, if you if an operation does not fail then it should continue All right, so we want s is going to be a service new And then just to demonstrate that this works. We are going to call just s dot run And now see if I run example basic No minion in the root. That's funny Uh Really, oh Want automatic extra and crates I cannot find listener Yeah, see Won't work. I need to implement trifor look a result that the reason here is it doesn't know that if you have a result you can Yeah, exactly question mark is only to find a result And that's why you have to explicitly implement try So like this is maybe useful, but really what we need is this which is a little sad because it requires lightly Try treat the thing But hey, it's another interesting thing to learn it's unstable to implement for anything else Right so look a result the okay type is nothing Yeah, I think that's right So look a result error e is big going to become error e anything else is going to become okay From error is going to be a loop results V and a from okay is going to be a loop result Think that should do it So what we're doing here is basically we're Implementing try so that you can use question mark in This case and question mark will take the error if except errors It will be returned as a loop result. Normally a question mark just returns results, right? One way we could get around this is actually here. I have a let's be smarter about this Loop state it's gonna be one of these and this is going to return a results loop state and Now you can input into Yeah, exactly So I don't want the user to have to explicitly call into because it's a little bit sad But this is going to make everything work and we're not going to need to try trade So the idea is that question mark is always defined for For result. So let's just have an error in for each be an error in a result And then this problem just goes away. So we're gonna match on this if it is okay Loop state Basically this or this is an okay and this is an error That's much nicer Um, yeah, yeah much better So this basically gets us away from having to implement try ourselves What is it complaining about? I think it's uh complaining even though it shouldn't be loop state I guess loop state is not a great name, but it's fine. Great. So now this will return instead a A result of a minion loop state And this will be an okay Loop state continue. I don't really want this to be called loop state All right, let's try to run it. Um, I don't care about the socket address When I get a stream, I just want the screen and I need to use Oh, yeah, we can do the nested imports Run not found. Oh, right. Use minion cancel stream has to be new and Service has to be 25. We're gonna unwrap that. Great. So now in theory, we should be able to do I'm going to really not have net cap on this machine. That's not great. I want I think I want the new one Which one do I have over here? Maybe it's the open bsd one I have. Yeah, open bsd It compiles ship it. We're done. No All right, open bsd nutcat. Uh, then we're gonna nutcat to localhost 65 56 We get a hello. Great What if we do it again? We get a hello. Great. So okay our service works Uh, that's all fantastic. But now of course the problem is how do you quit it? So currently we can just press ctrl c and ctrl c will just like kill the process and that's fine But you could totally imagine that like the service also has like a database connection and stuff that we want to terminate And that's why we have this whole thing in the first place. So what we're going to do is we are instead of just running inline We are going to do s. What do we call it spawn? Uh, we're going to get a canceller Move that into a thread. This thread is gonna This thread is going to sleep This is pretty arbitrary, but from sex black So the idea here is that um, the server is only going to be running for 10 seconds And then after that it's going to go away. So we're going to say something like a server running Uh Server terminating server terminate Fantastic. Okay. So if we now run this We will ooh So if we now run this example Why does it not need to be? Oh, because s consumes self Right. So the server is now running and if we wait for a little while it should then say that the server is terminating And we're going to now observe that the server doesn't actually terminate yet So we see here the server has now been told that it should terminate. So this is why we really called cancel But the server hasn't terminated yet. So in fact, if we if we do the if we try to connect to it We will get hello and then it terminated and this is because it only terminates Once we go back around this loop again Um, and so this is sort of a known shortcoming is something we'll have to document Um, but it doesn't mean that it works, right? So here we can do this multiple times And that's all good And then at some point it's going to say server terminated and then we can no longer do it So that's fine. And now we get connection terminated because the server is no longer running So we have a castable service that is fantastic. It's almost as though that's what we tried to build Good job team. Um Now let's see I also want this just for completeness I want to Not to do that Oh, they Shut down with net shut down both. Oh, why doesn't it? Oh, I guess this is net cat that keeps trying to be helpful. I don't really need that a close service anyway Okay, so now we have a crate that works. It does the thing that we designed it to So now we can start to do the nice. Okay, let's first do a Get add server It all works. I don't have any other windows. Oh, no, it's too bad Well, um, okay, so we have a commit we will go to get up Make a new repository way too many repositories at this point minion rust Crate for Managing Cancelable services Uh, I don't want to read me. I do eventually want to license, but that's fine Now let's do this this again All right, it's now been pushed so you can all look at the code if you want Um, oh did I forget to cargo format all before I did that? No great I don't know why my rest format is not working anymore Now we're gonna do all of the things that people are supposed to do before they publish a crate So we're gonna turn on deny missing docs and we are also going to do a vim diff of cargo.toml So whenever you publish a crate, um, there are a couple of fields that you should set So if you start out with just the cargo.toml that uh cargo gives you when you do new It's very very basic, right? It has the name. It has your name and it has the version which is just 0.1 If you look at the cargo manifest or just like look at any existing crates. So usually what I do is I um Take some old crate of mine that I know is decently well um documented like like factory rs for example um So here if you look at the cargo.toml and has a bunch of stuff in it and I sort of try to basically replicate what's in here um, so in this case I want description And description can just be what we used here I want a better description of this in cancelable services, but I don't have a good sense for that Uh documentation is actually now. Oh read me. Yeah. Um documentation is now Defaults to docs.rs. So you don't have to specify it anymore Which is kind of nice uh homepage and repository. You do have to specify. I think I don't think it defaults to um I don't think it defaults to the github stuff that crates.io sees that you're linked with yet um Keywords are basically unimportant. They're mostly there for a search Um categories are a little bit more important. I think here this will be something like service cancel Uh Service cancel sure why not? Categories are a little bit more interesting just because they're harder to find so you go here that lists all the categories um and looking through very roughly Do they're pretty few of these that apply to us? Um, maybe network programming unclear It should function without the now it needs threads Uh rust patterns maybe all right, um The really annoying part here is that um crates.io does not tell you which keyword to put in your cargo to automo for this category Like I think it's network dash programming. In fact, yeah, it looks like I've used that before but there are some of the other ones like you know um If you look at something like Internationalization I don't know what tag to use here is the i18n there So basically what you have to do is you like go to the repository You click there cargo to automo and then you look at what category they listed and lo and behold the i18n does not actually show up here It's a little bit sad. All right, so we won't never programming programming and probably rust patterns Which yeah, so here's an example. I think we have to go here cargo automo And this is rust dash patterns So usually you can guess what it is but you never quite know All right, what else do we have? license sure we will put this Because that's what rust defaults to Which also means that we need to copy in I wish cargo could do this for you, but I guess I kind of understand why they don't Close to here um We're going to add travis for this because it's just good to do That's going to be uh minion So this uh, the badges entry here is if you go to crates.io and you go to some crate bus It's maybe a bad example Uh, oh Well, it basically shows a badge here on the side as well. Although the badge here is a little sad. Why is it failing? You can do with that separately. Um And I think that's all we really have to list um We now need a readme. So for readme's, um, I've basically fallen in love with cargo readme So cargo This thing I wish there was a better way that was integrated into the language But the basic idea is that it uses the crate level documentation of your crate. So the Um The stuff that we are going to put like this at the top It takes all of the documentation that's here and generates it and puts it puts it in your readme.md Um, so the basic idea is you have a readme.tpl file and readme.tpl Contains any other text you want to be in the readme and then somewhere you put I think it's crate or something. Um, in fact, I can just Copy this as well from somewhere readme.tpl Okay, better one, like a factory um Readme.tpl And if you look at readme.tpl, it's basically just a just put other stuff that you don't want to be in your um In your crate level docs. Let me put that here in this case. Crate is going to be the name of the crate So that's going to say minion. Um, we want a link to crates.io I'll place all of factory rs factory Factory with minion And I don't have windows build and I don't have codecub Great So here I'm basically adding three badges. These are pretty standard. One is to crates.io One is to docs.rs and one is for the travis build status And then readme is the stuff that it generates from your From your crate level docs Um So now if we do a cargo check, it's going to complain at me because nothing is documented Um, the documentation here is actually pretty straightforward I'll do the crate level docs last because it's usually Useful to go through all the other things first. So loop state is um Indicate whether main server Service loop should continue accepting new work Except more work Stop And return So one thing that I think uh a lot of crates make this decision of trying to figure out where you put examples You put them in you put examples for every method you put it for the Wrapping struct or do you put them in the crate level docs? I like to put them on structs and on top level docs mostly unless there's one method that is particularly complicated Um, so there's one where I expect that people who use this method will not quite understand how to use it Including a specific use case example for that is good But in general like if you just think about when you browse docs yourselves You It's much more useful to like the moment you get to the crate documentation Immediately see the examples and just be able to copy and paste this code similar to if you're working with a particular struct um You sort of want to know stuff about that struct straight away You don't want to have to go in and like expand the individual methods to figure out what they do And if you're looking at a method, you probably care what it does But you don't necessarily Wonder about why you would call it because you're already looking at the method Uh, and so that's the pattern i'm going to follow here too. So we'll probably end up with an example here We'll probably end up with an example here Um, but we will not end up with examples for the individual methods Um, we'll also probably want tests for this. It's a little hard to Test maybe Actually, no, it'll be fine. We'll just have to Run some tcp servers and clients and that's fine All right, so cancelable Uh This is that implement. Oh, so this is another thing that recently I don't think it's unstable yet. Um But it is really useful and that is Automatic dock Okay, so what you can do is Uh This So note that there's no link after this you just put cancelable in ticks and with uh With links and now I guess I can turn off this for a second Uh, now if I cargo dock If I generate docks for this now, um, and I go into cancelable Note that cancelable is now a link to cancelable Automatically and it will go to the right place as well. Um, this is not used to be the case You would have to do something here like trait dot Cancelable dot html maybe like method is Uh method dot for each And stuff which is really annoying Um, whereas now the links are generated automatically for you. I think this is nightly only for now But it's going to land unstable pretty soon and it's quite exciting Um, a service that implements cancelable We don't actually need a link to cancelable here because it's already what we're talking about but it can be useful in general um regulates Service and implements cancelable Can be told to stop accepting We work at any time and we'll return the first opportunity First of all um Yeah, the docking is really really nice and it it makes writing integrated docks just so much nicer That's so often you want to do things like even just here like I want to be able to say like For this you Need to implement and then I want to say like Cancelable for each And if I had to dig up what the url for this was every time it would be really annoying But now if I go here, this will just work. It'll just be a link to the correct thing immediately, which is very very nice um More concretely so one thing I've found useful especially for api wrapper types like this is to give some pseudocode for Sort of what your library is doing without all the detail because I think it helps understanding a lot um It emulates a loop like the following loop some work Do some work Where the so so here like remember this is what we started out in the very beginning in the example We started out with code like this as this is sort of what we're trying to do and giving that in your docks can just be really helpful um But where the loop can be Canceled that is After the next piece Of work is processed No more work is handled and the loop breaks Um This trait provides two main method and here again, we can use that same thing I don't know if this will work if I just say spawn That's a good question I really wanted to It does not that's too bad Uh, I wish it would realize that I'm documenting this type And that when I say spawn I mean that method on this type But alas maybe later And Cancelable is a terrible word I guess this is run and spawn the former runs the Loop on the current thread And this blocks it I think in general libraries need to be pretty good about telling Consumers of the api when they block and when they don't this is becoming increasingly important when people start adopting futures and async i o Um, because it can just be devastating if you block and you weren't expecting it um The ladder spawns a new thread and executes the uh Main loop Executes the loop on that Loops started Using spawn Can be cancelled And then here we sort of want to give an example and we can probably then Sort of reuse this one um So here the user doesn't or the reader doesn't really have the same context as we do So they don't have all this like gcp stuff But here we don't really want to tell them about what the service is doing because it's not all that important um, what we really want to show them is Uh, just like the general idea of how As a user of this library how you would use it like this And here we can use the really handy trick by starting a line with bang You can hide lines. So in our case We will basically hide We'll basically hide something like use mini in a star so that we don't have to prefix all of this with minion um So here the idea is here the service is Well, the server isn't really running here the server is running Um, it might be that we want a callback on spawn so that if the user wants to run something on the thread that has the main loop They have that option, but we'll leave that alone for now. We could have a builder for services, for example um I guess here we're going to need something like the user doesn't know what a service is which is a little annoying Uh, if we want something like impel For service So spawn start the service service loop on another thread or a new thread Get a handle that allows Stop canceling The service loop spin off a new thread that will handle exit signals And then in here we can do something like, uh, this might catch See from the user It's useful in the docs to sort of give the user an intuition for what's going on without necessarily giving them all the details So here it doesn't really matter what happens in here, but we need to be clear to them that This is just example code. This particular line does not matter. Whereas this line really does matter um user Wait for a particular packet Or for any other condition That signals that the service should exit in this case. We just terminate After a fixed amount Great. So this is gonna Remember that this is going to be a test. So we don't really want the test to run forever um tell the service loop to exit at the first Uh opportunity Block so we want to point out here that weight also blocks. So, uh block until the service loop exits Or errors So now if you look at the docs, we get something quite nice. Now if we cancelable where This sort of is basically our example, but it doesn't have any of the other service stuff that we otherwise would have had Um, so you don't need to know about tcp listeners or anything, but it shows you exactly the flow of what cancelable does um, there's an argument for maybe this should be in the crate level docs and Cancelable should have an implementation example Let me think about that for a second I also want The line wrapping and card the box a little Yeah, actually, let's move this into the crate level docs so And then for cancelable, we'll have an example that is basically the implementation that we had earlier So we'll do something like here So star Don't really want to use your card to see this either Like this So there's a question of whether we show this or not. I don't think it really matters But sure why not And I guess if we're Being nice anyway, we should do this It's nice for examples to not have unwrapped and part of the reason for this is because it is so common That people just take the code that's in examples and just runs them They just like copy paste the code So it's nice for them to actually be Be right um And I guess we might have to give the main code again here, which is a little sad um Why do programs use error as a verb when error exists? I think it's because it's a little bit awkward to say er This might just be because I'm foreign, but It sounds weird to say that's something erred Um, that's the best answer I can give you I think Um So I guess here what we could do Is just because we only really care about running this Actually, here's what we'll do This example doesn't really care about how you want to run your service or that you want to cancel it All we care about is the fact sort of how you implement it Um, you may have to give a little bit more explanation around this But see now so the crate level docs will have this examples Which show basically how the crate is being used and cancelable has an example of how you would implement it We might have to provide some docs around this though. So here um For example, uh The implementation below, uh shows how Uh Classic server accept loop Could be turned into a cancelable Once Cancel I guess here we'll have to show something like because it has to be handle Is called I guess, uh, if it's called then uh At most one more connection Will be accepted before the loop exits before the loop returns and Handle wait I think one thing that is also nice in docs like these is to make sure that you tie together the whole workflow of the crate So for example, if the cancelable documentation did not mention handle People will have to realize that they get a handle back from spawn and then go look at the docs for handle whereas here we sort of Point out which method The fact that handle is relevant to cancelable and which which methods are relevant on handle Um, and that I think just makes it a lot nicer to work with um I guess error here is a pretty trivial type. Um third type for Called once for every iteration of the loop This method is small. Yeah, I think the I I totally agree with you the the Documentation integration that Rust has is fantastic. I do think that We don't have a good sense as a community of how it should be used yet Like I think it would be nice to have some guidelines for even just things like What kind of language should you use? So here I've now said this method is called once um, it could also just say Called once you could say this method will be called once Like I think if we made them all more uniform that would help a little It would also be really nice if you could easily link to docs and other crates Um, you can sort of do this and that you can refer to types That you like pub use for example Um, but sometimes I want to refer to like crate level docs of some other crate that I am depending on And there isn't really a good way to do that. Um, it's also really neat that we now have Um Dock include so this landed pretty recently at least I think it's landed now Uh Dock include and I'll find any of these um Because it might not be open anymore So the basic idea it seems like I can't quite find it now, but um Oh, it's really annoying. Um, but basically you can now do This which is really cool So you can include you can take a file that exists in the file system And make that file be the the contents of that file be the documentation for that method or that struct or that crate And of course you can use this for things like I think this is one of the intended use cases is this Which is really neat now I don't actually want to Do this currently because it would also include things like badges and headers that I don't want to include there And I don't have a good sense for how that would work in part This would be fixed if in read me In the read me you could also include a file then I could have some third file like read me inner or something The both of them included but currently I don't actually want to use this Yeah, yeah doc include is really nice And I I think you're right that I think the docs are going to improve over time But I do think we need to make sure that Broadly used crates are well documented and that that is definitely happening with sort of a lot of the The crate pushes that have been happening recently people have been more aware of this This is part of why I spent so much time on this In my live streams as well because I think it's really important And there's nothing more frustrating than coming to a crate and having it Feel like it's not well documented and feel like you have to guess at what happens everywhere Let's see it's called once for each iteration of the loop if it Returns The errors the outer Service loop will Also return with that same error if it Returns a loop state the The loop will continue or break accordingly Accordingly If it panics Um The waiting thread will The panic Will be propagated to the weight in the thread So this is one of those cases where here we could give an example, but I don't think Uh, this reminds me that I should work on my crate sometime. Yeah, I think you should I think working on crates is a good idea Uh, clearly Um, so run here is pretty uninteresting. Uh, I mean it's One of the core methods of our trait, but um, so the idea for run is, uh Continuously execute this noble for each This reminds me that I should make crates. Yeah, also true I think the rusty ecosystem is becoming better. Like there are crates for a lot of useful things now But I think crates like this like smaller crates are still really useful And I think they're a they're a good exercise in just like building good crates Like teaching yourself to build a good crate start with something small like you don't really want to start with something like, uh That's an example of this. So I started building or I built bindings for the factory work service Um, that it was recently published and like This is a fairly involved crate with lots of docs and you don't really want to start somewhere like this You want to start with something smaller like what we're doing right now? Or like building a data structure is actually a really good start because data structures are almost always useful And like they're pretty straightforward to implement And you end up with something like word search So this is one I built it turns out that you should not use it It's slower than anything else But it was like I found a paper with an interesting data structure and I implemented it and it's not very long or complicated But it is like fairly well documented has a bunch of tests a bunch of benchmarks This is like a good way to get into writing good crates Um, let's see. Continuously execute cancelable for each until it errors I guess this is where uh Rustafaro would like me to say until it errors See that just feels weird Returns an error See work around it until it turns an error or Loot state Break Continuously execute for each in a new thread return a Handle A day structure seems to be something that is regarded as hard and rust most of the time you need unsafe So it's totally true for the data structures that I've implemented Especially things that come from research like things that are related to concurrency You do often have to use unsafe, but But that's okay like It's basically you're writing to some extent you're writing c code But only for core parts. So one of the things I found really interesting is to take Try to make the things that are unsafe as small as possible. So What's an example of this? In bus So this is a Broadcast channel implementation It actually has fairly little unsafe code In part because most of the unsafe I've encapsulated in this thing called a slot So this is like There's like one this this Mute seat state is the primary thing that's unsafe And so I sort of encapsulate the unsafe in that and the rest of the code is mostly safe And I think it's a the good exercise to Like this is why data structures are a good idea because it forces you to use a little bit unsafe But find a good way to encapsulate it so that your whole code base isn't unsafe And you shouldn't as was pointed out in the chat as well. You shouldn't be unsafe You shouldn't be scared of using unsafe Especially if you're willing to go into low-level programming because unsafe is really just saying I need to be careful about managing memory So I think I I don't think my claim would be that implementing data structures is Straightforward necessarily, but I think it's a good exercise and it's not It's not an overly large job, right Like I think it's a good starting point, especially if you want to learn how to write real rust You get to combine sort of unsafe with api design With just like looking at cool data structures, which is nice And you get a better feel for the language and how it how it operates when you work at the Those kind of low levels Yeah Let's see Oh, yeah, um documentation for unsafe would definitely be nice. There's the nomicon If you haven't seen it so the nomicon is uh Pretty useful for many of these things Because it tells you a lot about sort of the low-level details. You need to make sure that you follow Um My crate is mostly unsafe because it does system api calls. Um, have you tried looking at nix? So the nix crate is really really nice So the nix crate basically takes a bunch of it takes lots of system calls that are Fairly low-level and wraps them in safe wrappers. So it often lets you get away with using Um having no unsafe code while still using all these like more Sort of integrated system system calls. So you might want to look at that um Anyway, yeah, so so nomicon is worth looking at if you're looking at unsafe Continues to execute castable for each Oh, the other thing I could recommend is look at other crates that people have built that use unsafe Like just like read the code I think nix also does windows But I could be wrong It's basically wrapping libc um Yeah, so you could read other crates. So for example There's a EV map as a data structure. I wrote which is actually really cool. And I I think it's decently well documented um And the source code if you read through it like there's definitely some unsafe in this is a pretty involved data structure um But it's like well commented and tries to explain why it is safe and correct Um, and it sort of scopes all the unsafeness as little as possible So you might find it interesting just like read something like this If you're if you're interested in learning how to write unsafe code and how to write more involved data structures in rest Uh back to our crate continues to execute callable cancelable for each in a new thread. I'm going to turn a handle uh to That loop so that it can be canceled or waited for great a handle um is A reference to uh handle or handle to a running um service loop You can use this handle you can use it to Cancel the running loop at the next opportunity to Handle cancel see how useful this feature is see how many times I've used this I'm so surprised they didn't have this sooner, but I'm very happy they have it now um Or to wait for the loop to terminate through if you want See this is another thing that I don't know what to do about docs I think in some cases it makes a lot more sense to talk about you as in talking to the developer directly And in some cases it's sort of more passive about what the function does for for each I think it's more reasonable to talk about the Operation of the function whereas for a handle I think it's useful to tell the user like you would use this handle because you're going to be given One you're not going to implement one, but you're going to be given one and that changes the tone um You can also use Canceler to get a To get a Cancel there's going to have a lot of cancel in it the sentence you can also use canceler to get a Canceler handle Which lets you terminate the service loop From another thread Or from elsewhere I'll wait in uh canceler is just a handle that Allows the cancellation of a running service loop I'm a little bit sad that we now have two cancel implementations Hmm I think I want this to be canceler Yeah, so I'm going to have handle deref into canceler instead Because that way we will only have one and that way we only have to document the method once Canceler is going to be canceler Keep running then now we're going to Use ops deref input deref for handle Target is Canceler deref is so useful it should be used everywhere um deref self I've used it so much that I now know the full type signature of the trait Canceler So now this method can go away from here because you can call it call it through handler Cancel the Get a another handle for Canceling the service This can be handy if you want the current thread If you want one thread to wait for the service loop to exit while another Watches for exit signals So here wait is pretty straightforward wait for the service loop to exit and return its result Here we have to be careful. So if you look at the standard library, you'll see that there are a bunch of cases where they have Subtitles for sections like panics I don't I don't know whether I like that part of the reason is because You end up with a section that's fairly large that has very little content in it. So usually I just put it straight in the text I think it makes a lot of sense though. If you have a very long documentation here Then you want panics to be clear to show that it can't panic If the service loop panics This Method will also panic the same Great and finally cancel Cancel the currently running service loop Note that this will not interrupt a currently executing Cancel level for each Instead Cancel level for Instead the next time cancel before each Would be called the service loop world Great, we're almost done. Let's see. So now crate level docs this crate provides a wrapper type For making long running service loops cancel level in particular We could just give the docs here It's not really what I wanted expected function Why is it expected function found struct? Did I do something silly? Oh, canceler has fields so cargo format cargo t maybe five Did not like that line six Oh, right. We need to do some trickery. We're gonna do a struct service April cancel level for service We just need a dummy implementation so that the code will actually compile For each It takes a new self It does nothing We need the return type The return type is going to be unit. It's going to return result unit use of Oh, I'm using thread and time here too Time thread. This is a one of the more mundane parts of writing Doc tests is it's just really annoying to debug them Because you need to recompile them every time and they're actually fairly slow to compile The question mark operator can only be oh, they don't have I can't use question mark in doc tests yet. You can use question marks in main now, but not in doc tests We will do we will cheat and do FN foo No, I mean you can fake this by doing what I'm about to do but it's It's not great So this is going to be I guess an IO result of nothing See how pesky does this For each has an incompatible type for trait That is true. It has to return loop state Okay Yeah, so the in doc tests the um the leading hash is really really useful. It's just like Oh Imple service FN U And you'll see in a second why they're really useful Basically, this code looks pretty horrible right now But the resulting output in cargo doc will look very straightforward The downside of doing this is and I've had this happen in the past Is that people will copy paste the code that they see and expect it to run Whereas all this hidden stuff is also required for the code to run And so that does trick at least some users up. Oh, I guess actually It's definitely a hack Yeah, so basically what I'm doing here is The user is going to think that this happens in main Uh, but in fact, it's happening in this other function that gets called for main And until we get question mark in doc tests, which like is something that we'll likely get in not too long Um, you have to pull this kind of oh no, it's gonna I want no run Because otherwise the this service is cancelable, but there are no requests So if we actually run this test, um Then it's just going to block waiting on this pcp listener And we will actually write a test that that does this but we will not do that in the doc test probably 11 Self is not a value service That's weird All right, great. So now all the doc tests pass So if you now look at the output after all of that, this still looks the same Um, but it is now actually verified at compile time that this code wouldn't indeed compile, which is nice If we go to cancelable Same with this code to this code Notice how the user can't tell that there's anything weird going on here And this means that this will basically just work wherever they put it We could have a function. We could actually call this main Would actually be a It's actually I don't think we can so we can't actually make that look as though it's in main I think oh, yeah. No, we totally can Do this So now It will look like this is just in main, but in reality, it's actually in foo And it still works just fine Cancelable right so it now looks like it looks like the way it would work if Now that main can return Can use the question mark operator I'm trying to middleware that will consume some json xml. I'm thinking to implement it in rust So it'll be my first experience with the language any cargo package that I can use are useful advice Um, yeah, sturdy json is a good place to start. Um It sort of depends on what you're going to Do with that and Also, what you'd mean by middleware like if you're thinking htp middleware, you probably want to use hyper as well But I'm sure people can help you in the chat as they are already as well Um, okay, so we look at these docs. They actually look pretty nice You know a pretty decent start. I think the um, the biggest thing that's missing now is We want a Uh, let's dive right in with an example Further details For further details see Cancelable okay, so this is going to be a little bit annoying because Uh, this link isn't actually going to work when we generate our read me So cargo read me will We'll just turn this into a regular link, but that is going to be a relative link which won't work So we will actually have to be a little bit finicky here and say um Docs rs it's going to be slash minion eventually star minion struck rate cancelable html It's going to be a little sad All right So now if you see if we do a cargo read me to read me md We now have a read me md file that has everything we had in Sourcelet Oh Did I mess that up? I did They should be us All right, so we're getting pretty close. I think the let's do this Commit Document all the things And hide this window And then we will also do A mess that up, didn't it cancel I also want to add dot Now we can push it that So the other thing that we will want to do Yeah, so if you have questions about rust the the rust discord is pretty good There's also the rust irc channel where people are really really helpful So it sort of depends which thing you prefer the rust users rust lang dot ro Users dot rust dash lang dot org Also has a really good place for you to ask questions and everyone is basically super helpful Right so the last thing I think we want here first of all we're going to publish this because otherwise One of you sneaky people might steal the name Which we're going to run cargo publish If we're really lucky this will work Hey crates crates minion Success Now we just need to set up travis and write a test All right, so we're going to do config tests mod tests It Runs Sure So here we're basically going to take our example Uh, just change my crate to use nix. Oh, yeah, nix is great. Nix is really great for these things Examples I'm going to do this specifically I want From seven Is there a good way to bind to any port? Think that I can do like fn port self to u16 Specifically, I want every test to use its own port um Use super implement castable for service It's going to do all those things it's going to write back low. That's all fine Um Service of that This is going to be What is the rest documentation for tcp listening Can I get the port out of that easily? local adder local adder dot unwrap because this is a test anyway Use me a socket adder which has a dot port Unwrap Great, so now Our tests should be able to do something like it's going to start a service in fact, it's going to service is going to be a thread So the idea the idea here for this test is we're going to spin up the server and then we're also going to start a tcp client And we're going to have the tcp client keep connecting to the service and then check various cancelling behaviors Um, which should be pretty straightforward. Um This is just going to dot run Like so dot unwrap s is going to be the service and then here we're going to do s dot run dot unwrap We're going to get the port which is going to be s dot port So in one thread, we're going to run the main service loop and then on this thread. We're going to Try to connect to it. So we're going to do tcp stream Uh connect So client is going to connect to one two seven zero zero one This is going to bind to any port Don't need main This is going to connect to this and that port And then we can sort of assert We can we could hear potentially a search that we read hello But let's just for now check if this works Um As if I'm going to try to test now Oh Oh, is it a config test? This is not going to be a minion anymore This is not going to be a minion time is going to be used eventually Um three that needs this to be Great. Yeah, so this works fine. So basically it's successfully connected to here. Um For reading the easiest thing is probably here if we look at Net tcp stream so tcp stream implements both read and write in our case we want Uh, I want something that just reads all of it, but there is no such thing yet. So instead we have to do Let r is string new c dot read What is it read to stream? R And then we assert equal R to hello Right, so we now have a test that passes and this test spins of the service This is really just testing that run works. We we have no reason to Expect that it doesn't. Um, we do want to make sure that they it doesn't like quit accidentally or something So we run it twice Specifically because the server shouldn't exit between any given time that we connect and indeed if we run it it passes And everything is fine the this run will of course never terminate At least shouldn't be terminating Unless an error happens inside. So in fact, we're going to have this return So I don't know if there's a good way to cause an error on the server side So I think we're just going to leave this test like that um, the other test, of course, we want is The the whole reason we wrote all of this is because we want it to be able to be cancelable So we will also have it cancels Uh, and for it cancels instead of run what we will do is here We will do s dot spawn So handle is s dot spawn and then we will Connect and see that works and then we will connect again to see that it works and then we will, um Cancel the Server service thread and now we know that once more should work. I guess actually we can uh Let's make our lives a little bit easier and do something like Uh, connect assert Which takes a fork And does this So we're going to Twice here. Yeah, so the idea is that, um Once we cancel We know that we should be able to connect one more time. So remember, uh, cancel will only Mean that for each will not be called again in this case. However For each has already been called again because we we process this connection Um, and so now it's currently in trying to accept a new connection So this should succeed, but then the next one should fail. So now we should do Um, oh, I guess we should do h dot weight dot unwrap Because we're not expecting there to have been an error, but here we should expect that it immediately now returns Uh, I guess this is not going to be removed And lo and behold it does the right 359 this isn't necessary All right, so now we have one test the test that we can run the service Um, and it it connects correctly and indeed when you call run it will run forever Um, and we have one where the checks that cancelling actually works. Um Ensure that, uh For each is not called again Terminate I think this is another thing that's important in rust code is um, we're in not just in rust code But in general is to document your tests like document why your tests test what they do Um Like here, for example, it's not clear why we try to connect and the weight if you don't intimately know the code So in this case, uh, we want to say it will not terminate the Currently running Now instead of calling for each again The loop should now have exited Uh, one thing that's missing here are tests for errors. So, um, if a connection errors in some way Then that should perhaps be returned. Um, we don't have a good way of dealing with that. We could test the panic Um, but I feel like that's probably not worthwhile at the moment um All right, so let's now Make sure that we also get support for travis because we want integration testing for this So here I usually start out with something that I've used before So in our case is because this travis this has a bunch of stuff that we don't need I don't need script and I don't need that Don't need sudo don't need dist This will work Yeah, we'll work on stable beta and nightly. We want to allow failures on nightly um Do I have any other reasons to suspect? No, I think that's all we want Great. So now we add travis. We also add all these tests Are you format them? Push. Oh, let's see Minion, let's see if we can give you that nice nice check mark um Minion A lot of tabs open. Let's get rid of some of those Add a repository I want Minion the other thing we might want to add is like code coverage, but I think it's A little bit less important for this particular project It's also a little bit of a pain to set up still. So there's um, uh Cargo Tarpaulin, I think that's how you pronounce that, uh, which is basically um Uh re-implementation of of k-cub or sort of the code coverage monitoring tool or measuring tool Written in rust that works pretty well. Um, but it travis for various annoying reasons. Um, that aren't entirely their fault They Require you to have sudo which means you can't run on the container infrastructure and so your tests are all Really slow and it's pretty sad All right, uh, we pushed now. Can I make it? trigger build on master How about that? Let's see In theory, this should be very straightforward Um, because it's not a very complicated crate, but we will see sanity check. I think we're all good Ooh, do we have a Minion yet on docks for us? We do. Hey, how about that? And now the big question, of course, is the read me if I click this cancelable link So much for writing, uh, running manual URLs Um Oh, that's interesting. So docks rs does not build with a nightly that's new enough to have this feature So that's a little bit annoying. Um, although I think that's something that they're working on Uh owner, isn't it? Oh, no I think it's owner Yeah, they're building on a pretty old version Um, so this is a little bit sad, but it's something that will be automatically fixed once these, uh, dock links Well, actually, I guess you'd have to trigger a rebuild It's fine though. Like it looking like this is a little bit annoying, but I think it's fine for now Um, let's see. Hey, it passed. Did it pass all of them? Did not pass on beta Well, that's interesting an action reset by pier Huh, but I'm on nightly. I feel like this is a lie what Really on beta and nightly, but not unstable. What is this? I don't quite see why that test would fail let's do, uh Doesn't seem to be failing for me Travis, what are you doing? I have noticed that sometimes Um, the Travis network in particular, but but the Travis machines in general are somewhat slower than local machines and therefore trigger like more weird corner cases and race conditions in the code So that could be what's happening here, but I'm not sure. I believe that this is actually a bug Maybe they like don't allow you to connect, but I don't know Oh, it's reliable too Really? All right. What if we do cargo Beta I don't even have beta installed. What is this? I feel like beta is one of those things that is only really used for continuous integration testing and very few people use it in practice Um, although like in a sense, that's I guess what it's for is to catch bugs In ci Let's see. What else? So the docs are now pretty nice Oh, this is neat. I haven't seen that before Another question is whether this method should really be called for each, but it seems like a relatively unimportant point Why is it taking forever to download that? Well, I guess we can do uh Your cargo test again Why does this not work on Travis? specifically it fails on Cancel But it doesn't tell me where can I set like, um Oh, I guess I can set rust back trace here, right? Rust back trace One well, that's taking a really long time Well, so one question is do we think there are more things that should be added to this? So certainly the crate level docs could use some more love Um, but I think the example is actually fairly useful in this case No, I think we're pretty much good. Um, I wanted to keep the stream a little bit shorter than the other ones In part because we were doing something end to end. Um, and so it's nice for that not to be like five hours long Um, and in part because I know that a five hour long live stream is a a bit on the long side for both you and for me So I think once we have this working We're pretty much good for the day unless you guys have Something in particular you want to see I'm also happy to like talk about rust things if you have questions All right, where did it fail? So it uh, stop falling Stop ah Travis stop. There we go. Um It failed at limb 251 Well, that's pretty unhelpful to a Huh, I bet you I know what this is On travis Oh, that's awkward. So, um Remember how the way the way our cancellation works Uh, which me window manager are you using? Uh, this is x monad. I'm not particularly tied to x monad I just wanted something that was tiling. I actually want to switch because x monad is not nice about integrating with like window events But I don't have a good alternative to switch to yet But the Like x monad is doing very little here apart from like managing workspaces and uh And like moving windows around the bar at the bottom is poly bar, which I'm really happy with Uh, okay, so sorry. So what I was saying was um, remember how the way cancel works is that ensures that for each will not be called again and the reason we believe that we can connect again here is because um Uh We know that Once this connect finished it enters the for each again, and then it tries to accept a new connection However, there and therefore it should be blocking and accept There are some cases under which accept will return early without there being anything to to do And this happens for example, if we look at, um That's totally what's happening. That's a little bit annoying. Um, so if you look at accept Uh accept So see how it returns a an io result like everything else If we look at the error you get here The thing to look for so there's a where's error kind specifically um, there's this Interrupted so interrupted will happen if for example, the process receives a signal Um, then usually if you get back interrupted what what you end up doing is you try again Uh, but then the accept did return Although in our case that should be an error But I bet you what happens here is really that the for each The for each gets an interrupt So I think here We want a Little awkward But if this is an okay stream Then we want to return the stream So if you get the interrupted error, then of course you'll go through for each again or rather you'll return an error But because the error is only returned when you wait, we don't get to see what that error is and my guess is it's interrupted Um, instead this connect just fails because the server is going away So here what we're going to do is we're going to say that if we get a stream and we return that stream Um, if we get an error E Where e dot kind is i o error kind interrupted then we Return okay loop state continue so we basically say like If you get interrupted then just try to accept another connection um And if you get an an actual error of any other kind then you return that cargo format and now let's see if we Okay, so this test still passes locally because nothing has really changed But the the thing that has changed is for travis now is so usually when you run things on Uh, whether it's travis or in any other kind of stuff that's not running on pure hardware You can get these kind of interruptions for all sorts of reasons like the Vm got interrupted or something and so these interrupted signals are actually a lot more frequent when you run um Sort of in the cloud Um, and that's why I think we don't have to handle this locally because the the Process just isn't getting interrupted. There's nothing to interrupt the system call Whereas when we're running on travis there totally is And here when we get interrupted what that really means is like try again Um, and that is what we're going to do. So Uh, try again if interrupted guess is This will fix our issue Build history try again if interrupted Let this gives I guess let's follow beta It's a little sad that it has to reinstall west each time I feel like the cache should be better about this I wonder why stable worked though Maybe we just got really lucky that it didn't get interrupted Because I don't think this this shouldn't be any more or less prevalent on any of the release channels Come on No Why Why why why? Uh, huh Wait 286 Well, that's awkward I guess the thing to try next is just make sure that we Here we're just gonna Like e print line Or we're just gonna panic with the error great. In fact, we're gonna do this Uh force errors to be visible on travis Hmm. It's a bit of a mystery Let's see what this gives us And specifically why did the previous one succeed on stable as well? It succeeded on stable and on nightly Okay, so that to me suggests that it is inter interrupted did help. It just didn't help enough So some other error kind too Um, I am not Uh Related to or working on redox. Um, I think redox is really cool, but unfortunately it's not something I'm currently working on It it is something that would be fun But it's also somewhat different to what I do even though you're right that I'm in a I am in an OS lab at MIT, but it's I don't personally do a lot of OS stuff I do have a lab mate who um, who's building Um An operating system and go which is pretty cool So it didn't panic The server didn't panic that is The server just like Connection reset by peer Yeah, so now stable and beta failed. Okay, so this is just Some kind of fluke Hmm Travis connection reset by peer This seems weird I feel like this is a travis issue table ecn odd seems uh Uh This seems like a very weird thing. I mean, yeah, you're you're not wrong I feel like this shouldn't matter Sort of don't want to do this, but uh localist Shouldn't matter for localist duplicate of 9 000 Well in that case I Don't know what we do so this is um So here's what's annoying about this Uh, this code is right It is true though that the uh Basically what it's telling me is that um This connect fails, but none of the previous ones And it's always the one after cancelling Oh I'm stupid. Uh, okay. So here's what's going on. Um We are now handling interrupted But all that means is if we get interrupted then we won't crash We won't return an error we'll but for each like the loop will continue Which means that if we get interrupted for each would have been called again, which means the cancel would cancel it um And so this won't actually do anything so so specifically If you call cancel and accept as interrupted Then it would have to call for each again, but you've canceled so it won't and so therefore sometimes on Travis when you get interrupted the cancel will indeed succeed and so here We will do something like seeded Is zero note that it may Terminate early if the assist if Except gets interrupted is what we're going to do is while this I guess we're going to have Uh assert is going to return a b um So we're going to have we're going to match on this we're going to say Um If it's okay, if you get a mute if you get a stream then Do what you used to do If you get an error Then return The option error Then return the error and now here we're going to assert That none, which I think I'm allowed to do Uh dot is Succeeded plus equals one So we're basically going to keep trying to connect until it fails to connect anymore and then we're going to hear Assert that uh succeeded is less than Two Less than or equal to so you should succeed at most once In connecting right because if you succeeded once you know that for each will have to be called again And then it definitely should cancel um And so here and we want this to start inside the while loop because otherwise the while loop might just keep going um If we if like cancel was broken for example Uh I cannot find values Oh, can I do this? I think I can do this now um Let error e is that Then return some and I'm not allowed to assert each none because IO error does not implement debug which is Well, we're going to do this dot Actually, no, we're just going to do unwrapped Or what's the What's the best way to assert that an option is none and print it if it is some It should just be a certain none, but I'm not allowed to do that So I guess I will just assert that this dot is Basically that there was no error and we will do the same here great So I don't know if you realize what the change I made here was but the idea is that Connect assert will now try to connect and if it's successfully If it's successfully connected it will assert that it got it gets the right string back If on the other hand it errors for some reason it will return what that error was And in the case of cancelling what we want to do is make sure that at most one connect Succeeds after we cancel and after that the servers should have gone away because we've told it to cancel Right and that's what this now does and this handles the the case where travis will end early because it gets interrupted Because the interrupt will cause the service to shut down basically immediately after we call cancel Which means that this loop will fail to connect even once and so therefore succeeded will be one which is less than or equal to The succeded will be zero which is less than or equal to one this loop will Not hit anything As we're all good. So now let's try to push this About now travis did I make you happy now? build history Who has faith who thinks it'll work? I think it'll work I'm like 75 percent sure that'll work I am using x monad although You can't actually tell what min window manager i'm using because the window manager is using doing basically nothing Which is how I want it to be the window manager Is it's like a tiling window manager? So if I open something else here it does this And then I can like switch around windows I can move between workspaces and that's really all the features that I want And x monad gives me this but I would happily switch the um Terminal is alacrity, which is written in rust and actually really nice. I've been really happy with it and the bar at the bottom is um poly bar, which I've also been really happy with all the configs are at Should arguably clean this up and document them, but But yeah, this has all the configurations for poly bar and such if you're interested. All right. Let's see travis build for Hey It all passed All right. So in theory that should mean that this turns green And that should mean that this turns green Fantastic We did it. We did a team. We now have a crate It has been documented It is tested and it is published and does what it should uh Yeah, this is alacrity. My alacrity config is also in the repo. Um And the font I'm actually a little bit sad about because it's recently gotten a lot worse, but it's um Noto from google Um, it used to be a lot nicer and then there was a an update they pushed a while ago Which made it worse So I might consider switching again if I don't get used to it I feel like I like I want it to not have serifs And to serifs and monophones are a little bit weird, but it's hard to work around um Well, I think that's sort of all I wanted to cover today. Um, if you have Other questions about the crate or other things you think we should do and let me know um Preferably like now it's it's a little bit hard to let me know after the fact I'll um I think these sort of shorter form. I mean still almost three hours, but um these shorter form Livestreams work really well for smaller projects like this. So I want to also do one on Some kind of data structure because I think that would be interesting to watch too If you have like feedback on the stream or if you have ideas for things I should cover or Like I've been thinking of doing a series where we investigate the standard library or try to implement things They're in the standard library like hash maps or mutexes. Um, I think that could be really interesting then Just ping me on twitter or my patreon as well. I'm like I follow that as well You don't actually have to support me if you don't want to but it's a nice way to keep up with what's happening um I will record um I'll post the the recording of the stream as well on youtube as usual and add it to the normal channels um And yeah, um Feel free to reach out if you have other questions or ideas Um, thanks for joining me. I guess if there are no more questions then I think we'll end it there Bye everyone It was uh another great stream. I'm glad to have you all with me. Um, and come back next time Oh Yeah, bye