 Hi folks, welcome to another de-crusted stream. We haven't done one of these in a little while. We arguably haven't done many stream in a while. But the de-crusted series, for those who either haven't watched the previous video where we did the Surdy crate or just it's been a long time, the idea of the de-crusted series is to take some crate that's popular in the ecosystem and especially one that a lot of people use fairly directly like you actually have to interact with and kind of need to understand how it works under the hood. And just sort of take it apart and figure out why is the API structure this way? Why does it work the way that it does? What are the techniques that it uses? And really just form a more accurate and useful mental model of how all the pieces fit together so that when you are building your own stuff using that crate, you'll be in a better position to do so. You'll be in a better position to understand why you get the areas you get, understand why something goes wrong, understand how to fix it or hopefully even just become more fluent with it. And when you want to build something using that framework in the future, you'll be more comfortable with what you have to do and potentially more efficient, more proficient and such. So the crate we're tackling today is one called AXM. And if you haven't heard of AXM, AXM very briefly is a web framework. Think like Rocket or like Aktix or Aktix Web, rather. And AXM is one that is a little bit different in the sense that it only really provides routing and request handlers. And then all the middleware stuff, anything that you might want to do in between a request comes in, a response comes back apart from the actual handler function, all of that stuff is handled through the tower crate and in particular the tower service trait. We'll get into what exactly that means. But the essence of this is that AXM is actually fairly small. It doesn't implement things like rate limiting, for example, that's not a part of AXM itself. Instead, you get that via these other middlewares that are built around the same trait, this tower service trait. And we'll dig into how all of this works and why they chose to do it this way. But at sort of from a 10,000 foot view, it is really just a way to express application routes, so things like get to slash and what should happen when a request comes in like that. And then in addition, a sort of mechanism for writing those handlers that receive the requests and extracting interesting information in those handlers. So let's go ahead and just grab the example and then start straight from running code. Let's do AXM fun times. Okay, so we're gonna add, sure, we'll add CERTI, we'll add TOKYO and we'll add AXM. And then instead of this, we'll start with this right here, right. And we'll do add tracing and we'll add tracing subscriber and that should give us all the stuff we need to get started. Great, so if I now just, I just made this thing, I'm gonna cargo run and that'll build some stuff. And now I should be able to just curl localhost port 3000 and I get back hello world. Okay, so we now have the sort of very rough idea of create. Well, we'll actually walk through the code, but like this is what it does. It has code that defines the routes you want to accept. It sets up a web server. It has defines handlers for each of them. And then you can access that web server through something like curl. So that's the basic functionality of the create. Okay, so let's then, let's see, does it have an HTTP parser or does it rely on hyper? It uses hyper under the hood. So hyper takes care of all the HTTP parts or at least all of the sort of protocol level HTTP parts. And then AXM builds on top of hyper. So one of the things that's interesting about AXM's approach to the whole web framework thing is they lean a lot on the rest of the ecosystem. So they use hyper for handling all the HTTP bits. They use tower for handling everything that has to do with middleware and the like. They use tracing for anything that has to do with logs. And they use the, what's it called? Oh, mini match, no. It is called match it. It uses the match it create for doing route matching in an efficient way. So there's a bunch of bits in there in AXM that are really just intended to be the glue between all of these to provide you the framework you need to write HTTP services. Okay, so let's now actually walk through the code here. So let's ignore the imports for now. We have a Tokyo main and async FN main. Okay, great runs on the Tokyo runtime that we can mostly ignore that. We initialize the tracing subscriber. We'll almost certainly do a de-crusted on tracing because there's so much to talk about there. So I'm not really going to talk a lot about the tracing parts of this as we go through AXM because we'll be just be handling that separately. There's nothing or there's very little that's unique about the integration between AXM and tracing that we need to know about here. But we set it up just so that we get log output in this case. Okay, so the main thing that we, or where we start with the main actual AXM logic is we create a new router. And a router is the sort of primary entry point in your application. It is the thing that when a request comes in, the router determines what function ultimately gets called to handle that request. At router has a bunch of routes. And so in this case, you see you have a route slash and when a request comes in for slash, you'll see this bit on the right which says get and root. Root is an asynchronous function that we defined ourselves right here. So it's just an asynchronous function and it just returns a static string. And so what this implies is when a request comes in for root, then reply with this string right here. And in fact, if we do something like curl verbose, you'll see back that the content type here is just text plain. It's not HTML, it's nothing like that. It's generally just the plain text hello world that we get back. So that's if we do a get request to root. The get here, if we scroll back to the imports, comes from routing, colon, colon get. So this is a method matcher. And in fact, if you go back and look at AXMs, if you look at routing and see the router sub module, no, the router sub module, you'll see that there are a bunch of these. So there's options, patch, post, put, get, delete, trace, head. So all of the sort of HTTP verbs are treated as separate method routers here. You can also use any, which if you wanna say, I wanna take any method under this path. And in addition, you can just create yourself. You can create one of these method filters that allows you to say, get and post, for example, all get passed in here. If we try to use one that there's not a route for, so if we try to do something like X post, and I still want verbose, please, then we get back four or five method not allowed. So AXM tries to follow the HTTP standard here where if the route exists, but the method, the HTTP verb you tried to use does not, then you get four or five method not allowed. If we try to access a path that doesn't exist, so if we do foo, then instead we get a four or four not found because that path does not exist. You can customize this if you want, but the default behavior is basically to follow the spec to the extent that it's reasonable. And we'll talk a lot about how exactly this sort of seemingly magic works. We can just have a random basic fn and it just works when you pass it to get. You'll see there's another route here, which is to slash users that goes, requires the post method to be used to the post HTTP verb and that goes to the create user function. And the create user function, you see has a bit more heft to it rather than the root function. Create user takes arguments. And if you just read this naively, you can sort of guess what it does, right? It says that it expects that the body that's provided to this request is JSON and that the contents of that JSON deserializes into the create user type and create user is just something that derives deserialize here. And that's just an argument to the function. You might think this looks weird, like why is there something on the left of the colon? This is just a structure pattern matching. So you could do this and then payload is of type JSON. So we could do inside of the function payload equals payload dot zero. If we look at the JSON type, it is just a tuple struct with a single field in it. So like this is the dot zero. So there's nothing to unwrap or anything. But rather than having to do this, let payload equals payload dot zero inside of the function, we can use pattern matching right up here to deconstruct that tuple struct directly in the argument list. You can do this for any function. This is not an axon feature. It just happens to be really useful in the axon case. And then you see the create user returns a tuple of status code and we see this JSON object again with user as the parameterized type to JSON here. And as you would expect, this means that it returns a status code, which is the status code here comes from the HTTP module of axon. And it defines all of the standard HTTP status code. In fact, I think it in fact is a re-export. I'm not entirely wrong. Yeah, so the HTTP module is just a re-export of the HTTP crate, which just has definitions for all of the different like verbs and methods and things in the HTTP standard. So all the status codes are in there. So as you might expect, returning a tuple of these two means that you get to set both the status code for the response and you say the body of the response should be the JSON encoding of a user struct. And that just kind of works. Like you just give a tuple saying the status code should be created and the body should be this but JSON serialized. If we go back now and we try to post so we'll do data and what's the data it expects? It expects a username and we do this, oops, users. It says unsupported media type because we didn't declare that the content type of what we sent in was actually application slash JSON. So we do dash H, content type application JSON. Fail to parse the request body. That's because this is not valid JSON. This is valid JSON. So you see now I passed in this JSON blob. I told the server that the content type is JSON and you see the request we sent now has application JSON as the content type. The response we get is indeed 201 created. So the status code that's in the handler, you see the response content type is automatically set to application JSON because it understands that if you're encoding a JSON response as the body, you should also set the content type to JSON. Oh yeah, there's also dash as JSON I think instead of data, but it doesn't really matter. And you see that we indeed got a JSON body back that has the encoding of that user struct that it produced. Okay, so this is just roughly how you write AXM handlers. Questions about this before we dig into how this actually works because it's pretty cool how this works. What happens when the payload doesn't match does AXM return specification to be error codes? I think it just gives bad request. So you can see this up here where I didn't correctly encode username with double quotes at like it basically wasn't valid JSON. This would also apply if it was valid JSON but it wasn't a valid serialization of the struct that we tried to deserialize it into. Then you just get a 400 bad request. And you see here by default, it includes a description of like basically a debug print of the deserialization error. You can turn that off to tell it to not sort of echo that back to the user if you're not running in debug mode, for example. In fact, it might even do this by default if you run it in release mode. Then I don't think it prints this error. Is it mandatory to have the status code first and the return tuple? No, it's not. In fact, as you'll see soon, the return type here can be a lot of different things. So I can do this. Like so, restart the server. And now if I post this, I get a 200 okay back instead of a 201 created. So it's polymorphic in the return type as well if you want to think about it that way. I'm interested about the variadic generics design pattern they use for services. Well, we'll get into all of that later. What happens when there are route function panics? So routes aren't functions. Oh, you mean with the handler panics. So we can try it. So if I panic in here, what happens? If I run this send request, I get empty reply from server and we got a panic. But what's important here is that these panics are isolated per Tokyo task. So if one Tokyo task panics, it does not panic the whole web server. It doesn't even panic that handler. So we can run other handlers. If we run that handler again, then it'll still run and it'll just panic again. So they're isolated from each other. So you can panic in there. It's just a panic message will be printed. Are the get or post functions returning functions themselves? Yes, we can go look. So this is generally what I recommend people do if you have questions like this. So the getter here is actually returning a router or a method router. And method router implements, if we go down here a little bit, method router. Oh, where is what I want? Now I can't find the thing I want. It basically gets, so the way that method router actually works is it internally contains a sort of map based on the method of the incoming request. So it's basically a middleware. When it receives a request, it'll look at its internal map from method to handler and it will look at the method of the request and it will just call the appropriate handlers handling of that request. So just think of it as like a mapping, let's do it this way. So the request comes in here and then it just gets routed to the right handler. So the request then goes into say this handler here for get requests. It produces a response and it goes back up and back out. And this is where we'll get into the tower service trait for how it does middleware like this. But the router is really just a thing that holds many handler functions and chooses which one to call depending on the properties of the request. Is there a way to return that JSON serialized object but force the content type to be something else? Sure, so if you look at the kinds of things that you can return, which is in the response type here, you can return all sorts of things in response. You can return something like append headers which lets you append additional headers to the response. You can fully customize the response if you want as well. So yes, you can add that here. And in fact, you can do this. So you can now make this a three tuple where one of the things that you're returning is at append headers. And again, we'll get into exactly how that works. I'm just trying to sort of get at all the top level questions before we dig into how this works under the hood. Can you explain the extractor syntax in the case of query parameters? Sure, so if you want to take a query parameter, you do this. Anything that implements DC-realize here really, but hash map string string is a common way to DC-realize parameters. And now params is a hash map from string to string. And inside here, I can do params.getmyparam to get the value for the query parameter myparam. And query here, just like JSON, is just a tuple struct that comes from the axem extract module, which has extract query. JSON is also defined under extract. Does it throw an error when a route function has no arguments, but you provide a request body? No, so that's what root does, right? It has no arguments. It doesn't read any, the way to think about that is it doesn't read anything from the request, but it produces a response that is a body. It's no error. Oh yeah, someone pointed out that if you have a panic here, there is technically a middle where you can put in that catches panics in the inner handler and turns them into HTTP error responses, rather than just getting like no body connection closed kind of thing. So that's something you can do with do. We're not gonna dig into that here. It's just worth knowing. Okay, great. So now we have a sense for how you structure these handlers and the kind of things you can do here. Let's now dig one level deeper. Oh actually, there's one last thing I wanna talk about which is starting the actual server. So once you have a router, you have this thing, they call it an app in the example, you create a socket adder like where you wanted to listen and then you call axem server bind and you provide that address. Axem server is really just a hyper server, as you can see here. So this is just reusing the hyper service stack and bind just, this just returns you back a hyper server. Specifically a hyper server builder. And then when you actually want to start serving requests, you call dot serve on that builder. So if I go to the hyper docs, then go to server and go to server. You see the bind returns a builder and builder has a, where are you, has a serve method with lots and lots of trade bounds. But essentially it takes a thing that can make a service. And what that means is every time a new connection comes in, it calls this thing to produce a service, a service being an implementer of the service trait. We'll get to the service trait in a second. And so each connection gets its own instance of a service that gets called for every request on that connection. And so serve basically never returns. Serve is just like start the loop to listen for connections now. And anytime there's a new connection, create a new service. And anytime there's a new request on a connection, then pass it to that connection's instance of a service and get the response back and then serialize that back over that same connection that the request came in on. And all of these bounds are all about making sure that you actually have an HTTP connection that you can read and write from, that the errors are sendable across thread boundaries and all that. And then we'll get into exactly what a service is. Cause that's sort of the natural next point to get to. And if we, oh, how did I end up so far down? As you see here, when you call serve, you get back an actual server object, which is a future that you await. And when you wait that, that await essentially never returns. It would return if the hyper server itself panicked, which basically it does not. There is a with graceful shutdown, which lets you take a future here. And when that, when this future returns, this future, when this future here resolves, then the server is going to shut down gracefully. And this await will resolve and this unwrap will finish with an okay. And so then you might be able to run additional commands after this. So commonly what you would do is do something like set up a one shot channel where, in fact, we can just do this. Tokyo sync one shot channel. And then this would be rx.await. And so now, so this will now, this graceful shutdown future will resolve the moment we send on shutdown. And so now you can pass this, the sender part of this channel to like a control C handler or whatever you might want to do. And so whenever you want the server to shut down, you call shutdown.send and you just send an empty thing. And then the server is going to shut down on its own. This future will resolve and you can continue. The other way in which it resolves is if it encounters an error. Like for example, it tries to call accept to get a new connection. And like the socket itself is broken in the kernel and you get it or like the, I don't know, the address has been, the network interface has been janked out. So you can't accept any more connections. So the kernel returns an error to hypers accept call. That error would also propagate up here. But let's get rid of that. That's just extra noise. Okay. So that's how you set up an AXM server. That's sort of the glue that puts all the pieces together. Let's now talk about tower services. So it says pretty prominently in the tower and the AXM docs that, I don't know if I can find the, maybe not here, but in AXM over here. It says, I thought I saw the word unique here. Has it gone away? Ah, the last port, the last point is what sets AXM apart from other frameworks. That is this point. It takes full advantage of the tower and tower HTTP ecosystem of middleware services and utilities. AXM doesn't have its own middleware system, but instead uses tower service. This means AXM gets timeouts, tracing, compression, authorization and more for free. And by free here, they mean via other things that already implement this trait independently of AXM. Like people don't implement the tower service trait because of AXM. They implemented for all sorts of other reasons, but if you're using AXM, you can make use of those implementations, which allows you to do things like share middleware with applications written using hyper or tonic. Okay, so let's now look at the service trait from the tower crate. So the tower crate mostly exists around this trait. Like it exists to define this trait and to allow an ecosystem to develop around that, this service trait. And the service trait fundamentally is a trait that takes in requests and asynchronously produces responses. And these are tied to HTTP. They can be requests of any kind, responses of any kind. And the idea is that when you call what you get back is a future, where that future types output is a result that is either a response or an error. It's almost the most generic way you can think of expressing a service, which is the intent, right? That you have this trait that everyone can implement if they have anything that deals with requests and responses. You'll see there's a poll ready in a call here. We're not gonna dig too much into why those are there, but the basic thing, in fact, there's a great article where can I find this? Blog. No, that's a lie. Where is that article? Maybe it's under Chokeo. Where's the article I'm thinking about? Google. Tower service trait async fn. Where is that article? This one. It was from the Tokyo blog. Okay, so this blog post is really good. It basically goes through how we arrived at the exact structure of the service trait. I'll send a link here in chat. But the basic way to think about service is that a tower service is roughly equivalent to something that implements async fn request to result of response and some error type E. Let's in fact do this for some request and E. So it's sort of generic over request and E. Like you might have a function that you might have a type that implements that supports multiple different types of requests. For example, you can implement this trait multiple times, but the very basic premise here is you have an asynchronous function from request to a response. So that's how to think about tower services. What's really nice about this is that they compose. So imagine that you have one function or one type that implements the tower trait. It has an asynchronous function from request to response. And then you have another function that does the same. So you have A and B. You can now define a C that takes a request and returns a result of response to E. And what it will do is it'll call A of B of R. No, I'm lying. I'm lying. They don't compose in this way. They compose through the use of a separate trait, which we haven't talked about yet called layer here. Where is the layer trait? So separate layer trait that lets you take services and sort of merge them. So you can in this case say A dot layer B. And what that produces is a thing that is now that implement service that applies both A and B to your request and returns the response back up. And so you can pass in here the request and that's how layering kind of works. Again, we're not diving too deep into the mechanisms of tower here, but I more wanna give you the mental model for what these services are. They are ways to express asynchronous mappings from request to responses. And every AXM handler is fundamentally one of these or rather to be more accurate, gets turned into one of these. Okay. So that's enough stuff you need to know about service in order to follow the rest of this, I think. So that thing gets us to the question of, well, how when a request comes in, how does it actually end up calling root and how does that get turned into response? Like we have this function, but how does that turn into like something that takes an HTTP request and turns it into an HTTP response? Like how does this transformation happen? Is somehow transformed into by AXM, right? Somehow that happens. And somehow that also happens for this. This function, which is constructed very differently, somehow also gets turned into that same tower service, that same kind of asynchronous mapping, right? So that's the idea here, that you want all of your handlers to eventually turn into one of these things. And if we go back to something like a router, right? So we were talking earlier about, what does get really do? Well, really you can think of get as it gets a request in and it's eventually going to return a response. And really it has like a map somewhere of all of the inner handlers. In fact, maybe even let's reconstruct method handler is H, where H is some handler type is to do get and post for now. And so we're gonna implement service for method handler. And service takes a request and response is gonna be HTTP response. I'm not gonna write this accurately but just accurately enough that it's fine. And so when a request comes in of type HTTP request, then we have to asynchronously produce a response. Well, what we can do is we can match on rec.method. And if it is get, then we call self.get of rec.await. And it was post. So that is what a method router is. So when you call the get function, the get router here, what you're really doing is you're constructing one of these method handlers and saying that the handler for get is the function that was passed in and the handler for all the other methods is return the method not allowed error. That's the way to think about it. Like there's sort of a fallback here that's also a handler. And so all of these are really an option handler. And so this is like if self.get is some, this is if self.post is some, right? You get the idea. And if none of these really go anywhere, so for anything else, then we call self.fallback of rec.await. And the fallback by default is return a 405 method not allowed. So that's what a method handler is. A method handler is really a service that wraps a bunch of other services. These h's here are themselves asynchronous functions from requests to responses. And then now you can start to sort of see how this composes, right? So imagine that, imagine you have this, you know, the outer router, it needs to know, well, how do I route this path versus this path? Well, I might have something like a path handler. And it is just a paths, which is a hash map from string to handler. And it too has an input like this. And again, h here is just a service HTTP request really. So we could even do, if you wanted to think about it this way, something like a box din service HTTP request. And response is HTTP response. So in fact, if we wanna be nice to ourselves here, type h equals that. And now this applies to the example further down as well. And again, of course, I haven't actually imported any of these. Like this is gonna produce a bunch of errors, but you can sort of glance at it and see that this is what makes sense. So if we now implement this for path handler, then it, instead of matching on the method, it's gonna match on the path. Or rather, it's gonna do self.paths.getwreck.path. And we're gonna, and if let some handlers equal to that, then handler of request.o8, return that. Otherwise, self.fallbackwreck.o8. There are lots of errors here, but hopefully you get the idea. Let me get rid of some of the errors that might actually make it easier to read. So a path handler is also just a service that takes a request and returns a response, but internally has a bunch of service and it chooses which one to forward to. That's how routing works. And it's really nice that you can just compose them in this way. You can imagine constructing your own routing function that just has some other logic for how it chooses which handler to call. But this still gets us to this box-dn-service bit. Like what this really means, if you think about it, is that the argument to get here has to implement handler. Like it has to implement service from request to response, at least kind of, right? But here we're calling it with one function that's just an asynchronous function from nothing to string. Here we're calling it with an asynchronous function that takes some like JSON object as an argument and returns a tuple that has a status code. Like how does that mapping happen from the handler to a tower service? If we go further down, right? Like this is somehow transformed into H. That type alias we had further up. Or if we wanna copy it so that we see it again, it's somehow transformed into this. How does that happen? And similarly, down here, this one is also transformed into that same type. How? And this is where we get into what I think is the sort of, maybe the nugget of axom. So let's now dig ourselves back up a little bit to here. And go to handlers. In axom, a handler is an asynchronous function that accepts zero more extractors. We'll talk about extractors later as arguments and return something that can be converted into a response. So we already see the sort of shape of this, right? The thing that you return has to be possible to turn into a response, fair enough. Handlers are where your application logic lives and axom applications are built by routing between handlers. That all makes sense to us. Okay, let's look at the handler module. Here they just give a bunch of examples of them, but let's go down and look at the handler trait. Okay, the handler trait is kind of close. Right, so a handler is something that, okay, it has a future, you can call it with a request and state, we don't know what state is yet, and returns one of these futures and the output from that future has to be an axom response. Okay, so it takes an axom request or an HTTP request and returns an axom response. This looks a lot like service. And in fact, we see here handler service, we look into handler service, handler service, slow that down a little bit, implements tower service. Okay, so there's like some path here where you can start to sort of see the shape that if we go back here to the method signature of the routing, if we go to, no, that's unhelpful, routing, we could get. So get takes a handler, h, where h implements handler. So what that means is this asynchronous function from root to static string implements handler, otherwise we wouldn't be able to call get with root, right? Up here, we call get and we pass in root. So root must implement handler. Otherwise we wouldn't be able to call this function because that is the trade bound of get. And similarly, post is passing create user. Let's assume that post has the same trade bound, which it does, then that means a create user. So this function also implements handler somehow. Okay, so this handler trait, this whole thing is somehow implemented for all of our random functions. And when we change this to have like query with params here and hash map, string to string, and we added in what append headers here. When we made all these modifications, this thing somehow still implements handler. How does that work? How is it that almost any function we write here, this is not quite true, but it feels like anything we wanna put here just kind of works. It somehow is a valid handler. Well, now we get to the key. So the handler trait is automatically implemented for a lot of types. And if you look carefully, you see there's a pattern here, like it's not random types, it's for a bunch of tuple types with an increasing number of tuples. This sounds suspiciously as though there is a macro at use. And of course there is. So if we go into the source here and we go to handler and we go to mod, we scroll down. Okay, we see some macro magic here. So you see there's an implementation of handler where the first thing here is at the empty argument list basically and S, we don't know what S is yet. It's state, but we haven't talked about it yet. If you have something that is just like the empty handler, then you do nothing. You just, like if it takes no arguments, then you just call it with no arguments and you turn whatever comes back as a response. That's fine. And then we have this ugly macro business here and we're actually gonna read through this macro in a second. If we scroll down here and you see, oh, we're right here, all the tuples, impulse handler. So it's passing a macro to another macro. Okay, what's all the tuples? Let's go over here to all the tuples. Aha, all of the tuples indeed. So you see here, this calls the macro those passed in with macro, with tuples of increasing length. Why? Why does it do all this? Well, if we go back to handler, this first type, this T, that we see here, let's look at the, where is my handler service? Ba, ba, ba, there's so many trade bounds. I'm gonna find, right, so if we scroll down here, let's look for something that is a meaningful length like this one, MT1, T2. So handler is implemented for any F, F is generic here, for any F, where F implements F and once. F and once is, we talked about this in the cross of rust on function pointer types. So a F and once, that takes two arguments, T1 and T2. Notice that this matches with what this first type is in the handler trade. So any function, this handler trade is implemented for any function F that takes two arguments and returns a Fute, Fute is a generic, where Fute is a future. And the output of that future, the thing that the future returns, is a REST, which is a generic, where REST implements into response. And into response, as you can guess from the name, is something that can be turned into an HTTP response. And then you see the last bit, where T1 and T2 implement from request parts and let's now scroll down to something that's much longer to see if the pattern holds. Okay, so here we have something that takes, I guess 13 arguments. Okay, so just handler's first generic parameter is M and then all of these type parameters. And F implements handler, if F is a function that takes 13 arguments and returns a future, where the future's output is REST and where REST implements into response and where all of the types that are passed, all of the types that are arguments to that function implement from request parts. And here too, you see the last one is special. The last one implements from request and not from request parts. We'll talk about that in a second too. But see how the pattern holds. Like the handler trait is automatically implemented for functions of basically any argument length, as long as all of the arguments implement from request parts and the last one implements from request and the response implements into response. So let's do a sort of basic test here. Okay, here we have an asynchronous function. Okay, so does it return a future implements future? Yes, check. Does future output implement into response? Well, let's see, what is into response? Into response is just something that can be turned into response where response here is HTTP response and it's implemented for the unit type. Okay, whatever parts is, let's ignore that for now. It's also implemented for, and here we see this sort of macro magic again where it's tuples of basically any length. So this trait is implemented for tuples of lots of different lengths where all of the individual components of the tuple themselves implement into response or into response parts and the last one implements into response. And the separation between parts and the last one being different, we'll talk about it in a second, I promise. And you see that there's a special case here for status code. So if status code is first, status code I think does not implement into response or into response parts. Let's see. Yeah, so it does not implement into response parts. So it sort of handled specially where status code can be the first thing before the other arguments, but all of those have to implement into response parts or into response if they're the last one. Okay, so then the question becomes, well, here we only return one thing which is the static reference to a string. So does static reference to a string implement implemented into response? Yes, it does right here. So the inter-response trait is indeed implemented for that type. And if we look at it, it just is the same as a cow borrowed, okay. And cow borrowed, we see the implementation for here and it inserts into the headers that the content type should be text plain UTF-8 which is what we saw when we curled against that endpoint. So that's where this comes from. And full from, this is the body type. Full here is saying the entire body is available straight away. It's not like a streaming body or anything. And we turn that into a response. Great, so now we understand sort of where that response gets built up from. So does it implement response? Yes, it does. Check. Does every argument except the last implement from request parts? Well, trivially check cause there are no arguments. Does the last argument implement from request? Well, there is no argument, so yes. And so that's how this ends up working. This one ends up using the Imple for Handler when the argument list is empty. So right here, this implementation is what we use and that implementation is just gonna call this function. That's all that implementation of the handler trade does when that is what the signature is. And then it's gonna call into response on the response here, which calls that function we just looked up with that implementation of into response that we just looked up and that's how you get a response. Great. And there is, it's not actually variadic generics here. Like it's not actually generic over functions of any argument length. It is macro expansion, right? There's all the tuples one that we saw. So it will only actually work for handlers with up to 16 arguments. If you have more than 16 arguments, it will not implement the handler trade automatically. Then you would need your own type that you implement handler for. It would be nice if this was genuine, this sort of supported by the language directly, but for now this is a trick where you don't really notice that it's a trick. Oh yeah, so tuples also implement from request. So this also means that any one of these can itself be a tuple. So you can actually construct larger ones if you're willing to do some nesting in there. Oops, that's not what I meant to do. Okay, so now we have looked at root. Great. Let's now look at a more complicated one. So if we go back here, we do the same sort of checklist. Does it return a future? Yes, because it's an asynchronous function. Does the output implement into response? Well, this is a tuple where the first thing is status code, okay, great. The second thing is append headers and the last thing is JSON user. Okay, append headers, does that implement into response or into response parts, right? So remember the rule here is that we need to, that a tuple here implements into response. If the first thing is status code, the last thing implements into response and the middle things all implement into response parts. So does append headers implement into response parts? I'm going to just tell you the answer is yes. Does the JSON type implement, oh, why did I scroll? Does the JSON type implement into response? Well, let's go look. So we look at the JSON type. No, not that one, but the response JSON type. Okay, so what does it implement? It implements into response if T implements serialize, which is what you would expect, that if you say that you return a JSON of some type, that thing is serializable so that you can, it can be passed to cert adjacent and produce the right thing. And so that might make you wonder, well, let's finish the analysis and then we'll do the wondering. Okay, so this response type does implement into response, great. Does every argument accept the last implement from request parts? Well, so that would mean that query needs to implement from request parts. Well, let's go look at query. So query is what's known as an extractor. We'll talk in a second about what extractors mean. It does indeed implement from request parts. If you want to look at it, like I recommend going to look at this, like what from request, the implementation of from request parts for query is really just calling, let's do from, try from URI. It extracts the URI from the request. Parts here is just like the request but split into its constituent parts. So the URI from the query, it extracts the query string, so the things that follow question mark from there. And then it passes that to CERDI URL encoded, which decodes the stuff that comes after the question marks into a type using the deserialized trait. Deserialized owned here. And so this from request parts implementation of query just extracts the URI, extracts the query, deserializes that into a T, and then that's the thing that it extracted. So it does implement from request parts. Great. And then the last part of our rule is does the last argument implement from request. So does JSON payload implement from request? Well, again, let's go look. So if we go back here, we go to JSON. It does implement from request as long as the type is deserialized, so that's fine. Look at the source here. Well, it checks the implementation of from request is the JSON content type set in the headers, then read all the bytes out of the body, and then deserialize with sturdy JSON. And that's kind of it. Like that's all you really need to do in order to extract JSON from request. And so at this point, we actually know what an extractor is. An extractor is something that implements from request parts or from request. And so it can take the parts of a request all the, or the whole request and turn it into something that can appear in the argument list for a handler and have it still, you know, implement the overall handler trait. Okay, hopefully that this journey we've been on so far makes sense. Now you hopefully see how the handler trait sort of fits together here. The last part that was really useful to me actually when I first found it is let's go look here at this one. Okay, so this macro right here is what I would argue is the heart of AXM. Let's go to like 223. I don't know why GitHub has started doing this, but it will only highlight lines if I change the URL if I refresh. Okay, so the ImpleHandler trait is the thing that actually implements the handler trait for all of these different function types. So the argument list here is all of the ones that are in the square bracket. Square bracket is a repeated list of types. Remember, it's the list of arguments to the handler except for that last one. And then the last type, the type of the last argument is sort of kept separately in this last meta variable. And so we implement handler for F where F is an FN once and this here is the macro expression for saying, repeat all the types in the argument list. So it's implemented for, you know, FN ones that takes two arguments, for example. These bits are fine. Res has to implement into response. All of the types have to implement from request parts except for the last one that has implemented from request. So this is really just the thing that generates the Imple blocks we looked at earlier. But then let's look at the actual call here. So what does it do? Well, this is the sort of equivalent of the service trait, right, of we get a request in, it splits that request into parts and into parts is really it splits the body of the request from everything else about the request. So think like the method, the URI, any additional headers, all of that goes into parts and then the body is kept separately. And the reason it does this and the whole reason why from request parts is separate from from request is because you only read the body once. We don't wanna keep the body like in reference counted memory or something. So that's why they sort of enforce this from request parts only gets access to the metadata of the request, the parts. And the last thing, the thing that gets to implement the from request trait that one is also given access to the body. And so we'll actually see that this means that you can't swap these two. If I get rid of this and I import Tushmap. Ooh. Yeah, up here. So it says, well, this is a relatively unhelpful error as you'll discover many of the errors when you use traits in this relatively advanced way are annoying. But it says the trait bound function, JSON and query implements handler is not satisfied. And then it tells you about some other random types that are satisfied. And what it doesn't tell you which is a little bit annoying, right? Is that it is because in the arguments to create user this implements from request, this implements from request parts and that wouldn't work because when Axem runs the handler, it's going to run all of these things, all the argument extractors first, and they don't have access to the body and then it'll run the last one which does have access to the body. But that means that JSON here is not given access to the body. So it would need to implement from request parts, but it doesn't because it needs access to the body and therefore only implements from request. There is a thing that can make this a little bit nicer. So there's a, I think it's mentioned here. Yeah, here. So at the very bottom, there's this thing called debug handler. And debug handler is an attribute that you can add to your handler and it will generally give you better errors about what went wrong. I don't know whether it'll work for this case, but let's see. Uh, use axem debug handler. And I need the macros feature. See what it does. Let's see if it can actually help us with this one. JSON consumes the request body and thus must be the last argument to the handler function. Great, so it gave us more information. It's basically, it's a procedural macro that looks for patterns in handlers that are commonly erroneous and tries to give you better errors than what you would get from the trait resolver, right? So by using that debug handler, it actually gives us a better description of what went wrong. And it correctly identifies that these need to be swapped. And now everything's happy again. Okay, so let's go back to the, nope, that's the wrong page. Let's go back to the code we were looking at over here. Okay, so what does call do? Well, it splits the request into the metadata of the parts and the body. Let's ignore state from now. And then this here is a macro rule for repetition and it's repeating over dollar TY. So that's these, that's all the arguments except for the last argument. And for each one, it calls from request parts, passes in a mutable reference to the parts and the state which we ignore and assigns that to a variable called TY type. This is just a variable name because all types are valid variable names as well. It's a little bit sneaky. And if any of the from request parts return an error then we turn that into a response and return that from the overall call. We have that be the HTTP response. So this is where you would get errors like if you tried to deserialize the query string into some struct and it doesn't have the right fields then what you'll end up with is that call to from request parts for query will fail with an error and it will be turned into a response right here. And as a result, no other parts will be exported from the response and the handler will not be called. So this just calls all of the from request parts for all the arguments except for the last and then it reconstructs the request from whatever is left of the parts and the body that are originally extracted and then it calls the from request trait method for that last type. So it actually gets access to the full request that we reconstructed here and not just to the parts. And afterwards it then calls the handler function with all of the arguments that it extracted, right? All of these things that we called from request parts to and that last arguments. So this is where the tower service call turns into a function call to the handler with all of the arguments in the right order with the right types. That turns into response, we just await it because we know it's the future and that response we call into response to in order to turn it into the appropriate response at the end. So that's the entire flow, that's sort of the heart of how do you take requests and turn them into these handlers? And you'll see a similar thing if you look at into response. So if we go over to into response here and we look at the implementation of response into response for whatever this one for example there's some random tuple. That is in an axon core response into response. Look at it on GitHub instead maybe response into response. Where's my macro down here? 401 to 490. So implement to response. This is again using all the tuples no last special case. So that's the same as that all the tuples except for response is the last one doesn't get special case. So we implement up here, we implement into response for tuples of basically every length. And the way we do that is we take the response that we get out of the last one. So last one is kind of special, right? That's the thing that gets to produce the body. So similarly to from for the arguments the last thing gets to consume the body for their quest for the response. The last thing gets to produce the body for the response. So we call, we get the response from the last argument of the tuple. We call into response on it. So that generates an actual like response type instance. And then we construct the parts from there and then we call into response parts for all of the other arguments. And they all get to, like we pass in the parts and they return the parts. So that way they can do whatever modifications they might want to. And that's sort of essentially you can think of it as we construct the parts with the body and we sort of pass it through all of the inter-response parts that are the other tuples in the type. And what we end up with is a response where all of the intermediate handlers all the response modifiers have gotten to act. And that in turn turns into the actual response. Okay, so we can look at the others too. Like the others look basically the same. This is a special case for if status code is one of them. This special case for if one of them is HTTP parts. So parts just means you've already assembled a bunch of things that are going to go into the HTTP response anyway. Those just get stuffed in there. But those are not all that interesting. And you see here for the R here at the end, R has to implement inter-response. Which is because again, the last argument is the one that gets to produce the body. And so we can see that here too. If we tried to say, let's do append headers. Let me bring that in here. I think I can do this. I just want some type that it's happy with. Right, and then this can be append headers of. So in this case, I'm not appending any headers. But here you would get a similar kind of error if I tried to do this. Because append headers is, and then this. See now this won't accept it again. And I get this horrendous error. Let me do debug handler. And let's see if debug handler can be helpful here too. See what it says. Our debug handler wasn't helpful here. Well, we do get this error, which is somewhat helpful. The trade-bound status code Jason user append headers implements inter-response is not satisfied. The following other types implement inter-response. And it gives you a bunch of those and 60 others. So that's not super helpful. But the actual reason for this error here is that again, append headers can't produce a response. It can only produce parts of a response. It doesn't produce a body. The thing that produces the body is the thing that goes last. And I think, you might think maybe it should go first, but I think it truly is to match the order of the arguments in the argument list where the body type has to go last. So they do the same for the response. I don't know whether I agree with this, but that's neither here nor there. And the reason why it's questionable to my mind is because all of these get applied in order. If we look at the expanded macro over here, you see that all of the type parameters that you pass in, they all get to be applied to the parts in the order that they appear. So the pass through here happens left to right, except for the body thing, which is at the end, but actually is applied first. So the ordering gets a little weird, but I think it's for symmetry with the argument list. Okay, so now we have the stuff for how handlers get called, how this all turns into a service, like how routing works, what the service trait is. And so we're getting pretty close to understanding the sort of whole Belgian waffle, I don't know, the whole cake. But there's at least one bit left. But before I go into the last main bit I wanna talk about, let's do questions, because I've been talking for a long time, and I'm sure this has a bunch of you going, I did not follow half of that and I'm lost. The body is also generally the last thing actually written over the wire, so maybe mimicking that. It's true, like I think there are arguments for keeping it last. The reason it's weird to me is because that type gets invoked first to produce the response type, and then the others see the response after it produced it. So there's like the order in terms of time in constructing the response is last, then second, then third, then fourth, and fifth, which is a weird order to me. The idea is that response tuples is that you shouldn't be able to accidentally overwrite the response. Yeah, you only get to overwrite parts of the response, but not the entire body, for example. Should I use tuple as a return type for all the handlers or use something else like impulse into response if it works that way? Yeah, so when you write the response type here, you can either give the concrete type or you can just do inter-response. This is what I usually use. Oh, right, and then I need to undo the mistake I made, the intentional mistake I made, but nonetheless, ooh, it didn't like that. Let's ignore that for now, it's not important. Yeah, so you can just use impulse into response here, and that way, if you decide to add another piece in here, it just kind of works. The main thing to be aware of here is even if you write impulse into response, which is what I almost always do, you're still under the restriction that you can only have one return type. So you couldn't do something like, if RANDUS4, then return status code not found. Like this won't work, let RANDUS6 and if RANDUS4. Because even though you used impulse into response, Rust still requires that there's a single return type for the entire function. And so it's complaining here, and you'll see this better in the actual output. It says mismatch type. It expected the return type to be status code, but here you're trying to return a tuple, which is not the same as status code. So you don't get to do this even though you tried to say, I only return something that's into response. So if you have a type that has to diverge like this, what you do instead is you do this, and then you do dot into response here, and you do dot into response here. I take that back, I want this to be response response. So here I'm saying, I'm just going to return the entire response. And the way I'm going to do that is by calling into response myself. Because that way there's only now a single type for the entire return type, but I get to still diverge internally. And response obviously implements into response. Well, I see the reason why status code, we're very lucky we have David who's basically the author of AXM in the chat. So I'm getting a lot of useful insights there. So the reason why status code has to be first in all the tuples is precisely to avoid you accidentally setting it twice. So that if you set it here, you sort of know that that's going to be the one that's used everywhere else. That's used for the actual response. What about having variadic types in Rust to not use these macros? Variadic function or variadic argument lists are really hard to get right into design into the language. So I wouldn't hold my breath for getting it for probably years. And I don't think we should wait until we get them before building things like this because they're useful. So that would be nice, but it's a much longer forward-looking solution. Okay, so the last thing I want to talk about I think is state. So let's, I want to start by going down here for sharing state with handlers. So there's an extractor. And again, remember extractors are just things that implement from request parts. That's the only requirement for something to be an extractor is that it can extract things from either a full request, including the body or from the parts of a request. There's an extractor called state which works a little bit differently. So when you want to have shared state between your handlers, I think something like a pool of database connections or even just a data structure behind a mutex or whatever it might be. Like you just want to, or a configuration like anything you might want to share here represented by app state. You construct the shared state, you construct the router, you call route and you call dot with state. So this basically hands that state into the router so that it is going to pass it to all the handlers. And we'll look at how that works because it's actually really interesting. Once you call dot with state like this, you're able to now in your handlers declare in your argument list, I want state and that is one of these. And so let's go look at what that actually looks like. So let's say for create user here, we also wanted, I can't use that. Can I? Arc. They just use, yeah, fine. So I can do this and then I can up here define, you know, struct app state. And it would normally have fields but it's not going to have them right now. And then up here for my route I'm going to call dot with state arc new app state, whatever fields that might have. So that's all it really takes. Now every handler here, so I could do the same thing up here for route now. So I could say route here is also going to get access to state. And in practice what that means is they're going to get separate clones of the state because that's why the arc is here. Because if you have multiple handlers, like those handlers might be called in parallel, you might have multiple handlers that all want access to the state. And so it needs to be cloned in order to be shared. You can imagine using like a static reference or something, but this arc is an easy way to get something that's trivially cloneable in order to share them. So the requirement is that for anything that you pass in with state like this, it has to implement clone. So it's pretty common to see arc immediately following state. And so now inside of state, this can access state and so can this one. And they are referencing the same state because if you clone an arc, you get a reference to the same thing. But how does this work? Like so far when we say from request parts or from request, it's all been about things that are in the request. How are they getting access to this state thing? Well, I'm gonna ignore things like closure captures. You can read that on your own time. If you want to, state is the sort of most common way to go about this. So the state extractor also implements from request parts. If we go look carefully at from request parts, this is an async trait. That's why it has a little bit of a weird signature here. So let's look at the source instead. From request parts is generic over S, S for state, we'll see why in a second. And the from request parts function does get parts, which is the parts of the HTTP request, but it also gets a reference to the S to the state. So this is how the state extractor can be constructed from request parts because from request parts also gets access to that state. That seems easy enough, but this generic is scary. Isn't this gonna end up everywhere? And you see there's some weird distinction here between outer state and inner state. That one is actually just because if imagine that there's like here I want like root state and here I want create user state. So I sort of want them to get specialized states, but root state and create user state are both like part of the app state. So this has, I don't know, root, just root state and create user state. Imagine that this was the structure I had. This indirection here of having two generics and then you require that the inner state which is what you actually need implements from ref to the outer state. So from request parts is past an outer state here app state. And as long as you can construct an inner state from a reference to the outer state, you're fine. Then you can call the handler with the state that it wants. Like you can call root with root state by extracting the root state from the reference to the app state. But this distinction we won't have to think too much about. But the implementation here is implement from request parts of state for state of S. Great, so there's a requirement here that the moment down here where we say, let's go back to having this be app state for simplicity. When I say I want a state of this, what that actually means is that this handler now only implements, remember that these functions only implement handler if the arguments implement from request parts. But state only implements from request parts for this generic state. So that only this doesn't implement, doesn't implement from request parts for any S. It only implements from request parts S for app state or ARC app state really. So this now means that the implementation of handler for root is conditional on S being equal to ARC app state. If there's any other S here, then root would not implement handler for that S. So if we go now back to handler, the handler trait is also generic over S. T is the argument list and S is the state. So what this really means is impel S handler T S for root. This doesn't exist. There's no generic implementation of handler. We root only implements this, right? This is the only version of handler that root implements. So what does that mean? Well, remember how forget? Get required that the handler implements handler for any S. Okay, great. So this is generic over the S. So you can pass in to get, you can tell get what state the handler is going to require. But inference works in both ways. So what actually happens here, and this is we're getting into a little bit of like nuances of the trait resolver. But basically when I call get root here, what really happens is the compiler starts to infer what these types are, what these generic types to what, H, T, S and B. So H is the handler. So that's going to be equal to root. That's because that's just what's passed in. So that's easy. This is the type of the argument. Like it's dictated by the argument we gave it. T is going to be the argument list, which for root is the empty tuple. It takes no arguments. So that's also easy. B is the body, which is a box body for everything in axiom. So it's a box body, which might be a streaming one or an already allocated like full string or byte slice. But the S isn't tied to anything. So what actually happens here is this sort of an inverse inference where the compiler knows that root only implements handler if S is arc app state. And therefore it infers that S must be, so it's not equal to by virtue of being called, it must be arc app state. Because that's the only way that this will actually work. And route, so now we go back to router, router also has an S. Okay, so router is generic over some S. And when you call route, that's a method router over S. And that S is tied to the S and get. So the inference goes all the way back to infer that this actually means that this router, which is S and B, well B is box body, that's fine. But the S here is inferred that it must be app state. So the inference goes back, forget. And then for post, it then goes sort of forward that all of these S's have to be the same. So for post here, they must also be the same. And here is not inference. Here is just, it must be equal to that. Well, so now let's see what happens if we just, let me get rid of, oh, do I want to get rid of these? Yeah, that's fine. I'll get rid of these. No, I don't want to get rid of them. I take it back. Okay, so what does that mean? Well, that means that at the end of this call chain of calling route, what we have is a router where S is arc app state and B is box body. And we know that in order to pass this application, oops, in order to pass this application into serve, it needs to implement tower service. Technically a service service, but ignore that for a second. Well, if you look here very carefully, we implement tower service for a request over B for router of unit and B. So the service trait is only implemented for router if the state is empty. Well, now we have a problem because the, the inference here told us that at this point, S is equal to arc app state because that was inferred from the fact that we passed in root here. That is the only S that works. But in order to be able to call, in order to be able to call serve down here, the sort of S inside of app must be equal to unit. But arc app state and unit are not the same thing. So how do we bridge those? Well, that's where with state comes into play. So with state takes in a state and returns you a router with a different state parameter. And in our case, we make use of that by saying with state and then we pass in the app state and we can just say what state is required next. We can just say with state, this is now unit. And in fact, that's inferred for us because S must be unit. Otherwise this call wouldn't work. So the way to think about the S on router is not that it's the type that you've passed into the router. It is the type that the router is going to require before it can be used as a service. Or in other words, the router with some S that's not unit needs to be provided with that state S before you can use it for anything useful. So which makes sense. We can't use root as a handler until we have provided the state that it's going to eventually see. And so hence this sort of slightly weird but really neat trait trick that makes it so that it's just impossible to construct a router and try to use it to serve requests if you haven't passed in the necessary state. I should raise another question mark for you, which is, OK, we've done all this business. Let me get rid of all of my extra annotations here. OK, so down here is SQL to unit. So how does with state work? How does it accomplish what it does? Because root requires a state. That state needs to make it into the request somewhere. When we looked at this from request here, you see state is equal to a reference to state. And state is just passed into the call in handler. But if we now go back all the way to tower service, which remember is the trait that actually drives all of this. In call for tower service, the only thing you have is a request. But in call for handler, in fact, let me put a handler here. Might be easier. So call for service. The only thing you get is the request call for handler. What you get is the request and the state. Where does that state come from? How does the state make it from the router and into the handlers when they actually get called? Well, we actually found this earlier. So see how on handler there's called this layer for stacking them. And then there's this innocent looking function with state. And what with state does is it takes a handler, anything that implements the handler trait, and it gives you a handler service. So you provide it with the state and it gives you a service. And that kind of makes sense. And let me show you how that actually works. So a handler service holds the handler and the state. And then it implements. Where's the implementation here? Implements. This is tower service now. So it implements tower service request for handler service. Where H is handler. And the call here is handler call. Passing the handler. So this is basically the same as handler dot call, right? Passing the request and clone the state out of ourselves and pass that in. So that's how the state actually makes it in. Is that you have to, in order to turn a handler into a service, you have to pass in the state that it needed. At that point it no longer needs the state. You don't need to pass the state in with every call because it's handled automatically in this input of the type. And so that's what happens for you in router. If we go back here to router. So when you have a router and you call with state at the end there to make it so that it no longer needs its state. Well, router internally has like path routers and fallback routers and whatnot. But if we find the path router with state, let me date that up real quick. Path router. Where's my path router at? Over here. I've seen the method router and method router with states. Oh, that's a state. Sorry, let me dig this one up real quick. Right. So method endpoint here, we were sort of, we ran really deep into the stack. But basically this is the thing that get produces. When you call get, when you call the little get router function and you pass in root, you pass in a handler. What it does is it boxes. In fact, let's look at it. Why not? We have nothing to hide. We're here to learn. We will do so relentlessly. So what does get do? Well, all of these do basically the same thing. Where is my top lever, top level handler fn? Top level, top level handler fn where you at here. So many layers of interaction. I understand why they're there. It just makes me, makes my job harder. Okay. I'm not going to show you then I take it back. But what happens is when you call get, it takes the handler that you get in, it boxes it so that it can store them all like together. And then it produces one of these, what a method filter. So this is the thing that we looked at earlier. Like it implements service. And depending on the method of the incoming request, it chooses which handler to actually call. In the case of the get constructor, it will call that handler if and only if the incoming method for the request was get. And the individual thing that it stores is one of these method end points. And the thing that it stores in there is either none. If there is no handler for like post, for example, then the handler is none. And so it does nothing. Let's ignore route for now. The default state of this type is this box handler. So that is, you know, when you call get root, it takes root, it boxes it, and it constructs a method endpoint box handler, and it sticks that in there. When you call with state, if it discovers that what it currently has is a box handler, then it knows that that handler still hasn't been passed its state because it implements the handler trait, not the service trait. And so when you call with state, like on router, it ends up calling with state all the way down into all of the handlers, including down to this little method endpoint, which then calls into route on handler, which then gets us to into route, which we're almost at the end of the journey, I promise. So into route over here. Here is into route again. It's like over here somewhere. Nope, not that one. No, it is in box. It is over here. Right here. I was trying to find the actual call to it. Actually, I'm going to skip one step. I'm going to skip the dash for skipping it. But when it finds that what it currently has is a box handler and it is provided with the state, it calls this into route thing, which indirectly ends up calling this from handler function for boxed into route. And what that does is it takes the handler, passes in the state. So this is how we go from a handler to a handler service. And it creates a route, which is really just a boxed service out of that and turns that into an erased handler. And a erased handler all the way up here is a handler that you've already turned into a service. So it already has all of its state. And when you do that, it gets turned away from being a method endpoint boxed handler into being a method endpoint route. So a route is a thing that doesn't need its state anymore and provided it. I know this part is convoluted and hairy and hard to follow, but it's a little bit important, which is that the moment you provide the state to a handler, it turns into a service. That's the key part to take away from this. So over here, the moment we've called with state now, now router is a service. And so we can actually after with state, we can now call route again with something that requires some other kind of state. So let's in fact do this because it might actually be useful to demonstrate. So let's say I have route one and I have route two. And route two takes a completely different state. It actually takes a mutex over a bool. Actually, it just takes an arc bool. Why not? So these take different states. So how do we make this work? Well, the way that you make that work is you construct all the routes that require one of the states and then you call with state with that state that it needed. And so now all of those handlers have been turned into services that internally store the state that they needed. And so now if we call, here a new route that requires a different state, then now we can pass in that state over here. And now these things had already been turned into services. So they don't see this new state. They're unaffected by it. This route, this handler, which doesn't have its state yet, is provided with its state. And so now it's turned into a service and doesn't need its state anymore. And you can keep doing this. So you could keep adding more and more states if you really wanted to and have different routes that require different states. And this also means you get into really interesting things. You can imagine that you have handlers that require different states and you actually say, well, this with state only provides half of the state. So it provides... Actually, I'm not going to talk about it. Unnecessarily complicated. But this piece is important to understand, which is that once handlers are provided with their state, they stop becoming handlers and start being self-standing services that do not need their state anymore because they already have it internally cloned. Okay. I know that's a rabbit hole, but I think the conclusion from it is useful. And hopefully if you rewind and watch it again, then it'll eventually make sense. It took me a few goes to piece this part together as well. One of the things that hopefully you find useful from this is to see the value in sort of jumping through the call chains and seeing where do the types change from one to another? Where do things implement different traits? Look at the source of those implementations and figure out how that worked. So hopefully this was useful, even if the specific conclusions from it are somewhat inscrutable. Okay. Let's do more questions now because hopefully and presumably you have some from this. Yeah. So one thing that's worth pointing out here is that even for the people who built this, this was not an obvious design from the start. This is convoluted, but it's not as though some people are just so smart. They come up with this like immediately and it's just perfect. Quite to the contrary, what happens is you start with something that is, you know, not as strict as this, not as complicated as this. And then you discover, for example, that there's a foot gun here, right? Like it's possible to put in a route and forget to provide the state for that route, but still try to start serving a request from it. So now you get runtime exceptions because you didn't pass in the state. That's bad. How can we solve this? And you iterate on the design until you get to something where you can guarantee it at compile time. It's not as though this like fell out complete in the beginning. Rather, it took a lot of iteration to get to the point where you have something that is enforced at compile time to this extent. The other thing I want to point out here is that I didn't dwell on this too much, but there's a lot of boxing happening here. There's a lot of trait objects. There's a lot of like trait erasure and generic erasure into boxes, which does impact performance. But in practice, I just don't think it meaningfully slows things down here because once you have a web server, like your bottleneck isn't dynamic dispatch, right? Your bottleneck is stuff like I.O. or talking to your database. So if you can make the ergonomics of using the tool nicer by doing a box somewhere, it's probably worthwhile. Yeah, box.rs is... I don't know if it's the most complex file, but it's probably the most cursed file. Let me go just pull a box here because it is a... it is fun boxed. So box is the thing that does all of this boxing things and providing the state and turning things from handlers into tower services and does like erasure of generic types. There's a lot of just... cursed voodoo in here. You only really see how cursed it is when you start seeing how it ends up being used throughout the codebase and how all these state transitions happen for the types, but it is really interesting. Can you talk about multi-part send in AXM? I think multi-part send is really just a body type that you can construct in parts. And yeah, so it uses... there's another library called... called MOLTER. Yeah, which lets you produce multi-part types by providing the constituent parts separately and then when it tries to produce an HTTP response, it streams it out with the appropriate encoding. Similarly for... we didn't really talk about this at all, but here you can do things like... colon me and now this is actually a string argument in the path that can be extracted and can be any value and the router has to deal with that, but that's actually not implemented in AXM itself. AXM has to deal a little bit with it, but it's actually implemented through a library called matchit that I mentioned at the beginning, which does efficient routing from strings with these parameterized parts to it into... what is it it constructs? It constructs a prefix tree, like a radix... or it's a radix try that lets you efficiently do string matching of real URLs against the patterns and then find the appropriate value in a map that's actually a try. Really neat data structure too that AXM is using below the hood or under the hood in order to do its... mapping an incoming request into the appropriate path handler. And you can see those parts in... routing... path router. So the whole thing in PathRounder is the sort of key part of it here is this node thing, which... let me see if I can find it. Here, the trick is always you just look for the implementation of service. Maybe that was the implementation of service, actually, now that I think about it. No, I can't find it. RouteValidPath. No, route is for creating one. Where is... where is it? Where is it? Ah, callwithstate. So it extracts the URI from the request, it extracts the path and then it calls self.node.at and node here is one of those match it things and what that gives it back is the handler associated with the path pattern that matched that path. So all of the actual string searching happens inside the match it library and then this, really, you can access almost like a hash map except that the lookups are fuzzy, if you will, are parameterized or searches rather than direct match lookups. Are there any somewhat near future rust features that will obviously serve Axemwell? I think TypeLAsImpletrate will probably help a little bit. There are a couple of places currently where Axem has to box but only because we don't have TypeLAsImpletrate. David can hopefully confirm that but that should be the case. I don't know if there are any others that are like likely to come any time soon. Oh, of course, asynchronous traits which are closely related to TypeLAsImpletrate but currently there are a bunch of places in here that use async trait. So if we looked at, what was the one we pulled up handler? No, not handler. FromRequest. Yeah, so if you look at fromRequest, for example, you see that it actually looks really weird. See it has all these lifetime traits and it has like pin box and where tick life zero implements async trait and stuff. That's all because under the hood it's using the async trait annotation on that trait which means anyone who implements it also needs to use async trait. And so being able to just have asynchronous functions and traits directly as part of the language would let them get rid of that as a sort of hack which also then would reduce boxing. So one of the things that async trait does in order to do its job is that it does everything like boxing and dynamic dispatch which you would no longer need if you had true async event and traits. Does the app state gets copied everywhere? Yeah, so when you use state, I mentioned this briefly but it might have been lost in the noise the state that you take into handlers that the inner type here must implement clone. It does not have to implement copy but it does have to implement clone and it ends up being cloned once for each handler. I don't think it gets cloned for every request or for every connection. It just gets cloned for each handler and then it ends up in handler service. Alright, so remember we looked at this. So a handler service holds the handler and the state and then when anything gets when a request gets passed in it just takes oh I guess it does clone the state actually it clones the state for every request. That makes me sad. I wish this could be a reference but it probably can't be because then you couldn't have multiple requests in parallel although technically you could but you could never request that outlives the handler service. So the reason why it has to clone the state for every request here is because if you look at the tower service trait the future that you return from call does not have a lifetime, oops does not have a lifetime bound so it has to be able to live independently of the borrow of self. So the future where you return cannot borrow self and therefore you can't return a reference to the state because that's contained in self so that's a clone for every request. That does mean it's only cloning the arc it's not cloning the actual contents of that arc but it does mean that you're like potentially contending on a cache line for the reference count for every request is that going to be your main bottleneck unlikely possible at immense scale at that point you would probably use like a sharded arc or something instead but it does make me sad I wish there was a nicer way to do this one of the ways you could do this if you because you know that the state is going to live for the entirety of your service and this is one of those where everyone's going to get mad at me including probably David which is instead of using arc here you do static and here too you do static and then up here instead of arc new you do box leak box new because when you create a box when you leak a box you get back a static reference so I think I can even do static here or I can do this I suppose and so that way what box leak does is it does it heap allocates the thing you pass it and then it leaks it so it basically never frees that memory which means you can get a static reference to it references are clone and it's static because it's a static reference so it will satisfy the tower service bound for the return future so that's the other way you could do this and then the clone is free the risk of course with using leaked memory like this instead of using reference counting is that the leaked memory is going to stay leaked for the entirety of the program it will never be dropped the destructor will not be run and that memory will never be reclaimed now that's not a problem if the entirety of your program is running the server then it's fine for that state to just be leaked because it's just a single instance of the state so it's not like it will keep ballooning over time and cloning that static reference won't make a difference that will just copy a pointer it doesn't allocate any more memory so it's just really that one allocation that gets leaked and lives for the remainder of your program but your server is running for the remainder of the program anyway so it would stay around regardless you're not really losing anything by leaking now the performance benefit that you get is this only really matters if you're running in highly concurrent environments but cloning an arc it is going to it requires incrementing a reference count which is on a shared cache line across all of the all of the users or all of the reference counted pointers to that one memory location all of them are going to share a cache line for the counter which means that if you have lots and lots of clones all happening at the same time they're going to contend on that cache line which means they're going to slow all of them down and so you might start to see a performance collapse if you have a lot of handlers that you state because all of those handlers every time they're invoked for every request are going to try to increment the counter on that cache line and decrement it again contention and contention is pretty sad when you get to highly concurrent states so if you have like lots of cores lots of concurrent requests and you're using the state all over the place this might actually be a sort of life saver for you if that's not you then just use an arc and it's fine so this is very much one of those like measure first like don't start to leak memory on purpose until you actually measure and discover that you truly need it because it does make your application like more painful to work with it makes testing more annoying because now every time you start to test you might leak memory and suddenly the lifetime of your program is actually quite long because it's every test spins up one of these and it remains until all of your test is completed so think carefully about this don't do this lightly but it is the trick that you can pull if it turns out to be actually important can you put it back into a box after you free your server if you cared enough I would hope all references are destroyed after the server is dropped this one's tricky so the proposal here is basically well I could do this and then down here I do state and then down here I do sort of I reconstruct the box by saying I would have to use from raw of state technically in order for this to work I think you would want to I actually think you have to be a little sneaky here you need some unsafe because of provenance which we're not going to dig too much into here but you could do this and then unsafe this and then from raw of this and so that way after your server exits you are actually going to drop and then we can just change this into this the reason you need to do this is that if you instead did this and you say this is going to be a static of this and then get rid of the unsafe and then you try to do this as star mute this that's an invalid cast so you would have to do this and cast it and this would be raw state so the compiler doesn't yell at you for this like this compiles but from a provenance point of view a pointer provenance point of view this is actually incorrect because what you're doing is you're taking a shared reference and casting it to a mutable reference and that is disallowed in all of rust you are not allowed to do it so this version of the code even though it compiles the unsafe here the unsafe assertion is incorrect this code is not actually safe you have to do it this way I believe and if you do it this way I think it's okay where the state that you pass back never leaves as a mutable pointer but you can do this you can recover the leak it's just you want to do this on clear will this be better if the tower API included lifetime bounds so this there is an argument that the service trade should move to use gats like generic associated types if they do then call could be generic over the lifetime to self and the future could have a lifetime argument that gets associated with the future we might end up with that the downside by doing it that way is you can no longer handle concurrent requests because when you do a call the future you get back is now tied to the lifetime of the mutable borrow of self which means that you can't call call again until that future is resolved in other words you can only handle one request at a time and I don't know if that's a requirement we want to add or maybe the trade needs to expand to allow for things like concurrent calls we'll have to see maybe call turns into taking a shared reference to self that's one way to get around it do you recommend using AXM for serving ML models the issue I'm thinking about here is having a message queue in the back into a server request if model inference takes a lot of time that should be fine you might end up using something like Tokyo blocking in order to say that the generation part actually is going to be slow so that you're not blocking handling of other requests but that's not inherently a problem oh yeah the other question for the unsafety here is whether do you actually know that once the server has exited that nothing is keeping that reference to state because it's really easy for them once they have a static reference to the state to just store it somewhere somewhere else static and now this unsafe invariant is no longer true so this is pretty risky you need to it's like a global invariant over your code that you need to make sure you don't violate why not also make service clones so that every request has its own service that feels unnecessary like you don't really want to add clone bounds unless you need to right because sometimes it is actually useful to have the call mutate the state for things like keeping track of how many requests have come through you don't really want to have to clone it for every time you would lose the ability to do something like that why is actics web faster than axem is it I very rarely believe these benchmarks because they're usually micro benchmarks and micro benchmarks don't really represent a real workload at the same time like I don't think there's a I don't it's not as though I'm like opposed to using actics webs or something either I like the way that axem does things because it integrates nice with other ecosystems and I just I like the this way to write code I think there are there is more rust complexity to it but in some sense that's what the language is for so it's like it eliminates more foot guns at compile time which does add some complexity and cognitive overhead but I also think it's better I think that's all I really wanted to cover for axem do we have other questions anything else that people want to know before we before we say goodbye let's go stick this back here so I get rid of my own safe you seem to use the use the term provenance a lot can anyone in chat or you briefly explain what provenance is it's very hard to briefly explain explain provenance I would look up Ralph Jung's introduction to provenance it's really good at giving examples for this can you talk about the from ref trade yeah so from ref is not really all that magical from ref so from ref is mainly used in things like states so remember we looked at this so state implements from request parts but it implemented via this from ref trade and the idea here is that if if you have a an impulse of from ref which direction is from ref go outer state so app state for specific state if you have this impulse then now you can write an async a root handler that takes a state s state root specific state you can write a handler like this and you can still call router with state with an app state so you might wonder well why is that useful this can be useful in things like libraries where your library needs to have access to like a database connection for example so there's like some like I don't know delete user or something like your library provides a route and that route requires that you have like a db connection or whatever it is some connection type but your application the application that you have written the larger application that includes that library and includes the delete user route from the library you don't want to have the requirement that the state that you pass around is just connection because you have all sorts of other state you want to include there too and so the way that you do this is you implement from ref app state for connection and so that way you basically write how you can construct a state connection from a state app state so it's a way to have a level of interaction between the state that any given route needs and the overall state that you provide the fallback router is just if you have a fallback router what that means is if the main router fails to find a way to route the request the fallback router is invoked instead is action production ready? yes I think so I don't have a reason to believe it's not custom HTTP method handling I think for method filter um no method filter is not what I want I want on I guess this actually does specifically only handle these that's fine so what you would end up doing is instead of using the method routing router from axom which only supports the sort of standard HTTP verbs and so it has a pretty hard coded list of mappings from the known verbs to other things what you would do instead is you would just have your own type that you implement that has a service where's the impulse I just want the impulse I just want the impulse here and you would write an impulse like this impulse service request request body type for and your type goes here and then in call the call with state here for the method handler is really just match on the method just figure out which method was in the request so you would do the same and your type all you would look at what is the method for the request and rather than try to turn it into one of the well known HTTP verbs you just check whether the method is your own the HTTP verb you're looking for and if so call the handler so you wouldn't do it through the normal method router you just write a tower service let's see using think a synchronous traits will stabilize this year I hope I hope so but my my belief is fairly low why doesn't Rust have a way to async drop because async drop is really hard okay in that case there's one last thing I want to mention which is axem extra so there's a crate called axem extra that has a bunch of extra utilities for axem things like handling cookies or jason lines so the format where you have multiple jason separate new like new line separated jason objects and proto boff and stuff so these aren't in the standard axem crate because they have to take a bunch of extra dependencies and so you want to keep them separate so that the dependencies of axem itself is smaller and some of them I think are more experimental or the versioning of the underlying crates would mean that we would have to bump the version more often I say we but I mean David so this is a good crate to know about if you if there's some extractor for example that you really want to have and you don't see it in axem it might be an axem extra instead they have like other routers as well I think for query parameter based routing I think there's also other response types yeah like CSS specifically so that it sets the content type that kind of stuff alright I think oh there's also axem server for HTTP support axem server interesting I don't know how this differs this is also by a different author I don't think you need this I think you can just do it through like it's already through hyper in um it's already through hyper in axem itself alright let's call it there thank you all for coming I hope that was interesting annoying that it got split in the middle but what I'll do is I'll I guess stitch this together so if you were watching the video on demand you don't realize what I'm talking about and that's fine it'll be a a treat for the people who are here alive alright I will see you all next time I don't know what we're gonna do next but we will do another stream at some point in the future and it will be fun and interesting alright bye all