Skip to content

rnag/rust.aws-cdk-lambda

Repository files navigation

Amazon Lambda Rust Library


rust.aws-cdk-lambda: Stable npm

This is an unofficial CDK library based on the Amazon Lambda Node.js and aws-lambda-rust Libraries.

It's intended for use with the new AWS CDK v2.


This library provides a construct for a Rust Lambda function.

It uses cargo-lambda under the hood, and follows best practices as outlined in the official AWS documentation.

Rust Function

The RustFunction construct creates a Lambda function with automatic bundling and compilation of Rust code.

Getting Started

  1. Install this npm package, and zig:

    $ npm i rust.aws-cdk-lambda --save-optional
  2. Install the aarch64-unknown-linux-gnu toolchain with Rustup by running:

    $ rustup target add aarch64-unknown-linux-gnu
  3. Use cargo to install cargo-lambda:

    $ cargo install cargo-lambda

Examples

You can find sample CDK apps built using Typescript or Node.js in the cdk-examples/ folder of the GitHub project repo.

Usage

First, import the construct:

import { RustFunction } from 'rust.aws-cdk-lambda';

Now define a RustFunction:

new RustFunction(this, 'my-handler', {});

By default, the construct will use directory where cdk was invoked as directory where Cargo files are located.

If no bin or package argument is passed in, it will default to the package name as defined in the main Cargo.toml.

That is, the above usage should work for a project structure that looks like this:

.
├── Cargo.toml
└── src
    └── main.rs

Alternatively, directory and bin can be specified:

new RustFunction(this, 'MyLambdaFunction', {
    directory: '/path/to/directory/with/Cargo.toml',
    // Optional
    bin: 'my_lambda',
});

All other properties of lambda.Function are supported, see also the AWS Lambda construct library.

How It Works

When bundling the code, the RustFunction runs the following steps in order:

  • First it runs cargo lambda, and passes in the --release and --target flags, so it compiles for a Lambda environment - which defaults to the aarch64-unknown-linux-gnu target, as mentioned above. Note that cargo lambda does initially confirm that the Rust code can compile.

  • The directory path to the executable is then passed in to lambda.Code.fromAsset, which creates a zip file from the release binary asset.

Use cross for Deployment

If you instead prefer to use Docker and cross for deployment, as outlined in the official AWS docs, you can install and use the latest v0.x release instead:

$ npm i [email protected] --save-optional

Multiple Rust Lambdas

Assuming you have a CDK project with more than one Rust lambda, there are a couple approaches - as outlined below - that you can use to deploy with cdk.

Multiple Binaries

Suppose your project layout looks like this:

.
├── Cargo.toml
└── src
    └── bin
        ├── lambda1.rs
        └── lambda2.rs

Here's one way to deploy that via cdk:

new RustFunction(this, 'my-function-1', {
    bin: 'lambda1',
});

new RustFunction(this, 'my-function-2', {
    bin: 'lambda2',
});

You can find a more complete project structure in the rust-bins/ CDK sample project.

Multiple Packages

Suppose you use Workspaces in your Cargo project instead.

The full contents of the main Cargo.toml would need to be updated to look like this:

[workspace]
members = [
    "lambda1",
    "lambda2"
]

And your new project layout would now look similar to this:

.
├── Cargo.lock
├── Cargo.toml
├── lambda1
│   ├── Cargo.toml
│   └── src
│       ├── main.rs
│       └── utils.rs
└── lambda2
    ├── Cargo.toml
    └── src
        ├── main.rs
        └── utils.rs

Where the utils.rs files are optional, but the point is that they can be imported by the lambda handler code in main.rs if desired.

Now you will only need to update your CDK code to pass package instead, for each workspace member:

new RustFunction(this, 'MyFirstRustFunction', {
    package: 'lambda1',
});

new RustFunction(this, 'MySecondRustFunction', {
    package: 'lambda2',
});

You can find a more complete project structure in the rust-workspaces/ CDK sample project.

Conditional Compilation

A common use case is building Rust code with enabled features, and compile- time environment variables that can be used with the env! macro.

For example, we might want to run different logic in our code for development and production environments, or call a different API endpoint depending on which environment we are deploying code to.

For a sample CDK app that demonstrate such usage, check out the rust-bins/ example.

Enabling Features

You can achieve conditional compilation by introducing features which can later be enabled in Rust code.

In the Cargo.toml, create a new features section:

[features]
my-feature = [] # feature has no explicit dependencies

In your code, add the line #[cfg(feature = "my-feature")] before a function declaration, or before a statement to execute.

In your CDK code in the lib/ folder, add the following line:

// Enable features at compile or build time.
Settings.FEATURES = ['my-feature'];

Build Environment Variables

You can also introduce environment variables which are resolved at build or compile time. These values can be used in code via the env! macro in Rust.

In your code, add a call to the env!() macro:

// Retrieve an environment variable set at build (compile) time.
const BUILD_VALUE: &str = env!("MY_BUILD_VAR");

In your CDK code in the lib/ folder, add the following line:

// Enable environment variables at compile or build time.
Settings.BUILD_ENVIRONMENT = {
    MY_BUILD_VAR: 'Hello World! Testing 123.',
};

Rust Function Properties

Below lists some commonly used properties you can pass in to the RustFunction construct.

Name Description
target Build target to cross-compile to. Defaults to the target for the arm64 architecture, aarch64-unknown-linux-gnu.
directory Entry point where the project's main Cargo.toml is located. By default, the construct will use directory where cdk was invoked as the directory where Cargo files are located.
buildDir Default Build directory, which defaults to a .build folder under the project's root directory.
bin Executable name to pass to --bin
package Workspace package name to pass to --package
setupLogging Determines whether we want to set up library logging - i.e. set the RUST_LOG environment variable - for the lambda function.

The format defaults to warn,module_name=debug, which means that the default log level is warn, and the executable or library's log level is debug.
features A list of features to activate when compiling Rust code. These must also be added to the Cargo.toml file.
buildEnvironment Key-value pairs that are passed in at compile time, i.e. to cargo build or cargo lambda. This differs from environment, which determines the environment variables which are set on the AWS Lambda function itself.
extraBuildArgs Additional arguments that are passed in at build time to cargo lambda. For example, [--all-features].

Settings

Settings can be imported as follows:

import { Settings } from 'rust.aws-cdk-lambda';

Below are some useful global defaults which can be set for all Rust Lambda Functions in a CDK app.

Name Description
BUILD_INDIVIDUALLY Whether to build each executable individually, either via --bin or --package.
DEFAULT_LOG_LEVEL Log Level for non-module libraries. Note that this value is only used when RustFunctionProps.setupLogging is enabled. Defaults to warn.
MODULE_LOG_LEVEL Log Level for a module (i.e. the executable). Note that this value is only used when RustFunctionProps.setupLogging is enabled. Defaults to debug.
WORKSPACE_DIR Sets the root workspace directory. By default, the workspace directory is assumed to be the directory where cdk was invoked.

This directory should contain at the minimum a Cargo.toml file which defines the workspace members.
FEATURES A list of features to activate when compiling Rust code. These must also be added to the Cargo.toml file.
BUILD_ENVIRONMENT Key-value pairs that are passed in at compile time, i.e. to cargo build or cargo lambda. This differs from environment, which determines the environment variables which are set on the AWS Lambda function itself.
EXTRA_BUILD_ARGS Additional arguments that are passed in at build time to both cargo lambda. For example, [--all-features].
SKIP_BUILD Whether to skip building Rust Lambdas -- e.g. skip the compile step with cargo lambda.

Alternatively, this can be set in the build context, when invoking cdk -- for example:
$ cdk synth -c build=[T | true | 1 | Y | yes | ok | on]
Any values other than the listed evaluate to false. So, this would skip the build step on synth:
$ cdk synth -c build=0