Tween: a middleware library experiment
This article is intended for people who are familiar with how web frameworks such as Django and Iron work; if you’re not familiar with such things you probably won’t get much out of this particular post. I’m not going to be explaining everything you might need to know.
The existing state of middleware libraries in Rust
iron::middleware as an example of the current state of middleware in Rust:
The request and response objects have slots for adding data to them:
Response.extensions, which are
Middleware can inject things into the request and/or response objects, or not, as the fancy takes you, and read from it to get data from previous middleware or the main handler in post-processing;
There is no type-level dependency tracking, so you can’t say “such and such a piece of middleware must have been executed first”;
Putting more data into the extension location requires allocating a new trait object each time, and accessing it is dynamic dispatch. Not slow, especially compared with what a dynamic language would be doing, but not as fast as if you wrote the whole stack by hand.
There are two main problems I wish to outline with all of this:
Confidence. Lack of dependency tracking means that you end up with all sorts of places where you might end up panicking, and that it’s easy to make mistakes. Debugging can also be difficult, because your extension data is approximately opaque and so you can’t easily examine or print out its contents. (Admittedly,
Debugcould be added as a bound in the
TypeMap, MOPA-style, which would reduce its opacity to debugging.)
Performance. heap memory allocations and indirection everywhere. Imagine if you just had a simple struct containing all of this stuff, and so no heap allocations or complex lookups—just straight field lookup. This turns something from being fairly fast to being fast.
Encoding middleware dependencies into the type system is something that we (people such as myself and Jonathan Reem) tried two years back, but the Rust type system wasn’t quite good enough back then: we collectively came to the conclusion that it just wasn’t possible, and probably wouldn’t be until the language gets HKT.
This has annoyed me from time to time. Sure, they’re not necessarily serious problems, but I do want to solve them.
I came up with a new approach last week or the week before, and decided to have one last try. Well, that particular approach didn’t work (I think—I’ve actually forgotten most of the details of what it was now), but a part of it did and it headed me in another direction and I wound up with something that got steadily more and more complex until it worked, and then I steadily pulled bits out of it until I wound up with a drastically simplified core that also worked. Then I just had to continue cutting the macros down (Quxxy helped me with the subcontexts one, thanks!) until the whole thing of a middleware chain was just one macro with only one extraneous piece of information per middleware in the chain (and when we get eager macro expansion, there will be no extraneous information in the definitions).
The end result: Tween
The end result is a proof-of-concept middleware library for web frameworks. I’ve called it Tween, because of a certain fondness I have for Pyramid, which calls ’em that. (Aside: using traversal for URL routing instead of dispatch is really interesting. If you’re not familiar with it, you should try it purely to broaden your horizons. I don’t care whether you actually end up using it or not.)
Tween’s key features are robustness (no cause for panicking) and performance (no need for heap memory), and static tween dependency declaration.
The repository is a work in progress, a proof of concept. It has proven its concept, I believe.
What to do
An explanation of how it works is there in that README.
This is a proof of concept. For the sake of performance it plays fast and loose with memory safety internally, and although I believe it handles everything appropriately even in case of panicking, it’d be good to have review by others. I could easily have missed something. There could also be better ways of doing some parts of it.
If you want to help me by giving feedback—good things and bad things about the design, issues I haven’t thought of, ideas of ways of improving it, &c.—please comment in /r/rust, file an issue in the tween repository, or drop me a line at firstname.lastname@example.org.
And no, I haven’t written any benchmarks yet.
Comments? Questions? Corrections? If you want to contact me about anything in this post, write to me at @__chrismorgan or email me.