AWS Lambda + Rust
Tags: devops, rust, aws, backend
Published: 2020-11-10 17:19:05

> This article was first published on [techpilot.dev](https://techpilot.dev/article/aws-lambda-rust)
> TL;DR: There's an example repo [here](https://github.com/valentinradu/rust-aws-lambda-example) for those who want to skip the story mode

[Rust](https://www.rust-lang.org) piqued my interest when I found out it consistently ranked first in the StackOverflow's annual developer survey for the world's most loved programming language. [Here's](https://insights.stackoverflow.com/survey/2020#technology-most-loved-dreaded-and-wanted-languages) the 2020 survey, but it also holds the first position for 2019, 2018, 2017, and 2016.

It turns out it's as awesome as they say and now I'm in that particular moment in the hype phase when I try to do everything in Rust. I know that's a terrible idea and I strongly advise against it: pick the language that has the strongest support (aka libraries, community) for the problem you're trying to solve. Doing ML in Rust when Python is de facto standard is not such a great idea.

Anyways, I figured I can make an exception and since I'm not that excited about any of the popular backend languages, I started to experiment in that direction.

Running a Rust HTTP server using [Rocket](https://rocket.rs) is really easy and well documented, however, if you plan to go serverless, there's still a lot of uncharted territories.

For AWS Lambda, there are a couple of resources out there, but many are outdated or somehow incomplete.

Here are the main steps we'll have to follow:
- implement the lambda handlers
- (cross)compile our code for the Amazon Linux platform (x86, 64bit)
- build each lambda as a standalone binary
- configure AWS Lambda for deployment
- deploy & enjoy

So, let's get started!

```bash
# create a new crate
cargo new rust_aws --bin
# delete the main.rs, we'll be using a binary for each lambda
cd rust_aws && rm src/main.rs
# these are the two lambdas we're going implement
touch src/comment.rs
touch src/contact.rs
```

Next, our dependencies in `Cargo.toml`

```toml
[package]
name = "your_proj_name"
version = "0.1.0"
authors = ["You <you@example.com>"]
edition = "2018"

[dependencies]
lambda_runtime = "0.2.1"
lambda_http = "0.1.1"
tokio = { version = "^0.3", features = ["full"] }

[[bin]]
name = "comment"
path = "src/comment.rs"

[[bin]]
name = "contact"
path = "src/contact.rs"
```


A couple of things to mention here. 
First, we have the `lambda_runtime` and `lambda_http` crates which are responsible for communicating with the Lambda API. This usually means running the setup code, fetching the handler name from an environment variable, and passing events to our code. You can find out more about how custom runtimes work [here](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html).

Although lambdas are stateless, AWS can run our binaries and send multiple events to the same process, as long as the process doesn't exit. This requires an event loop: a fancy way to handle asynchronous I/O and scheduling. We use [tokio](https://tokio.rs) for that.

Finally, we declared 2 different binaries, one named `comment`, the other `contact` and each will be deployed as a standalone lambda function


Next up, compilation. Unless you're on an x86, 64bit Linux machine, you'll have to cross-compile your code. To do so, we need the correct toolchain:

```bash
# adds the x86 64 target to the toolchain
rustup target add x86_64-unknown-linux-musl
# installs the x86 64 toolchain on macOS (for Windows, you can probably do it with cygwin-gcc-linux, but I haven't tried it out)
brew install FiloSottile/musl-cross/musl-cross
```

Lastly, we need to let `cargo` know we're cross-compiling: add the following in `./.cargo/config.toml`

```toml
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
```

Now we're ready to compile. `cargo build --target x86_64-unknown-linux-musl` to test it out.


The next thing we need to do is to configure [SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). I'll assume you're already familiar with SAM and focus only on the critical section for our case. You can have a look at the full `template.yml` in the example repository. Also,  skip `sam init` since there is no Rust template available anyhow (to my knowledge)  and simply start with the `template.yml` file and build your own directory structure.

Let's go through the one of the lambdas' definition:
```yaml
# template.yml
Resources:
    Comment:
    Type: AWS::Serverless::Function
    Properties:
    FunctionName: Comment
    Handler: doesnt.matter.the.runtime.is.custom
    Runtime: provided
    MemorySize: 128
    Timeout: 10
    CodeUri: .
    Description: 
    Policies:
        - AWSLambdaBasicExecutionRole
    Events:
    comment:
    Type: Api
    Properties:
    Path: /comment
    Method: post
```

This tells SAM to create a lambda serverless function named `Comment`, with a custom runtime (handled by or Rust `lambda_runtime`) and expose it as a REST API resource at `/comment`.

We're almost done. One last (important) thing: when we build and deploy our lambdas with `sam build && sam deploy --guided` SAM will look for a `Makefile` since it doesn't know how to build our project by itself.

```bash
touch Makefile
```

```makefile
build-Comment:
    cargo build --bin comment --release --target x86_64-unknown-linux-musl
    cp ./target/x86_64-unknown-linux-musl/release/comment $(ARTIFACTS_DIR)/bootstrap

build-Contact:
    cargo build --bin contact --release --target x86_64-unknown-linux-musl
    cp ./target/x86_64-unknown-linux-musl/release/contact $(ARTIFACTS_DIR)/bootstrap
```

The way this works is straightforward, you need to add a target for each lambda name and prefix it with `build-`.  That's it. SAM will invoke them as needed.
Each target builds the respective binary (`--bin contact`) and copies it in the artifacts directory, where it will be zipped and sent to the AWS servers for deployment.

And that's it. We're done. Have fun with your new Rust-powered AWS lambdas!