---
title: tonic (gRPC)
url: https://doc.liz6.com/ja/rust/12-async-ecosystem/02-tonic-gRPC
locale: ja
area: rust
tags:
- rust
- async-ecosystem
date: 2026-06-30
modified: 2026-07-16
description: gRPC は protobuf を用いて強型付けされたサービスインターフェースを定義する——.proto を変更すると、呼び出し元はすべて更新しなければならない。さもないとコンパイルエラーになる（規約ではなく、コンパイラで検証される契約）。tonic は Rust 用の gRPC 実装であり、codegen は .proto から Rust の trait やストリーミング型（unary/client/server/bidi）を生成し、interceptor 層は異なるユースケースや横断的ロジックに対応する。
---

# tonic (gRPC)

> gRPC は protobuf を用いて強型付けされたサービスインターフェースを定義する——.proto を変更すると、呼び出し元はすべて更新しなければならない。さもないとコンパイルエラーになる（規約ではなく、コンパイラで検証される契約）。tonic は Rust 用の gRPC 実装であり、codegen は .proto から Rust の trait やストリーミング型（unary/client/server/bidi）を生成し、interceptor 層は異なるユースケースや横断的ロジックに対応する。

## gRPC と REST の違い

REST は HTTP メソッドと URL を用いて操作を表現し、ペイロードは通常 JSON です。gRPC は protobuf を用いて強型付けされたサービスインターフェースを定義し、ペイロードはバイナリエンコードされた protobuf メッセージです。違いはフォーマットだけでなく、**プログラミングモデル**にあります。REST のインターフェース定義は「規約」（API ドキュメント + 定型コード）です。gRPC のインターフェース定義は**コンパイラで検証される契約**——.proto を変更すると、呼び出し元はすべて更新しなければならない。さもないとコンパイルエラーになります。

tonic は Rust エコシステムで最も成熟した gRPC 実装であり、tokio + tower + prost (protobuf コンパイラ) を基盤としています。

## .proto から Rust 型へ

```protobuf
syntax = "proto3";
package greeter;

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply);           // unary: 1 リクエスト → 1 レスポンス
    rpc StreamHello (HelloRequest) returns (stream HelloReply); // server streaming
    rpc Chat (stream ChatMessage) returns (stream ChatMessage); // bidi streaming
}

message HelloRequest { string name = 1; }
message HelloReply { string message = 1; }
```

`build.rs` 内の `tonic_build::compile_protos()` は protoc と prost を呼び出し、以下を生成します：
- `GreeterServer<T: Greeter>` — サーバーラッパー（`Greeter` trait を tower `Service` にラップ）
- `Greeter` trait — ビジネスロジックのインターフェース（あなたが実装するメソッド）
- `GreeterClient` — クライアントスタブ（呼び出し側が使用する非同期メソッド）

生成されたコードは `#[tonic::async_trait]` を用いて trait メソッドを実装しています。これは、trait 内に `async fn` を含めることを可能にします（Rust のネイティブな trait はまだ `async fn` をサポートしていません）。

## サーバー: trait の実装

```rust
struct MyGreeter;
#[tonic::async_trait]
impl Greeter for MyGreeter {
    async fn say_hello(&self, req: Request<HelloRequest>) -> Result<Response<HelloReply>, Status> {
        let name = req.into_inner().name;
        if name.is_empty() {
            return Err(Status::invalid_argument("name is required"));
        }
        Ok(Response::new(HelloReply {
            message: format!("Hello {}!", name),
        }))
    }
}

tonic::transport::Server::builder()
    .add_service(GreeterServer::new(MyGreeter))
    .serve("[::1]:50051".parse().unwrap()).await?;
```

`Status` は gRPC の標準エラー型です——HTTP ステータスコードではありません。gRPC は 17 種類のステータスコードを定義しています：`Ok`, `Cancelled`, `InvalidArgument`, `NotFound`, `AlreadyExists`, `PermissionDenied`, `Unauthenticated`, `ResourceExhausted`, `Unimplemented`, `Internal`, `Unavailable`, `DeadlineExceeded`。各コードには明確なセマンティクスがあり、HTTP の 400 が「フォーマットエラー」也可能是「パラメータ不足」を表すのとは異なります。

## ストリーミング: 「1 リクエスト 1 レスポンス」だけではない

```rust
// Server streaming: サーバーが段階的にデータを送信
async fn stream_hello(&self, req: Request<HelloRequest>) -> Result<Response<Self::StreamHelloStream>, Status> {
    let mut stream = tokio_stream::iter(vec!["Hello", "Bonjour", "Hola"].into_iter()
        .map(|s| Ok(HelloReply { message: s.into() })));
    Ok(Response::new(Box::pin(stream)))
}

// Bidirectional streaming: 両側が同時に送信
async fn chat(&self, req: Request<Streaming<ChatMessage>>) -> Result<Response<Self::ChatStream>, Status> {
    let mut in_stream = req.into_inner();
    let (tx, rx) = tokio::sync::mpsc::channel(4);
    tokio::spawn(async move {
        while let Some(Ok(msg)) = in_stream.next().await {
            tx.send(Ok(ChatReply { response: format!("echo: {}", msg.text) })).await.unwrap();
        }
    });
    Ok(Response::new(Box::pin(tokio_stream::wrappers::ReceiverStream::new(rx))))
}
```

ストリーミングの鍵：server streaming = サーバーが `stream T` を返し、クライアントが逐次受信する。bidirectional = 両側がそれぞれ `stream` を送信する。これは WebSocket の「双方向チャネル」とは異なります——gRPC ストリーミングの各メッセージは依然として独立した、型付きの protobuf メッセージです。

## Interceptor: リクエストレベルのミドルウェア

```rust
fn auth_interceptor(mut req: Request<()>) -> Result<Request<()>, Status> {
    let token = req.metadata().get("authorization")
        .ok_or_else(|| Status::unauthenticated("missing token"))?;
    validate_token(token)?;
    Ok(req)
}

Server::builder()
    .add_service(GreeterServer::with_interceptor(MyGreeter, auth_interceptor))
```

interceptor はハンドラーの前に実行されます——認証、レート制限、メタデータの注入などが可能です。tower Layer との違い：interceptor は tonic 固有の概念であり、Layer は tower の汎用概念です。tonic の interceptor は実際には tower `Service` の薄いラッパーです。

## 参考

- **tonic**: github.com/hyperium/tonic
- **gRPC**: grpc.io (仕様、ステータスコード)
- **protobuf**: protobuf.dev (エンコーディング、proto3 言語ガイド)

*Keywords: tonic, gRPC, protobuf, streaming, interceptor, Status, unary, bidirectional*
