This article was first published on techpilot.dev
TL;DR: There's an example repo here for those who want to skip the story mode
Rust 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 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 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!
# 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
[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.
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 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:
# 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
[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. 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:
# 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.
touch 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!
Top comments (6)
Thanks for this article. I tried using Windows to follow along, though I prefer MacOS I have been using Windows for most of my development projects, I wasn't able to get past trying to install the FiloSottile piece. So I will be getting a MacBook soon and will try following along again at that point. But wanted to ask you what resources you used to learn Rust and what projects you would recommend helped you learn the basics of the language. Thanks.
For the language itself I think the book is all you need. For libraries, frameworks and best practices, I used a mix between migrating what I've already knew from other languages and using Google to see how Rustaceans do it. This might not work for everyone tho.
Awesome. Thank you. I have the book and have been going through it. I was just curious about whether or not it was worth trying another project in between going through the book or just go through the book then try my hand at a project.
Thanks for the article. I'm completely new to RUST but have been using Lambda for years and one thing I'd be interested in is whether RUST performs better in AWS Lambda. I've not seen any information on this and this benefit might be the reason why people would switch away from more easily achieved Javascript/Typescript lambdas.
I guess to add onto this ... if not performance ... why? I'm not questioning RUST in this use case as much as just wondering why folks would take on a less known language that has less direct support and examples to work off of.
Thanks for reading! I didn't test the performance. I suppose it's better than NodeJS since it eliminates some of VM overhead, however, I also feel that lambdas are many times slow for other reasons related to how they are orchestrated, and unrelated to the hosted function itself (again, I haven't tested this either, just a hunch from experience)
In any case, I'm pretty new to Rust as well and the reason I did this is because I like it as a language and (very important) its ecosystem too: it's a pleasure to use.
I realize this might not scale well talent-wise (aka, if you start a Rust project you automatically reduce the talent pool by 80%), but, for my particular use case that's not such a problem.