Borrow and AsRef

## この資料について

2015/08/30 時点の [Borrow と AsRef](https://doc.rust-lang.org/nightly/book/borrow-and-asref.html) について調べたことをまとめたものです。[Rust of Us - Chapter 3](https://rust-of-us.doorkeeper.jp/events/28471) で話したときに使いました。一部のコードは参照している文書から引用しています。

## Borrow と AsRef は似ている

API Doc の Examples を見ても差分がよく分からない。

[Borrow の Examples](https://doc.rust-lang.org/nightly/std/borrow/trait.Borrow.html):

```rust
use std::borrow::Borrow;

fn check<T: Borrow<str>>(s: T) {
    assert_eq!("Hello", s.borrow());
}

let s = "Hello".to_string();
check(s);

let s = "Hello";
check(s);
```

[AsRef の Examples](https://doc.rust-lang.org/nightly/std/convert/trait.AsRef.html):

```rust
fn is_hello<T: AsRef<str>>(s: T) {
   assert_eq!("hello", s.as_ref());
}

let s = "hello";
is_hello(s);

let s = "hello".to_string();
is_hello(s);
```

## Borrow と AsRef は違うもの

実際同じことができる場面があるし「分かれている必要はないのでは」という Issue もあげられていた。しかし、これらの trait は異なる目的で作られたようだ。この Issue をきっかけにして、[TRPL: Borrow and AsRef](https://doc.rust-lang.org/nightly/book/borrow-and-asref.html) が追加された。

* [`Borrow` and `AsRef` are redundant](https://github.com/rust-lang/rust/issues/24140)
* [Improve docs differentiating `Borrow` and `AsRef`](https://github.com/rust-lang/rust/issues/24163)
* [TRPL: Borrow and AsRef](https://github.com/rust-lang/rust/pull/24996)

`Borrow` や `AsRef` は一体どこからやってきたのか。コミットログを追いかけた先にはそれぞれ別々の [RFC](https://github.com/rust-lang/rfcs) があった。

## Borrow はどこから

`Borrow` は [rust-lang/rfcs#235 collections-conventions](https://github.com/rust-lang/rfcs/blob/master/text/0235-collections-conventions.md) で登場した。この RFC は `std::collections` の設計を改善したり API を安定させるというモチベーションで作られた。ここで挙げられている当時の問題のひとつに、`HashMap<String, ...>` オブジェクトに対してキーを渡したいとき、次のように書かなくてはならないというものがあった。

```rust
// map.find("literal") としたいところ
map.find(&"literal".to_string())
```

これは面倒なうえに無駄に heap を使う。当時はこの問題への workaround として、`Equiv` という trait を使っていた。が、それも決して使いやすいものではなかった。`find` に対して `find_equiv` のようなほとんど重複したメソッドを用意しなければならなかったし、`TreeMap` のような順序に依存するものには応用できない限定的なものだった。さらに、`TreeMap` は別の方法でこの "Equiv" 問題を扱っており、全体として一貫性に欠けていた。

```rust
// "literal" で引くには後者を使う必要があった
fn find(&self, k: &K) -> Option<&V>
fn find_equiv<Q: Hash<S> + Equiv<K>>(&self, k: &Q) -> Option<&V>
```

どちらも文字列なのに同じように扱えない。`String` と `&str` の間で起きるようなこの "Equiv" 問題をよりうまく解決するために、この RFC では `Borrrow` trait が提案された。`Borrow` が導入された現在、上記2つのメソッドは廃止され次のメソッドに統一されている。

```rust
fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V> where K: Borrow<Q>, Q: Hash + Eq
```

この変更によって `&String` と `&str` どちらを渡しても同じメソッドで同じ結果を得られるようになった。

```rust
use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();

    map.insert("alpha".to_string(), 1);

    assert_eq!(map.get("alpha"),              Some(&1));
    assert_eq!(map.get(&"alpha".to_string()), Some(&1));
}
```

どちらの型も `Borrow<str>` を実装している。あらゆる場面で両者を同じように扱えるようになった。

```rust
use std::borrow::Borrow;

fn assert_hello_message<T: Borrow<str> + ?Sized>(src: &T) {
    let hello: &str = src.borrow();

    assert_eq!("Hello", hello);
}

fn main() {
    assert_hello_message("Hello");
    assert_hello_message(&"Hello".to_string());
}
```

`Borrow<T>` を実装する場合、`Self` と `T` の実装する `Hash` `Eq` `Ord` はそれぞれ同じ結果になるよう実装するべきだ (`Borrow` 導入の目的を思えば納得がいく)。とはいえ `Borrow` の API は比較結果の一貫性を保証するわけではない。

Blanket implementation として `Borrow<T> for T` が用意されているため、ほとんどの場合は自分で `Borrow` を実装する必要はない。

## AsRef はどこから

一方の `AsRef` は [rust-lang/rfcs#529 conversion-traits](https://github.com/rust-lang/rfcs/blob/master/text/0529-conversion-traits.md):で登場した。これは、データを変換する trait をひとつに統一しようという趣旨の内容だ。ここでデータを変換するというのは、例えば `&str` を `&Path` にするようなことを指す。`&str` を `&Path` に変換できる trait があると次のようなケースで嬉しい。

```rust
// こうではなくて
let new_path = my_path.join(Path::new("fixed_subdir_name"));

// こう書けて嬉しい
let new_path = my_path.join("fixed_subdir_name");
```

これを実現するために当時は `AsPath` という trait が使われていた。`AsPath` を実装したオブジェクトの `as_path()` を呼ぶと `&Path` を得られる。`Path` と `&str` がこれを実装していれば、どちらも引数として使えて便利だ。他にも `AsSlice` `FromStr` など、同じ趣旨の様々な trait があった。

上記の trait は確かに便利だが、このやり方には2つの問題があった。第一に、このようなデータの変換が求められる度に新しい trait を定義しなければならないのはおかしいということ。第二に、例えばもし path のような API に `AsPath` のような仕組みが含まれていなかったら、path を使う複数の crate が似たような (互換性のない) `AsPath` trait を実装してしまう恐れもある。

この RFC ではそれら既存の trait に代わるものとしていくつかの generic な trait を提案している。`AsRef` はそのうちのひとつだ。
現在では `AsPath` は `AsRef<Path>` で置き換えられている。

次のように、様々な型が `AsRef<Path>` を実装している。

```rust
use std::path::Path;

fn print_dirname<T: AsRef<Path>>(src: T) {
    let path: &Path = src.as_ref();

    println!("{}", path.parent().unwrap().display());
}

fn main() {
    let a_str      = "/usr/src/linux";
    let a_string   = a_str.to_string();
    let a_path     = Path::new(a_str);
    let a_path_buf = a_path.to_path_buf();

    print_dirname(a_str);
    print_dirname(a_string);
    print_dirname(a_path);
    print_dirname(a_path_buf);

    print_dirname(&a_str);
    print_dirname(&a_string);
    print_dirname(&a_path);
    print_dirname(&a_path_buf);
}
```

Blanket implementation `AsRef<U> for &T` によって柔軟なインターフェースを定義できる。
上記の関数 `print_dirname` は `Path` と `&Path` のどちらも受け取れる。

## Borrow と AsRef の使い分け

`Borrow<T>` を実装する場合は `Self` と `T` でハッシュ値や比較結果が同じであることが求められる。`AsRef<T>` にはそのような制限は無い。
`HashMap` や `BTreeMap` のキーのようにハッシュ値や比較結果を気にするような場面では `Borrow` を使い、`fs::File::open` のように様々な型の引数を受け取れる柔軟なインターフェースを実現したい場合には `AsRef` を使う。