

# 定义 Rust Lambda 函数处理程序
<a name="rust-handler"></a>

Lambda 函数*处理程序*是函数代码中处理事件的方法。当调用函数时，Lambda 运行处理程序方法。您的函数会一直运行，直到处理程序返回响应、退出或超时。

本页介绍如何使用 Rust Lambda 函数处理程序，包括项目初始化、命名约定和最佳实践。本页还包括 Rust Lambda 函数的示例，在示例中该函数接收订单信息，生成文本文件收据，然后将此文件放入 Amazon Simple Storage Service（S3）存储桶中。有关如何在编写函数后部署函数的信息，请参阅[使用 .zip 文件存档部署 Rust Lambda 函数](rust-package.md)。

**Topics**
+ [

## 设置 Rust 处理程序项目
](#rust-handler-setup)
+ [

## 示例 Rust Lambda 函数代码
](#rust-example-code)
+ [

## Rust 处理程序的有效类定义
](#rust-handler-signatures)
+ [

## 处理程序命名约定
](#rust-example-naming)
+ [

## 定义和访问输入事件对象
](#rust-handler-input)
+ [

## 访问和使用 Lambda 上下文对象
](#rust-example-context)
+ [

## 在处理程序中使用 适用于 Rust 的 AWS SDK
](#rust-example-sdk-usage)
+ [

## 评估环境变量
](#rust-example-envvars)
+ [

## 使用共享状态
](#rust-shared-state)
+ [

## Rust Lambda 函数的代码最佳实践
](#rust-best-practices)

## 设置 Rust 处理程序项目
<a name="rust-handler-setup"></a>

使用 Rust Lambda 函数时，涉及编写代码、对其进行编译，以及将编译后的构件部署到 Lambda。在 Rust 中设置 Lambda 处理程序项目的最简单方法是使用[适用于 Rust 的 AWS Lambda 运行时](https://github.com/aws/aws-lambda-rust-runtime)。尽管名称如此，但适用于 Rust 的 AWS Lambda 运行时并不是托管运行时，与适用于 Python、Java 或 Node.js 的 Lambda 中的托管运行时不同。相反，适用于 Rust 的 AWS Lambda 运行时是一个 crate（`lambda_runtime`），支持在 Rust 中编写 Lambda 函数并与 AWS Lambda 的执行环境交互。

使用以下命令安装 [Cargo Lambda](https://www.cargo-lambda.info/guide/what-is-cargo-lambda.html)，它是 Cargo 命令行工具的第三方开源扩展程序，可简化 Rust Lambda 函数的构建和部署过程：

```
cargo install cargo-lambda
```

成功安装 `cargo-lambda` 后，使用以下命令初始化新的 Rust Lambda 函数处理程序项目：

```
cargo lambda new example-rust
```

当您运行此命令时，命令行界面（CLI）会询问您有关 Lambda 函数的几个问题：
+ **HTTP 函数** – 如果您打算通过 [API 网关](services-apigateway.md)或[函数 URL](urls-configuration.md) 调用您的函数，请回答**是**。否则，请回答**否**。在本页的示例代码中，我们使用自定义 JSON 事件来调用函数，因此我们回答**否**。
+ **事件类型** – 如果您打算使用预定义的事件形状来调用您的函数，请选择正确的预期事件类型。否则，请将此选项留空。在本页的示例代码中，我们使用了自定义 JSON 事件来调用函数，因此我们将此选项留空。

成功运行命令后，进入项目的主目录：

```
cd example-rust
```

此命令会在 `src` 目录中生成 `generic_handler.rs` 文件和 `main.rs` 文件。`generic_handler.rs` 可用于自定义通用事件处理程序。`main.rs` 文件包含您的主应用程序逻辑。`Cargo.toml` 文件包含有关您程序包的元数据并列出了其外部依赖项。

## 示例 Rust Lambda 函数代码
<a name="rust-example-code"></a>

以下示例 Rust Lambda 函数代码接收有关订单的信息，生成文本文件接收，并将此文件放入 Amazon S3 存储桶中。

**Example `main.rs` Lambda 函数**  

```
use aws_sdk_s3::{Client, primitives::ByteStream};
use lambda_runtime::{run, service_fn, Error, LambdaEvent};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::env;

#[derive(Deserialize, Serialize)]
struct Order {
    order_id: String,
    amount: f64,
    item: String,
}

async fn function_handler(event: LambdaEvent<Value>) -> Result<String, Error> {
    let payload = event.payload;

    // Deserialize the incoming event into Order struct
    let order: Order = serde_json::from_value(payload)?;

    let bucket_name = env::var("RECEIPT_BUCKET")
        .map_err(|_| "RECEIPT_BUCKET environment variable is not set")?;

    let receipt_content = format!(
        "OrderID: {}\nAmount: ${:.2}\nItem: {}",
        order.order_id, order.amount, order.item
    );
    let key = format!("receipts/{}.txt", order.order_id);

    let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
    let s3_client = Client::new(&config);

    upload_receipt_to_s3(&s3_client, &bucket_name, &key, &receipt_content).await?;

    Ok("Success".to_string())
}

async fn upload_receipt_to_s3(
    client: &Client,
    bucket_name: &str,
    key: &str,
    content: &str,
) -> Result<(), Error> {
    client
        .put_object()
        .bucket(bucket_name)
        .key(key)
        .body(ByteStream::from(content.as_bytes().to_vec()))  // Fixed conversion
        .content_type("text/plain")
        .send()
        .await?;

    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    run(service_fn(function_handler)).await
}
```

此 `main.rs` 文件包含以下代码部分：
+ `use` 语句：使用这些语句可导入 Lambda 函数所需的 Rust crate 和方法。
+ `#[derive(Deserialize, Serialize)]`：在此 Rust 结构中定义预期输入事件的形状。
+ `async fn function_handler(event: LambdaEvent<Value>) -> Result<String, Error>`：这是包含主应用程序逻辑的**主处理程序方法**。
+ `async fn upload_receipt_to_s3 (...)`：这是主 `function_handler` 方法引用的帮助程序方法。
+ `#[tokio::main]`：这是一个标记 Rust 程序入口点的宏。它还会设置一个 [Tokio 运行时](https://docs.rs/tokio/latest/tokio/runtime/index.html)，允许您的 `main()` 方法使用 `async`/`await` 并异步运行。
+ `async fn main() -> Result<(), Error>`：`main()` 函数是代码的入口点。在其中，我们指定 `function_handler` 为主处理程序方法。

### Cargo.toml 文件示例
<a name="rust-cargo-toml"></a>

此函数随附以下 `Cargo.toml` 文件。

```
[package]
name = "example-rust"
version = "0.1.0"
edition = "2024"

[dependencies]
aws-config = "1.5.18"
aws-sdk-s3 = "1.78.0"
lambda_runtime = "0.13.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
```

要使此函数正常运行，其[执行角色](lambda-intro-execution-role.md)必须允许 `s3:PutObject` 操作。此外，请确保您定义了 `RECEIPT_BUCKET` 环境变量。成功调用后，Amazon S3 存储桶应包含接收文件。

## Rust 处理程序的有效类定义
<a name="rust-handler-signatures"></a>

在大多数情况下，您在 Rust 中定义的 Lambda 处理程序签名将采用以下格式：

```
async fn function_handler(event: LambdaEvent<T>) -> Result<U, Error>
```

对于此处理程序：
+ 此处理程序的名称为 `function_handler`。
+ 处理程序的单一输入是事件，类型为 `LambdaEvent<T>`。
  + `LambdaEvent` 是 `lambda_runtime` crate 里的包装程序。使用此包装程序可以访问上下文对象，其中包括特定于 Lambda 的元数据，例如调用的请求 ID。
  + `T` 是反序列化事件类型。例如，它可以是 `serde_json::Value`，允许处理程序接收任何通用 JSON 输入。或者，如果您的函数需要特定的预定义输入类型，它也可以是像 `ApiGatewayProxyRequest` 这样的类型。
+ 处理程序的返回类型为 `Result<U, Error>`。
  + `U` 是反序列化的输出类型。`U` 必须实现 `serde::Serialize` 特性，这样 Lambda 才能将返回值转换为 JSON。例如，`U` 可以是像 `String`、`serde_json::Value` 这样的简单类型，也可以是自定义结构，前提是它实现了 `Serialize`。当您的代码到达 Ok(U) 语句时，表示成功执行，您的函数会返回一个 `U` 类型的值。
  + 当您的代码遇到错误（即 `Err(Error)`）时，您的函数会在 Amazon CloudWatch 中记录错误并返回 `Error` 类型的错误响应。

在我们的示例中，处理程序签名如下所示：

```
async fn function_handler(event: LambdaEvent<Value>) -> Result<String, Error>
```

其他有效的处理程序签名可能具有以下特点：
+ 省略 `LambdaEvent` 包装程序 – 如果省略 `LambdaEvent`，则无法访问函数内的 Lambda 上下文对象。以下是该签名类型的示例：

  ```
  async fn handler(event: serde_json::Value) -> Result<String, Error>
  ```
+ 使用单位类型作为输入 – 对于 Rust，您可以使用单位类型来表示空输入。这通常用于具有定期计划调用的函数。以下是该签名类型的示例：

  ```
  async fn handler(_: ()) -> Result<Value, Error>
  ```

## 处理程序命名约定
<a name="rust-example-naming"></a>

Rust 中的 Lambda 处理程序并没有严格的命名限制。尽管您可以为处理程序使用任何名称，但 Rust 中的函数名称通常采用 `snake_case`。

对于较小的应用程序，例如在该示例中，您可以使用单个 `main.rs` 文件来包含所有代码。对于较大的项目，`main.rs` 应包含函数的入口点，但您可以使用其他文件将代码分成逻辑模块。例如，您可以使用以下文件结构：

```
/example-rust
│── src/
│   ├── main.rs        # Entry point
│   ├── handler.rs     # Contains main handler
│   ├── services.rs    # [Optional] Back-end service calls
│   ├── models.rs      # [Optional] Data models
│── Cargo.toml
```

## 定义和访问输入事件对象
<a name="rust-handler-input"></a>

JSON 是 Lambda 函数最常用且最标准的输入格式。在此示例中，该函数需要类似于下方的输入：

```
{
    "order_id": "12345",
    "amount": 199.99,
    "item": "Wireless Headphones"
}
```

在 Rust 中，您可以在结构中定义预期输入事件的形状。在此示例中，我们定义了以下结构来表示 `Order`：

```
#[derive(Deserialize, Serialize)]
struct Order {
    order_id: String,
    amount: f64,
    item: String,
}
```

此结构与预期的输入形状相匹配。在此示例中，`#[derive(Deserialize, Serialize)]` 宏会自动生成用于序列化和反序列化的代码。这意味着我们可以使用 `serde_json::from_value()` 方法将通用输入 JSON 类型反序列化到我们的结构中。处理程序的前几行对此进行了说明：

```
async fn function_handler(event: LambdaEvent<Value>) -> Result<String, Error> {
    let payload = event.payload;

    // Deserialize the incoming event into Order struct
    let order: Order = serde_json::from_value(payload)?;
    ...
}
```

然后，您可以访问该对象的字段。例如，`order.order_id` 从原始输入中检索 `order_id` 的值。

### 预定义的输入事件类型
<a name="rust-input-event-types"></a>

`aws_lambda_events` crate 中有许多预定义的输入事件类型。例如，如果您打算使用 API 网关来调用您的函数，包括以下导入：

```
use aws_lambda_events::event::apigw::ApiGatewayProxyRequest;
```

请确保您的主处理程序使用了以下签名：

```
async fn handler(event: LambdaEvent<ApiGatewayProxyRequest>) -> Result<String, Error> {
    let body = event.payload.body.unwrap_or_default();
    ...
}
```

有关其他预定义输入事件类型的更多信息，请参阅 [aws\$1lambda\$1events crate](https://crates.io/crates/aws_lambda_events)。

## 访问和使用 Lambda 上下文对象
<a name="rust-example-context"></a>

Lambda [上下文对象](rust-context.md)包含有关调用、函数和执行环境的信息。在 Rust 中，`LambdaEvent` 包装程序包括上下文对象。例如，您可以使用上下文对象通过以下代码来检索当前调用的请求 ID：

```
async fn function_handler(event: LambdaEvent<Value>) -> Result<String, Error> {
    let request_id = event.context.request_id;
    ...
}
```

有关上下文对象的更多信息，请参阅[使用 Lambda 上下文对象检索 Rust 函数信息](rust-context.md)。

## 在处理程序中使用 适用于 Rust 的 AWS SDK
<a name="rust-example-sdk-usage"></a>

通常，您将使用 Lambda 函数与其他 AWS 资源进行交互或对其进行更新。与此类资源最简单的交互方法是使用 [适用于 Rust 的 AWS SDK](https://docs.aws.amazon.com/sdk-for-rust/latest/dg/welcome.html)。

要向函数添加 SDK 依赖项，请将其添加到 `Cargo.toml` 文件。建议仅添加函数所需的库。在上述示例代码中，我们使用了 `aws_sdk_s3::Client`。在 `Cargo.toml` 文件中，您可以通过在 `[dependencies]` 部分下添加以下行来添加此依赖项：

```
aws-sdk-s3 = "1.78.0"
```

**注意**  
这可能不是最新的版本。为您的应用程序选择合适的版本。

然后，直接在代码中导入这些依赖项：

```
use aws_sdk_s3::{Client, primitives::ByteStream};
```

示例代码随即按如下方法初始化 Amazon S3 客户端：

```
let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
let s3_client = Client::new(&config);
```

初始化 SDK 客户端后，您可以使用该客户端与其他 AWS 服务进行交互。示例代码在 `upload_receipt_to_s3` 帮助函数中调用 Amazon S3 `PutObject` API。

## 评估环境变量
<a name="rust-example-envvars"></a>

在处理程序代码中，您可以使用 `env::var` 方法引用任何[环境变量](configuration-envvars.md)。在此示例中，我们使用以下代码行引用已定义的 `RECEIPT_BUCKET` 环境变量：

```
let bucket_name = env::var("RECEIPT_BUCKET")
    .map_err(|_| "RECEIPT_BUCKET environment variable is not set")?;
```

## 使用共享状态
<a name="rust-shared-state"></a>

您可以声明独立于 Lambda 函数的处理程序代码的共享变量。这些变量可以帮助您在函数接收任何事件之前的 [Init 阶段](lambda-runtime-environment.md#runtimes-lifecycle-ib) 期间加载状态信息。例如，您可以通过更新 `main` 函数和处理程序签名来修改此页面上的代码，以便在初始化 Amazon S3 客户端时使用共享状态：

```
async fn function_handler(client: &Client, event: LambdaEvent<Value>) -> Result<String, Error> {
    ...
    upload_receipt_to_s3(client, &bucket_name, &key, &receipt_content).await?;
    ...
}

...
      
#[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
```

## Rust Lambda 函数的代码最佳实践
<a name="rust-best-practices"></a>

在构建 Lambda 函数时，请遵循以下列表中的指南，采用最佳编码实践：
+ **从核心逻辑中分离 Lambda 处理程序。**这样您可以创建更容易进行单元测试的函数。
+ **将依赖关系的复杂性降至最低。**首选在[执行环境](lambda-runtime-environment.md)启动时可以快速加载的更简单的框架。
+ **将部署包大小精简为只包含运行时必要的部分。**这样会减少调用前下载和解压缩部署程序包所需的时间。

**利用执行环境重用来提高函数性能。**连接软件开发工具包 (SDK) 客户端和函数处理程序之外的数据库，并在 `/tmp` 目录中本地缓存静态资产。由函数的同一实例处理的后续调用可重用这些资源。这样就可以通过缩短函数运行时间来节省成本。

为了避免调用之间潜在的数据泄露，请不要使用执行环境来存储用户数据、事件或其他具有安全影响的信息。如果您的函数依赖于无法存储在处理程序的内存中的可变状态，请考虑为每个用户创建单独的函数或单独的函数版本。

**使用 keep-alive 指令来维护持久连接。**Lambda 会随着时间的推移清除空闲连接。在调用函数时尝试重用空闲连接会导致连接错误。要维护您的持久连接，请使用与运行时关联的 keep-alive 指令。有关示例，请参阅[在 Node.js 中通过 Keep-Alive 重用连接](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/node-reusing-connections.html)。

**使用[环境变量](configuration-envvars.md)将操作参数传递给函数。**例如，您在写入 Amazon S3 存储桶时，不应对要写入的存储桶名称进行硬编码，而应将存储桶名称配置为环境变量。

**避免在 Lambda 函数中使用递归调用**，在这种情况下，函数会调用自己或启动可能再次调用该函数的进程。这可能会导致意想不到的函数调用量和升级成本。如果您看到意外的调用量，请立即将函数保留并发设置为 `0` 来限制对函数的所有调用，同时更新代码。

Lambda 函数代码中**不要使用非正式的非公有 API**。对于 AWS Lambda 托管式运行时，Lambda 会定期为 Lambda 的内部 API 应用安全性和功能更新。这些内部 API 更新可能不能向后兼容，会导致意外后果，例如，假设您的函数依赖于这些非公有 API，则调用会失败。请参阅 [API 参考](https://docs.aws.amazon.com/lambda/latest/api/welcome.html)以查看公开发布的 API 列表。

**编写幂等代码。**为您的函数编写幂等代码可确保以相同的方式处理重复事件。您的代码应该正确验证事件并优雅地处理重复事件。有关更多信息，请参阅[如何使我的 Lambda 函数具有幂等性？](https://aws.amazon.com/premiumsupport/knowledge-center/lambda-function-idempotent/)。