Googleが公開しているRustのRPCフレームワークtarpcを使ってみた

## tarpcとは

tarpcはRustでRPCを書くためのフレームワークで、Googleのオープンソースプロジェクトです。

https://github.com/google/tarpc

公式には以下のように書いてあります。

> tarpc is an RPC framework for rust with a focus on ease of use. Defining a service can be done in just a few lines of code, and most of the boilerplate of writing a server is taken care of for you.

簡単にサービス定義がかけることを重視しているようです。

また、

> Disclaimer: This is not an official Google product.

とあるように、Googleが会社として開発しているわけではなく、Googleの１従業員が20%ルールとか、趣味とかで作ったものをGoogleの許可を得て公開しているものと思われます。

## どんな感じで使うのか

以下に完全なサンプルがあるのでこれをベースにすれば簡単にできそうです。

https://github.com/google/tarpc/tree/master/example-service

*注意：
この`example-service`というサンプルでは、`lib.rs`にサービス定義が書いてあるんですが、これを`service`という名前空間にセットしています。
なので、`client.rs`にある`service::WorldClient`というのはlib.rsに書かれた`WorldClient` (`#[tarpc::service]`により自動生成）のことを意味しています。
サーバーのほうには`WorldServer`みたいなのは自動実装されません。*

### サービス定義

サービス定義は以下のような感じです。通常traitではasyncなfunctionは定義出来ない（[async-trait](https://github.com/dtolnay/async-trait)を使う）ので、`#[tarpc::service]`がいい感じに中身を展開してサービス定義にしてくれているようです。

```rust
#[tarpc::service]
pub trait World {
    /// Returns a greeting for name.
    async fn hello(name: String) -> String;
}
```

### サービスの実装（サーバー側）

サービスは以下のような感じで実装します。README.mdにあるサンプルより簡単になっています。特に返り値が`Future`じゃなく`String`になっているあたり進化していますね。

```rust
#[derive(Clone)]
struct HelloServer(SocketAddr);

#[tarpc::server]
impl World for HelloServer {
    async fn hello(self, _: context::Context, name: String) -> String {
        format!("Hello, {}! You are connected from {:?}.", name, self.0)
    }
}
```

サンプルなので、引数と接続情報を返す意味のない関数になっています。

### 各バイナリの実装

あとは上記サービス定義を使ってサービスの実行ファイルを作ればよいです。

#### クライアント

`trapc::serde_transport::tcp`にRustではおなじみのserdeによるシリアライゼーションを利用したtcp通信が用意されています。

```rust
    let mut transport = tarpc::serde_transport::tcp::connect(server_addr, Json::default);
    transport.config_mut().max_frame_length(4294967296);
    let mut client =
        service::WorldClient::new(client::Config::default(), transport.await?).spawn()?;
    let hello = client.hello(context::current(), name).await?;
```


#### サーバー

こちらはクライントに比べるとちょっと複雑です、というか用意されているサンプルが最小限ではなくいろんなオプションを指定しています。

```rust
    let mut listener = tarpc::serde_transport::tcp::listen(&server_addr, Json::default).await?;
    listener.config_mut().max_frame_length(4294967296);
    listener
        .filter_map(|r| future::ready(r.ok()))
        .map(server::BaseChannel::with_defaults)
        .max_channels_per_key(1, |t| t.as_ref().peer_addr().unwrap().ip())
        .map(|channel| {
            let server = HelloServer(channel.as_ref().as_ref().peer_addr().unwrap());
            channel.respond_with(server.serve()).execute()
        })
        .buffer_unordered(10)
        .for_each(|_| async {})
        .await;
```

## 実行してみる

### ビルド

```bash
gh repo clone google/tarpc
cd tarpc/example-service
cargo build --release
```

### サーバーの実行

```bash
../target/release/server --port 12345
```

### クライントの実行

```bash
$ ../target/release/client --name OTL --server_addr 127.0.0.1:12345
Hello, OTL! You are connected from 127.0.0.1:61610.
```

## その他のサンプル

example-service以外にも`tarpc/examples`にいろんなサンプルが用意されていますので一読の価値ありです。

https://github.com/google/tarpc/tree/master/tarpc/examples

## 終わりに

`async-trait`と同時に使えると面白いなー、と思って調査を始めたのですが、関数名、trait名がconflictしてしまうので不可能そうでした。
Rustだとserdeでシリアライズの問題がほぼ完全にとけているのでこういうフレームワークもマクロを利用して簡単に(?)作れそうだなぁ、と思いました。
使ってみた、というより試してみた、って感じの内容になってしまっていますが、examplesのほうは結構充実しているのでいろいろできそうです。pubsubとかありました。

以上、元Googlerの[スマイルロボティクス株式会社](https://www.smilerobotics.com)代表取締役社長[OTL](https://twitter.com/OTL)こと小倉がお送りしました〜。