

# Rust の Lambda 関数ハンドラーの定義
<a name="rust-handler"></a>

Lambda 関数*ハンドラー*は、イベントを処理する関数コード内のメソッドです。関数が呼び出されると、Lambda はハンドラーメソッドを実行します。関数は、ハンドラーが応答を返すか、終了するか、タイムアウトするまで実行されます。

このページでは、プロジェクトの初期化、命名規則、ベストプラクティスなど、Rust で Lambda 関数ハンドラーを操作する方法について説明します。このページには、注文に関する情報を取得し、テキストファイル受信を生成し、このファイルを Amazon Simple Storage Service (S3) バケットに配置する Rust Lambda 関数の例も含まれています。関数を書き込んだ後にデプロイする方法の詳細については、「[.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)
+ [ハンドラーでの AWS SDK for Rust の使用](#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) を使用することです。AWS Lambda Runtime for Rust という名称になっていますが、これは Lambda for Python、Java、Node.js と同様のマネージドランタイムではありません。AWS Lambda Runtime for Rust は、Rust での Lambda 関数の書き込みと AWS Lambda の実行環境とのインターフェイス接続をサポートするクレート (`lambda_runtime`) です。

次のコマンドを使用して、Rust Lambda 関数の構築とデプロイを簡素化する Cargo コマンドラインツールへのサードパーティーのオープンソース拡張である [Cargo Lambda](https://www.cargo-lambda.info/guide/what-is-cargo-lambda.html) をインストールします。

```
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` ファイルには、パッケージに関するメタデータが含まれ、その外部依存関係が一覧表示されます。

## 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 クレートとメソッドをインポートします。
+ `#[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` クレート由来のラッパーです。このラッパーを使用すると、呼び出しのリクエスト ID などの Lambda 固有のメタデータを含むコンテキストオブジェクトにアクセスできます。
  + `T` は逆シリアル化されたイベントタイプです。例えば、これは `serde_json::Value` にすることができます。これにより、ハンドラーは汎用 JSON 入力を取得できます。または、関数が特定の事前定義された入力タイプを想定している場合 `ApiGatewayProxyRequest` のようなタイプにすることもできます。
+ ハンドラーの戻り値タイプは `Result<U, Error>` です。
  + `U` は逆シリアル化された出力タイプです。Lambda が戻り値を JSON に変換できるように、`U` は `serde::Serialize` 特性を実装する必要があります。例えば、`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` です。

この例のような小規模なアプリケーションでは、1 つの `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` クレートには、多くの事前定義された入力イベントタイプがあります。例えば、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 クレート](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)」を参照してください。

## ハンドラーでの AWS SDK for Rust の使用
<a name="rust-example-sdk-usage"></a>

多くの場合、Lambda 関数を使用して、他の AWS リソースとやり取りしたり、更新したりします。これらのリソースとインターフェイスする最も簡単な方法は、[AWS SDK for Rust](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 クライアントを初期化したら、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 の内部 API にセキュリティと機能面の更新を定期的に適用します。これらの内部 API 更新には後方互換性がないことがあり、関数にこれらの非公開 API に対する依存関係がある場合、呼び出しの失敗などの意図しない結果につながります。公開されている API のリストについては、「[API リファレンス](https://docs.aws.amazon.com/lambda/latest/api/welcome.html)」を参照してください。

**冪等性コードを記述します。**関数の記述に冪等性コードを使用すると、重複するイベントが同じ方法で処理されるようになります。コードでは、イベントを適切に検証し、重複するイベントを適切に処理する必要があります。詳細については、「[Lambda 関数を冪等にするにはどうすればよいですか?](https://aws.amazon.com/premiumsupport/knowledge-center/lambda-function-idempotent/)」を参照してください。