Upgrading to Rust's Actix Web 2.0
Tags: rust, actixweb
Published: 2019-11-25 21:25:09

For the past year or more, I've been [passively watching](https://areweasyncyet.rs/) the progress of the "async/await" implementation in the Rust programming language. Though I can't speak to the technical details in the Rust compiler, it's been neat to learn new concepts (to me) like `Pin`, `waker`, and `Future`; these aren't things I deal with often as a higher-level developer.

# A Little Context

In September, 2018 I took a little adventure developing a small web application built with the [actix-web](https://github.com/actix/actix-web/) framework: [Bible.rs](https://github.com/DSpeckhals/bible.rs). This app is relatively simple, but I tried to use actix-web to its most powerful extent. You can read more about the initial development of Bible.rs in [this Reddit post](https://www.reddit.com/r/rust/comments/9n3dji/biblers_a_web_application_written_in_rust_using/).

A few weeks before I wrote this post, [async/await landed on Rust stable](https://blog.rust-lang.org/2019/11/07/Async-await-stable.html). Since the syntax for this new functionality stabilized in the springtime, I know that Rust async crate maintainers had been working on migrating to `std::future`, most notable [Tokio](https://github.com/tokio-rs/tokio). When Tokio published an alpha version with the `std::future::Future` and async/await, discussion and work began on migrating actix-web as well. I watched closely as the main actix maintainer migrated to async/await (which was a ton of work in actix-net and actix-web). A few days ago, an alpha version was released, and I got to work on upgrading Bible.rs! This post is my experience upgrading to actix-web 2.0. The actix-web API may change in the alpha and beta process, and if it does, I'll be sure to update accordingly.

# Upgrading actix-web

The first step I took after opening up my editor was to change the `Cargo.toml` entries.

- `actix-files` (middleware for static files): 0.1.4 -> 0.2.0-alpha.1
- `actix-web`: 1.0.5 -> 2.0.0-alpha.1
- `futures`: 0.1.28 -> 0.3.1

Of course, this caused a decent amount of compiler errors, but not as many as I anticipated. The first breaking change I dealt with had to do with the registering of handler functions on a route. Before 2.0, there were two functions that could be used for handler registration: `to` and `to_async`. `to` just had to return a type that could be cast to a response, where `to_async` had to return a `Future`. In 2.0, _all_ handlers return a `Future` (now from `std::future` instead of the `futures` crate). After renaming all of the handler registrations from `to_async` to just `to`, the compiler let me know that the handlers were now returning the wrong type.

## Converting a Handler to Async/Await

I've been using async accessing of the database through a thread pool that actix-web provides in `web::block` for some time. This might be a little bit of an overkill, but it's been a good learning experience understanding thread pools and blocking code.

### Before: actix-web 1.0

```rust
pub fn reference<SD>(
    data: web::Data<ServerData>,
    path: web::Path<(String,)>,
    req: HttpRequest,
) -> impl Future<Item = HttpResponse, Error = HtmlError>
where
    SD: SwordDrillable,
{
    let db = data.db.to_owned();
    let raw_reference = path.0.replace("/", ".");
    match raw_reference.parse::<Reference>() {
        Ok(reference) => {
            let data_reference = reference.to_owned();
            Either::A(
                web::block(move || SD::verses(&reference, &VerseFormat::HTML, &db.get().unwrap()))
                    .map_err(HtmlError::from)
                    .and_then(move |result| {
                        let verses_data = VersesData::new(result, data_reference, &req);

                        if verses_data.verses.is_empty() {
                            return Err(Error::InvalidReference {
                                reference: raw_reference,
                            }
                            .into());
                        }

                        let body = TemplateData::new(
                            &verses_data,
                            Meta::for_reference(
                                &verses_data.reference,
                                &verses_data.verses,
                                &verses_data.links,
                            ),
                        )
                        .to_html("chapter", &data.template)?;
                        Ok(HttpResponse::Ok().content_type("text/html").body(body))
                    }),
            )
        }
        Err(_) => Either::B(err(HtmlError(Error::InvalidReference {
            reference: raw_reference,
        }))),
    }
}
```

There are a few things in this function that had to be done in order to make the compiler happy _before_ `std::future` and async/await. First, it had to return an `impl Trait` type for `to_async`. You'll also notice that if the inputted `path` variable is not "valid" it skips the database call (and `web::block` completely). A `Future` still has to be returned though, which is why the response is wrapped in `err` (similar to `reject` for a JavaScript `Promise`). However, this wasn't quite good enough for the handler's return type yet; both the happy-path and `error` results still have to be wrapped in [`Either`](https://docs.rs/futures/0.3.1/futures/future/enum.Either.html), thus combining the two possible results into a "single" return type. Yes, all of this works (and did fine for a year), but it was a major process of trial and error even getting to this point.

You'll observe that the truly async portion of this handler is the bit of code around `web::block`. In order to make this work, you have write the "what do I do with the result after this finishes" code in `and_then`. Rightward drift of the code across your screen can happen very easily. Now imagine having two or more consecutive async processes.

### After: actix-web 2.0

Before I start on the explanation of the migration, I realized after everything compiled and tests passed that this is quite similar to moving a JavaScript `Promise` to its own async/await syntax.

```rust
pub async fn reference<SD>(
    data: web::Data<ServerData>,
    path: web::Path<(String,)>,
    req: HttpRequest,
) -> Result<HttpResponse, HtmlError>
where
    SD: SwordDrillable,
{
    let db = data.db.to_owned();
    let raw_reference = path.0.replace("/", ".");
    match raw_reference.parse::<Reference>() {
        Ok(reference) => {
            let data_reference = reference.to_owned();
            let result =
                web::block(move || SD::verses(&reference, &VerseFormat::HTML, &db.get().unwrap()))
                    .await??;
            let verses_data = VersesData::new(result, data_reference, &req);

            if verses_data.verses.is_empty() {
                return Err(Error::InvalidReference {
                    reference: raw_reference,
                }
                .into());
            }

            let body = TemplateData::new(
                &verses_data,
                Meta::for_reference(
                    &verses_data.reference,
                    &verses_data.verses,
                    &verses_data.links,
                ),
            )
            .to_html("chapter", &data.template)?;
            Ok(HttpResponse::Ok().content_type("text/html").body(body))
        }
        Err(_) => Err(HtmlError(Error::InvalidReference {
            reference: raw_reference,
        })),
    }
}
```

The first thing of note is adding the new (as of Rust 1.39) keyword `async` to the function signature. This lets the compiler know that this function can be awaited (or less likely, polled). To put it very simply, the return type from the function is "automatically" wrapped in a `Future`. With this specific function, it takes away the need of returning an `impl Future` type. Instead, it can return anything that can be a `Future::Output`; in this case a `Result<HttpResponse, HtmlError>`. With this change to the signature, this function is now the correct actix-web handler type for `to(handler)`! But it gets better.

The code in the `and_then` closure for `web::block` can now be moved out and into the main execution path. You can then use the new `await` keyword at the end of `web::block` instead of `and_then`. Note the two question mark operators (`.await??`); the first unpacks the result of the actual `Future` (e.g., it might be cancelled), while the second unpacks the result of the database call. Perhaps I could eliminate the need for two result unpacks, but it works for now. Of course, now that the result handling code is after `await`, the entire `and_then` call can be removed. I found that `map_err` was no longer necessary to make the compiler happy either.

You'll notice that the wrapping the error match for the input validation no longer has to be wrapped in `err`, and `Either` isn't necessary either. I think this handler function is much simpler now!

Here's the PR for the upgrade: https://github.com/DSpeckhals/bible.rs/pull/44

# Conclusion

I'm extremely pleased with the result and migration for this simple application. Async I/O can lend itself to confusing code in any programming language, and in Rust with its strong type system, it can bring a steep learning curve along with it. With the addition of `std::future` and async/await, I think that async programming is much more accessible and ergonomic; especially in actix-web. I'm impressed with the work that the Rust language team and working groups accomplished. The fairly quick turnaround to get actix-web to this point was also impressive (thanks to [fafhrd91](https://github.com/fafhrd91) and a few others). I look forward to seeing what's next in Rust's async story and actix-web.