BorrowとAsRef

Borrow トレイトと AsRef トレイトはとてもよく似ていますが違うものです。ここでは2つのトレイトの意味を簡単に説明します。

Borrow

Borrow トレイトはデータ構造を書いていて、所有型と借用型を同等に扱いたいときに使います。

例えば、 HashMap には Borrow を使った get メソッド があります。

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

このシグネチャは少し複雑です。K パラメータに注目してください。これは以下のように HashMap 自身のパラメータになっています。

fn main() { struct HashMap<K, V, S = RandomState> { }
struct HashMap<K, V, S = RandomState> {

K パラメータは HashMap の「キー」を表す型です。ここで再び get() のシグネチャを見ると、キーが Borrow<Q> を実装しているときに get() を使えることが分かります。そのため、以下のように String をキーとした HashMap を検索するときに &str を使うことができます。

fn main() { use std::collections::HashMap; let mut map = HashMap::new(); map.insert("Foo".to_string(), 42); assert_eq!(map.get("Foo"), Some(&42)); }
use std::collections::HashMap;

let mut map = HashMap::new();
map.insert("Foo".to_string(), 42);

assert_eq!(map.get("Foo"), Some(&42));

これは標準ライブラリが impl Borrow<str> for String を提供しているためです。

所有型か借用型のどちらかを取りたい場合、たいていは &T で十分ですが、借用された値が複数種類ある場合 Borrow が役に立ちます。特に参照とスライスは &T&mut T のいずれも取りうるため、そのどちらも受け入れたい場合は Borrow がよいでしょう。

fn main() { use std::borrow::Borrow; use std::fmt::Display; fn foo<T: Borrow<i32> + Display>(a: T) { println!("a is borrowed: {}", a); } let mut i = 5; foo(&i); foo(&mut i); }
use std::borrow::Borrow;
use std::fmt::Display;

fn foo<T: Borrow<i32> + Display>(a: T) {
    println!("a is borrowed: {}", a);
}

let mut i = 5;

foo(&i);
foo(&mut i);

上のコードは a is borrowed: 5 を二度出力します。

AsRef

AsRef トレイトは変換用のトレイトです。ジェネリックなコードにおいて、値を参照に変換したい場合に使います。

fn main() { let s = "Hello".to_string(); fn foo<T: AsRef<str>>(s: T) { let slice = s.as_ref(); } }
let s = "Hello".to_string();

fn foo<T: AsRef<str>>(s: T) {
    let slice = s.as_ref();
}

どちらを使うべきか

ここまでで見てきた通り、2つのトレイトは、どちらもある型の所有型バージョンと借用型バージョンの両方を扱う、という意味で同じような種類のものですが、少し違います。

いくつかの異なる種類の借用を抽象化したい場合や、ハッシュ化や比較のために所有型と借用型を同等に扱いたいデータ構造を作る場合は Borrow を使ってください。

ジェネリックなコードで値を参照に直接変換したい場合は AsRef を使ってください。