Define Lambda function handler in Rust
Note
The Rust runtime client
The Lambda function handler is the method in your function code that processes events. When your function is invoked, Lambda runs the handler method. Your function runs until the handler returns a response, exits, or times out.
Rust handler basics
Write your Lambda function code as a Rust executable. Implement the handler function code and a main function and include the following:
-
The lambda_runtime
crate from crates.io, which implements the Lambda programming model for Rust. -
Include Tokio
in your dependencies. The Rust runtime client for Lambda uses Tokio to handle asynchronous calls.
Example — Rust handler that processes JSON events
The following example uses the
serde_json
use lambda_runtime::{service_fn, LambdaEvent, Error}; use serde_json::{json, Value}; async fn handler(event: LambdaEvent<Value>) -> Result<Value, Error> { let payload = event.payload; let first_name = payload["firstName"].as_str().unwrap_or("world"); Ok(json!({ "message": format!("Hello, {first_name}!") })) } #[tokio::main] async fn main() -> Result<(), Error> { lambda_runtime::run(service_fn(handler)).await }
Note the following:
-
use
: Imports the libraries that your Lambda function requires. -
async fn main
: The entry point that runs the Lambda function code. The Rust runtime client uses Tokioas an async runtime, so you must annotate the main function with #[tokio::main]
. -
async fn handler(event: LambdaEvent<Value>) -> Result<Value,
Error
>
: This is the Lambda handler signature. It includes the code that runs when the function is invoked.-
LambdaEvent<Value>
: This is a generic type that describes the event received by the Lambda runtime as well as the Lambda function context. -
Result<Value, Error>
: The function returns aResult
type. If the function is successful, the result is a JSON value. If the function is not successful, the result is an error.
-
Using shared state
You can declare shared variables that are independent of your Lambda function's handler code. These variables can help you load state information during the Init phase, before your function receives any events.
Example — Share Amazon S3 client across function instances
Note the following:
-
use aws_sdk_s3::Client
: This example requires you to addaws-sdk-s3 = "0.26.0"
to the list of dependencies in yourCargo.toml
file. -
aws_config::from_env
: This example requires you to addaws-config = "0.55.1"
to the list of dependencies in yourCargo.toml
file.
use aws_sdk_s3::Client; use lambda_runtime::{service_fn, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; #[derive(Deserialize)] struct Request { bucket: String, } #[derive(Serialize)] struct Response { keys: Vec<String>, } async fn handler(client: &Client, event: LambdaEvent<Request>) -> Result<Response, Error> { let bucket = event.payload.bucket; let objects = client.list_objects_v2().bucket(bucket).send().await?; let keys = objects .contents() .map(|s| s.iter().flat_map(|o| o.key().map(String::from)).collect()) .unwrap_or_default(); Ok(Response { keys }) } #[tokio::main] async fn main() -> Result<(), Error> { let shared_config = aws_config::from_env().load().await; let client = Client::new(&shared_config); let shared_client = &client; lambda_runtime::run(service_fn(move |event: LambdaEvent<Request>| async move { handler(&shared_client, event).await })) .await }
Code best practices for Rust Lambda functions
Adhere to the guidelines in the following list to use best coding practices when building your Lambda functions:
-
Separate the Lambda handler from your core logic. This allows you to make a more unit-testable function.
-
Minimize the complexity of your dependencies. Prefer simpler frameworks that load quickly on execution environment startup.
-
Minimize your deployment package size to its runtime necessities. This will reduce the amount of time that it takes for your deployment package to be downloaded and unpacked ahead of invocation.
-
Take advantage of execution environment reuse to improve the performance of your function. Initialize SDK clients and database connections outside of the function handler, and cache static assets locally in the
/tmp
directory. Subsequent invocations processed by the same instance of your function can reuse these resources. This saves cost by reducing function run time.To avoid potential data leaks across invocations, don’t use the execution environment to store user data, events, or other information with security implications. If your function relies on a mutable state that can’t be stored in memory within the handler, consider creating a separate function or separate versions of a function for each user.
-
Use a keep-alive directive to maintain persistent connections. Lambda purges idle connections over time. Attempting to reuse an idle connection when invoking a function will result in a connection error. To maintain your persistent connection, use the keep-alive directive associated with your runtime. For an example, see Reusing Connections with Keep-Alive in Node.js.
-
Use environment variables to pass operational parameters to your function. For example, if you are writing to an Amazon S3 bucket, instead of hard-coding the bucket name you are writing to, configure the bucket name as an environment variable.
-
Avoid using recursive invocations in your Lambda function, where the function invokes itself or initiates a process that may invoke the function again. This could lead to unintended volume of function invocations and escalated costs. If you see an unintended volume of invocations, set the function reserved concurrency to
0
immediately to throttle all invocations to the function, while you update the code. -
Do not use non-documented, non-public APIs in your Lambda function code. For AWS Lambda managed runtimes, Lambda periodically applies security and functional updates to Lambda's internal APIs. These internal API updates may be backwards-incompatible, leading to unintended consequences such as invocation failures if your function has a dependency on these non-public APIs. See the API reference for a list of publicly available APIs.
-
Write idempotent code. Writing idempotent code for your functions ensures that duplicate events are handled the same way. Your code should properly validate events and gracefully handle duplicate events. For more information, see How do I make my Lambda function idempotent?
.