

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 在 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)
+ [

## 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 處理常式專案，最簡單的方法便是使用 [AWS Lambda Runtime for Rust](https://github.com/aws/aws-lambda-rust-runtime)。雖然名稱相同，但 Rust 的 AWS Lambda 執行期與適用於 Python、Java 或 Node.js 的 Lambda 中的執行期並不相同。反之， AWS Lambda Runtime for Rust 是一種木箱 (`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 Gateway](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` 檔案包含有關套件的中繼資料，並列出其外部相依項。

## 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`。
+ 處理常式的單一輸入參數為 event，類型為 `LambdaEvent<T>`。
  + `LambdaEvent` 是來自 `lambda_runtime` 套件的包裝函式。借助此包裝函式，您可以存取內容物件，其中包含 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,
}
```

此 struct 符合預期的輸入形狀。在此範例中，`#[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_id` 可以從原始輸入擷取 `order.order_id` 的值。

### 預先定義的輸入事件類型
<a name="rust-input-event-types"></a>

`aws_lambda_events` 套件中有許多預先定義的可用輸入事件類型。例如，若要使用 API Gateway 調用函式，需包含下列匯入：

```
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 函數處理常式程式碼的共用變數。這些變數可協助您在函數接收任何事件之前在 [初始化階段](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 的內部 APIs。這些內部 API 更新可能是向後不相容的，這會導致意外結果，例如若您的函數依賴於這些非公有 API，則叫用失敗。請參閱 [API 參考](https://docs.aws.amazon.com/lambda/latest/api/welcome.html)查看公開可用 API 的清單。

**撰寫等冪程式碼。**為函數撰寫等冪程式碼可確保採用相同方式來處理重複事件。程式碼應正確驗證事件並正常處理重複的事件。如需詳細資訊，請參閱 [How do I make my Lambda function idempotent?](https://aws.amazon.com/premiumsupport/knowledge-center/lambda-function-idempotent/) (如何讓 Lambda 函數等冪？)。