Rustのプロジェクトを始める前に知っておきたかったこと

先日AWS lambdaをRustで実装する案件をやった。工数的には１ヶ月もないくらい。
Rustは6,7年前にちらっと勉強しただけで、実質未経験だったけど、ちょっとした案件だったので、わからないことは[Rust by example](https://doc.rust-lang.org/stable/rust-by-example/)などを見ながらどうにかなると思ってた。
実際は、ライブラリの作法や事情/背景を知らず混乱したり、雰囲気で分かったと思っていても裏側の仕組みを認識してなくて、後々ハマることが多かった。そこで少し前の自分向けに事前にこういうこと知っておきたかったと思ったことをまとめておく。

識者の方々には不正確なところ、間違ってはいないがミスリードしそう、言い回しが不自然、これも触れたほうが親切、等々、ご指摘いただけると助かります。

# Resultに関する諸々
Rustにはtry-catchの例外機構はない。他の言語でいうところの`Either`型と同じ構造の`Result`型を使う。

```rust
pub enum Result<T, E> {
    Ok(T),
    Err(E),
}
```
`main`関数は`Result`型を返すことができる。失敗時の型は`std::fmt::Debug` traitが実装されていればなんでもいい。

## ファイル出力
例としてファイル出力するコードサンプルから解説する。

```rust
use std::io::prelude::*;
use std::fs::File;

fn main() -> std::io::Result<()> {
    let mut file = File::create("foo.txt")?;
    file.write_all(b"Hello, world!")
}
```

`std::io`はファイルに限らない入出力一般のインターフェースおよび実装を提供しており、`std::io::prelude`は`std::io`でよく使われるtraitをまとめてimportするためのモジュールである。この例では、`std::fs::File`がimplementしている`std::io::Write`traitが`std::io::prelude`に含まれているため、`std::io::Write`で定義されている`write_all`が使えるようになる。Rustは字面はオブジェクト指向っぽいが、ライブラリ構造や使い方はHaskellのそれっぽさを感じる。

`std::io::Result`は標準の`Result<T, E>`の`E`のエラー型を`std::io::Error`に固定したもので、`std::io::Error`は`std::error::Error`traitを実装した`std::io`固有のエラーを表すstructである。
Javaとの対応を書いて整理すると下記表の感じ

| Java                        | Rust                          |
|-----------------------------------+-------------------------------|
| `java.lang.Throwable` | `std::error::Error` (trait)   |
| `java.io.IOException`     | `std::io::Error`  (struct) |
このように、Rustは`Error`や`Result`などの一般性の高いものでも、ライブラリの中で同じ名前で定義していくスタイルらしい。


`File::create`も`file.write_all`も`Result`型を返すが、rustにはモナド用の構文がない。
代わりに`Option`と`Result`については`?`演算子を使うと、値の取り出しとエラー時の早期returnをいい感じにしてくれる。
上記のコードは`?`演算子を使わないと下記のようなコードになる。

```rust
fn main() -> std::io::Result<()> {
    let mut file = match File::create("foo.txt") {
        Ok(t) => t,
        Err(e) => return Err(e)
    };
    file.write_all(b"Hello, world!")
}
```

## JSONにシリアライズする
`serde_json`でJSONのシリアライズ/デシリアライズができる。

```rust
use serde_derive::{Serialize, Deserialize};

#[derive(Serialize)]
struct Person {
    name: String,
    age: u8,
}
fn main() -> Result<(), serde_json::Error> {
    let model = Person { name: "taro".to_owned(), age: 18 };
    let json_text = serde_json::to_string(&model)?;
    println!("{}", json_text);
    Ok(())
}
```
structに`#[derive(Serialize)]`をつけると、そのstruct用のシリアライザが生成されて、`serde_json::to_string`でJSON出力している。JSON出力も失敗しうるのでResult型を返す。エラー型は`serde_json::Error`となる。
JSONに変換してファイルに出力しようとすると`serde_json::Error`と`std::io::Error`が混在することになるが、このケースでは何もしなくても、戻り型は`Result<(), std::io::Error>`のままでコンパイルできる。

```rust
 fn main() -> Result<(), std::io::Error> {
    let model = Person { name: "taro".to_owned(), age: 18 };
    let json_text = serde_json::to_string(&model)?;
    let mut file = File::create("foo.txt")?;
    file.write_all(&json_text.as_bytes())
}
```

`?`を使うと、先に述べた早期リターンのコード書き換えだけでなく、エラー型を`Into`traitによって型の変換をしてくれる。つまり`serde_json::to_string(&model).map_err(Into::into)`的なことが行われる。

`serde_json::Error`→`std::io::Error`の変換ができるかを調べるために、`serde_json::Error`のrustdocの[Trait Implementations](https://docs.serde.rs/serde_json/struct.Error.html#trait-implementations)を見ると`Error`を含む定義がいくつか並んでいる。
![serde_json_error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12251/eec505c8-9e5b-59f6-ec53-21421ab1876f.png)

`Error`にマウスをホバーすると、ツールチップに名前空間が見えるので、それぞれ異なる`Error`だと分かる（このUIに誰も疑問を感じないのだろうか？？？）。`From<serde_json::Error> for std::io::Error`によって変換が可能となっている。(`From`または`Into`のどちらかが定義されていれば変換ができる)

今回は変換できたのでいいが、そうでない場合に自力で型の変換をするのはだるい。`std::io::Error`も`serde_json::Error`も`std::error::Error`traitを実装しているため、`Box<dyn std::error::Error>`型でまとめて扱うことができる。実行時まで具体的な型が分からないので`dyn`というキーワードがついている。また型が分からないと、サイズが決まらないので、stackではなくheapに置く必要があるためBox型になる。
`From<serde_json::Error> for std::io::Error`がなかったと仮定しても、Box型を使った下記のコードでもコンパイルは通る。

```rust
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let model = Person { name: "taro".to_owned(), age: 18 };
    let json_text = serde_json::to_string(&model)?;
    let mut file = File::create("foo.txt")?;
    file.write_all(&json_text.as_bytes())?;
    Ok(())
}
```
最後の`Ok(())`とその前の文の`?`演算子がないとコンパイルエラーになる。

```rust
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let model = Person { name: "taro".to_owned(), age: 18 };
    let json_text = serde_json::to_string(&model)?;
    let mut file = File::create("foo.txt")?;
    file.write_all(&json_text.as_bytes())
}

/*
error[E0308]: mismatched types
   --> src/main.rs:190:5
    |
184 | fn main() -> Result<(), Box<dyn std::error::Error>> {
    |              -------------------------------------- expected `std::result::Result<(), std::boxed::Box<(dyn std::error::Error + 'static)>>` because of return type
...
190 |     file.write_all(&json_text.as_bytes())
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `std::boxed::Box`, found struct `std::io::Error`
    |
    = note: expected enum `std::result::Result<_, std::boxed::Box<(dyn std::error::Error + 'static)>>`
               found enum `std::result::Result<_, std::io::Error>`
*/
```

write_allの戻り型は`Result<(), std::io::Error>`なので、エラー型を`Box`型に持ち上げる必要がある。`?`演算子によって`Into`でBOX型へ変換されてコンパイルできている。ネット上のサンプルコードを見ていると変換がなくても最後の`Ok(())`は書いてる。

## AWS lambda上で動かす
`aws-lambda-rust-runtime`を使用してAWS lambda上で動かすことができる。

```rust
use serde_derive::{Serialize, Deserialize};
use lambda_runtime::{lambda, Context, error::HandlerError};

#[derive(Serialize, Deserialize)]
pub struct SampleRequest{
    name: String,
    num: i64,
}

#[derive(Serialize)]
pub struct SampleResponse{
    message: String,
}

fn handler(event: SampleRequest, _ctx: Context) -> Result<SampleResponse, HandlerError> {
    let json = serde_json::to_string(&event)?;
    Ok(SampleResponse{ message: format!("request is {}", json) })
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    simple_logger::init_with_level(log::Level::Debug)?;
    lambda!(handler);
    Ok(())
}

```
`handler`関数がリクエストを処理する関数で、serde_jsonを使用してリクエストのJSONをデシリアライズしたものを第１引数に渡して、関数を実行して、戻り値をまたserde_jsonでシリアライズしてレスポンスとして返す。main関数の場合はエラー型はこちら側で決めることができたが、`aws-lambda-rust-runtime`を使う場合は、ハンドラのエラー型が`lambda::error::HandlerError`であることが要請される。
`serde_json::to_string`は`Result<String, serde_json::Error>`を返すが、`HandlerError`に`serde_json::Error`からの変換が定義されているため、上記のコードは動作する。

ファイルの出力もするようにハンドラを書き換えてみる。

```rust
fn handler(event: SampleRequest, _ctx: Context) -> Result<SampleResponse, HandlerError> {
    let json = serde_json::to_string(&event)?;
    let mut file = File::create("foo.txt")?;
    file.write_all(json.as_bytes())?;
    Ok(SampleResponse{ message: format!("request was {}", json) })
}
```
これは次のようなコンパイルエラーになる。

```
error[E0277]: `?` couldn't convert the error to `lambda_runtime_errors::HandlerError`
  --> src/main.rs:45:43
   |
45 |     let mut file = File::create("foo.txt")?;
   |                                           ^ the trait `std::convert::From<std::io::Error>` is not implemented for `lambda_runtime_errors::HandlerError`
   |
   = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
   = help: the following implementations were found:
             <lambda_runtime_errors::HandlerError as std::convert::From<&str>>
             <lambda_runtime_errors::HandlerError as std::convert::From<failure::error::Error>>
             <lambda_runtime_errors::HandlerError as std::convert::From<serde_json::error::Error>>
             <lambda_runtime_errors::HandlerError as std::convert::From<std::alloc::LayoutErr>>
           and 22 others
   = note: required by `std::convert::From::from`
```
エラーメッセージはかなり親切で、`std::io::Error`から`lambda_runtime_errors::HandlerError`への変換ができないことだけでなく、どんな型なら変換できるか、いくつか候補を出してくれる。

ここで、先の候補の上から２番めにある`failure::error::Error`とは何か？
rust標準の`std::error::Error`が使いにくいので、代替えのライブラリが現れては消えてが繰り返されており、failureはその中のひとつである。エラーライブラリの歴史はここ( https://blog.yoshuawuyts.com/error-handling-survey/ )にまとめらているが、2017年11月に登場したfailureは2019年にはdeprecatedとなっている。Rust界隈の開発の激しさを感じる。現在も決定打はまだないらしい。

`failure`はすでにdeprecatedだが`aws-lambda-rust-runtime`が使っているなら受け入れざるを得ない。
`std::io::Error`→`failure::Error`→`HandlerError`と２段階の変換すればうまくいく

```rust
fn write(content: &[u8]) -> Result<(), failure::Error> {
    let mut file = File::create("foo.txt")?;
    file.write_all(content)?;
    Ok(())
}

fn handler(event: SampleRequest, _ctx: Context) -> Result<SampleResponse, HandlerError> {
    let json = serde_json::to_string(&event)?;
    write(json.as_bytes())?;
    Ok(SampleResponse{ message: format!("request was {}", json) })
}
```

Rust界隈の荒波の例として、非同期処理も大変なことになっている。 ~~`aws-lambda-rust-runtime`の`future`は0.3系だが、rust用のAWS SDKライブラリである`rusoto`が使っている`future`は0.1系で混乱したりした。~~
[2020.06.25 追記]
`aws-lambda-rust-runtime`の`future`も0.1系で、ただ自分の設定ミスで混乱してただけだった。次の節を追記した。

[2020.06.29 追記]
最新のコードを見たら、failureからanyhowに置き換えた後、ライブラリ内で下記のように定義されているErrorに置き換えていた。

```rust
type Error = Box<dyn std::error::Error + Send + Sync + 'static>
```

# S3のオブジェクトを読み込む
[2020.06.26追記]
`aws-lambda-rust-runtime`も`rusoto`も0.3系に移行済み or ほぼ移行済みとのこと
https://qiita.com/maeda_/items/d765d514e7c72778f29f#comment-a40c4cef80af00a3b887

```rust
use rusoto_core::Region;
use rusoto_s3::{S3, S3Client, GetObjectRequest};
use futures::stream::Stream;
use futures::future::Future;

fn read_s3_object(bucket: String, key: String) -> Result<String, Box<dyn std::error::Error>> {
    let client = S3Client::new(Region::default());
    let request = GetObjectRequest {bucket, key, ..GetObjectRequest::default()};
    let obj = client.get_object(request).sync()?;
    let stream = obj.body.expect("body is empty");
    let body = stream.concat2().wait()?;
    Ok(String::from_utf8_lossy(&body).to_string())
}
```
S3上のファイルを読み込む上記のコードを書いたら、下記のコンパイルエラーが出た

```
error[E0599]: no method named `concat2` found for type `rusoto_signature::stream::ByteStream` in the current scope
  --> src/aws.rs:11:23
   |
11 |     let body = stream.concat2().wait()?;
   |                       ^^^^^^^ method not found in `rusoto_signature::stream::ByteStream`
   |
   = help: items from traits can only be used if the trait is in scope
   = note: the following trait is implemented but not in scope; perhaps add a `use` for it:
           `use futures::stream::Stream;`
```

エラーメッセージにある`use futures::stream::Stream;`もやっているし、なぜ`method not found`が出るのか？ 原因は`Cargo.toml`で`futures`の`0.3.4`を指定していたことで`0.1.29`に変更したら無事コンパイルが通った。当時の自分が特に思慮なく最新を入れていたのだと思う。

# 便利なtrait


## Into/From trait
`?`演算子によるエラー型の変換に使われる`Into`/`From`traitによる変換だが、それ以外の場所でもしばしば使われる。
`Into`または`From`のどちらかが定義されていれば`.into()`をつけると変換できる。

```rust
let x: String = "hoge".into();
let y: f64 = 10.into();
```

`A`に変換できる何かという指定をするためにシグネチャに`Into<A>`と書くこともできる。例えば下記のようなコードが書ける。

```rust
struct Person<'a>{
    name: Cow<'a, str>,
}

fn new_person<'a>(name: impl Into<Cow<'a, str>>) -> Person<'a> {
    Person{ name: name.into()}
}

new_person("yamada");            // 引数は&'a str型
new_person("satoh".to_owned());  // 引数はString型
```

[`Cow<'a, B>`]( https://doc.rust-lang.org/std/borrow/enum.Cow.html )型は`B`の実体か`B`の参照のいずれかを持つ型である。`new_person`の引数は`Into<Cow<'a, str>>`なので、`&'a str`型でも`String`型でも渡すことができる。またstrとStringは暗黙に変換される。

## Default trait
型ごとのデフォルト値を`Default`traitを実装することで定義できる。

```rust
let x: i32 = Default::default();   // 0
let y: String = Default::default(); // 空文字列
```

一部のフィールドを変えてstructのコピーする時、モダンなJavascriptみたいな記法で下記のように書ける。

```rust
let taro = Person(first: "yamada", last: "taro", country: "Japan");
let jiro = Person(first: "jiro", ..taro);
```

この機能と`Default`traitを組み合わせると下記のように書ける。

```rust
let param = ComplexParameter(some: Some("taro"), ..Default:default())
```
`ComplexParameter`にオプショナルなフィールドがたくさんあっても、使用するフィールドのみ指定すれば良い。

serdeでもdefaultを指定することでフィールドない場合に`Default`traitを使って値を生成することができる。

```rust
#[derive(Serialize, Deserialize)]
struct Hoge{
  x: Option<i32>,   // フィールドがない、または、値が空の場合はNone

  #[serde(default)]
  y: i32,           // フィールドがないがない場合は0
}
```
ここで、`default`を指定した場合にフィールドは存在するけど値が空の場合はエラーになるので注意。


# structにスコープがある
関数の中でもstructを宣言することができる。もちろん関数外からはそのstructは参照できない。どういう時に便利かの例として、`serde_dynamodb`というライブラリを紹介する。
AWSのサービスをRustから使う時`rusoto`を使うが、`rusoto`はAWSのAPI仕様からコードを自動生成している。自動生成された型を真面目に書こうとするとかなり辛い。
例えば、dynamodbのあるレコードの`timestamp`属性を`u64`型の`time`変数の値で書き換えたいとする。
`rusoto_dynamodb::UpdateItemInput`の[`expression_attribute_values`]( https://rusoto.github.io/rusoto/rusoto_dynamodb/struct.UpdateItemInput.html#structfield.expression_attribute_values )
フィールドは`Option<HashMap<String, AttributeValue>>`型であり、これを愚直に書くと

```rust
let values:Option<HashMap<String, AttributeValue>> = Some(vec![
    ("timestamp".to_owned(), AttributeValue{n: Some(time.to_string()), ..Default::default()})
].iter().cloned().collect());
```
となる。(`timestamp`に数値で格納するために、`AttributeValue`の`n`フィールドに値を入れる。文字列なら`s`フィールドであったり、型によってフィールドを変える必要がある。)

`serde_dynamodb`を使うと、下記のように書ける。

```rust
#[derive(Serialize, Deserialize)]
struct Update {
    timestamp: u64,
}

let values = serde_dynamodb::to_hashmap(&Update{timestamp: time})?;
```

`serde_dynamodb`を使ったほうがboiler plateがなくなり、意図が明確になる。しかし、ここで定義したstructは他の場所で使うことはない。こういう使い捨てのstructを関数内に書けば、命名にも悩まないし、実際に使う場所も絞られてうれしい。

ちなみに`serde`はRustの汎用のシリアライザ/デシリアライザで、`serde`本体と形式固有の実装を組み合わせて使う。`serde`があるおかげで、`serde_dynamodb`のようなヘルパーライブラリも実装が容易になる。

# ここまでのまとめ
Rustには記述をシンプルにする仕組みもあり、ライブラリ実装者も気の利いた作りにしてくれたりするが、実際に裏側で何をやっているか把握しないとハマることがある。
また、例外処理や非同期のような基本的なところでも活発に議論されているし、ライブラリも刻々と変わっている。他の言語以上に流行り廃りが激しいことに留意しておくことが大切に感じた。