Aug 06 2025

warp v0.4

Yesterday, warp v0.4 was released. warp is a Rust web server framework, with a focus on functional programming and type system routing.

Upgrading is likely pretty simple, the main API stayed very similar. The biggest deal is that it should be easier for you to stay up-to-date on dependencies. You could stop there. But if you want to know what else happened, off we go!

Filters remain the focus

The biggest raison d’être of warp is its Filter system. It was why I made a different web framework originally, and it remains the reason to consider warp even now.

Should you use warp? That depends. If you just want a standard, super fast, featureful Rust server framework, one that looks like ways you’ve coded servers before, you probably want Axum. But, if you like functional programming, and (ab)using the type system, I think warp is pretty cool.

Consider an example:

let update = warp::path!("todos" / u32)
    .and(warp::header("accept"))
    .and(warp::body::json())
    .map(|id, todo: Todo| {
       // ... 
    });

Every filter extracts something from the request (even if that’s just ()). They can all be .and()ed together in any order, it will result in natural, typed arguments you can map.

You can build up layers of filters by just putting another .and() on anything. You can make a group of filters with .or().

Once you have combined all into a master filter, you can convert them into a tower Service, which you can then add any other middleware from other libraries, and serve with an HTTP implementation, such as hyper.

Upgraded to hyper v1

The underlying HTTP dependencies, those which are public, have been upgraded to hyper v1.

This should improve interoperability if used with other libraries such as reqwest or tower-http. Their latest versions depend on v1 of hyper and http. So adding in more middleware, or using a higher-level HTTP client should mean less dependencies compiled, and no conversion required between types.

It also means warp users will be able to stay on maintained versions of hyper and its dependencies.1

Crate features on a diet

In previous versions, a few heavy features were enabled by default. Now, the default features are much slimmer.

Keeping with filters being the focus, the default features only include the Filter system, and a way to convert them into an impl Service. You can take that service and use it with your own build of hyper, with HTTP/1 or 2 or whatever enabled, and warp won’t care.

If you want the simple server runner, just enable the server feature, and then warp::serve(filters) is back, along with all the hyper implementation to run it.

Likewise, the warp::test helpers have been put behind a test feature. You can enable it in your dev-dependencies for testing, and while keeping that code out when building for release.

The tls feature was dropped completely. It existed previously as a simple way to add TLS to your server, but it’s not strictly required in a server framework, and it adds a bit of maintenance churn to stay up-to-date and safe. Anyone wanting TLS with their warp server can make an accept loop using the TLS implementation of their choice, and use warp’s filters as a Service.

Finally, the multipart and websocket features were removed from default, but are still available to enable. This was a planned change once they became features in the first place, since they enable a lot of code that you might not need.

Iterating on the Server builder

The Server API was, uh, iterated on.

Before, it had a whole bunch of bind_*, run_*, and serve_* methods that slowly grew as people wanted more options to configure the server. Now, most all of them are gone.

Part of the reason is because it’s not the focus of warp. The Filter system is. I figured the Server is meant to be a simple way to get your filters serving requests. If you wanted more customization, make your own.

But there’s another reason, too.

I’ve been noticing more and more places where I wish the API of some crate was at the same time simpler, and also more powerful, putting options into the type system. The warp Server was an excellent place to iterate on this.

It is now an unnameable builder making use of expandable type state.

It encodes as generics into the Server<F, A, R, Etc...> various parts of how the server works. The filter, the acceptor, the runner loop. Depending on what methods you call, new ones get unlocked. For instance, bind() will make the acceptor a TcpListener, but once one is set, it doesn’t make sense to be able to call it again.

The type is rendered in the docs, but otherwise not publicly exported, so you can’t name it. There’s a benefit to this, as the API designer and maintainer. I can add more options, needing more generics, without it being a breaking change. As long as add a default that keeps it all compiling, there are now instances of Server<F, A, R> that need to be updated to Server<F, A, R, E>.

And the reality is that the name of the type is not generally interesting to you as a user. You just need a fluid way to add options, and once it’s all combined away, you just await a future.

I’m hopeful this pattern carries over well to other builders as well.

  1. I’ve backported important bug fixes to hyper v0.14 (and dependencies like h2), but I think that’s about done. It’s been almost 2 years, time to focus on v1. (If you need help, reach out to me!) 

  • #rust
  • #http
  • #warp
  • #open-source
  • #programming