Rust with Serverless Framework: Introduction
Introduction on how to deploy simple Lambda function in AWS using Rust programming language.
The Serverless framework is our go-to environment for serverless applications. Particularly, we are focused on developing applications that run on AWS. Thus, in this post, we will mostly focus on AWS Lambda-specific topics.
AWS has taken great efforts on bringing Rust into a first-class citizen in Lambda. They have published their Rust runtime environment as open-source on Github https://github.com/awslabs/aws-lambda-rust-runtime. It is easy to verify that it is under active development. This is good news as Rust has not yet gathered a whole lot of spaces from the serverless world, but it is on its way.
Serverless has also a plugin to support applications made with Rust https://www.serverless.com/plugins/serverless-rust. This should bring us all the tools to start developing Serverless applications in Rust.
Note that the Serverless framework plugin is specifically designed for AWS Lambda.
Why Rust?
Serverless applications are mostly done in higher-level languages such as JavaScript, TypeScript, and Python. It is easy to see why. They have a fantastic ecosystem behind them that has readily available libraries for most tasks.
So why would we want to go with a lower-level language that does not have as wide a range of libraries accessible?
Performance is the most obvious answer. Compiled languages, such as Rust, have inherent performance benefits attached to them. In serverless applications, this usually means faster runtime and lower memory usage, which translates into better service and lower costs.
Preliminaries
We're assuming that you have a Rust development environment installed. The default behavior of serverless-rust
plugin is to use dockerized builds. However, I found it somewhat tedious to use and reverted to the dockerless builds. In any case, the dockerless build environment is supposed to be enabled by default in the future.
Furthermore, you should have basic knowledge of the serverless framework, and know your way around the basic workflow.
To keep things nice and tidy, AWS Rust runtime relies on musl libc. So you will probably need a cross compiler for musl. Luckily, this is easy to do with rustup
. Just run
Simple handler source
We attempt to use a minimal setup for our first project without any unnecessary bloat that we all tend to gather in our projects as they progress. Let's start by having a look at a super simple handler code in Rust. We have taken the example handler from the AWS Rust runtime examples. Add the following in src/main.rs
.
We use service_fn
to declare the handler function, in this case, named func
. AWS runtime runs this function and passes the LambdaEvent
as a parameter. The event contains all information passed to the event handler.
Events are split into actual event body and context. Since this is just a simple hander, we ignore the context and focus on the event body. The event body is assumed to contain property firstName
. If it is not present, we populate it with a string world
. Then we return a JSON response with a property message
containing the string Hello {firstName}!
.
Serverless configuration
Now that we have set up our simple handler code. We can configure our serverless environment around it.
Let's start with package.json
. We'll only install the serverless base and rust dependencies.
Install all necessary packages in the development environment
yarn install
Now we are ready to install the necessary Rust packages. We need the lambda dependencies and, additionally, we use tokio
to implement our unit tests.
You can try cargo build
to check that you can build the dependencies. Note that running cargo directly will use your system's default Rust build environment, which is not the same that is used by serverless to build the packages.
Next, we move on to configuring the actual serverless environment. We initialize our serverless.yml
template with the following
service: serverless-with-rust
provider:
name: aws
runtime: rust
memorySize: 128
stage: dev
region: eu-central-1
package:
individually: true
custom:
rust:
dockerless: true
plugins:
- serverless-rust
functions:
func:
handler: "serverless-with-rust"
Most of this is pretty much self-explanatory if you know your way around serverless workflow. Not that we set up the dockerless build by setting custom:rust:dockerless
to true
. Note also that the handler name is the package name defined in Cargo.toml
.
Test the build by running
npx serverless package
If the packaging succeeds without any hiccups, we can safely assume that the build environment is working.
You are now ready to deploy the app by running
npx serverless deploy
You now also invoke the default func
lambda by running
npx serverless invoke -f func -d '{"firstName":"me"}'
Congratulations! You have successfully deployed your first rust applications using the serverless framework.
Bonus: unit testing
Here's how to add simple unit testing for your simple handler function. We just test different event bodies and check for the appropriate response.
You can execute the unit tests as you normally would by running
cargo test
Further reading
Check these resources for further reading on how to use Rust with the Serverless framework and AWS Lambdas.