トレイト Borrow

Since 1.0.0 (const: unstable) · Source
pub trait Borrow<Borrowed>
where
    Borrowed: ?Sized,
{
    // Required method
    fn borrow(&self) -> &Borrowed;
}
説明を展開

データを借用するためのトレイト。

Rust では、異なる用途には異なる型の表現を与えるのが一般的である。例えば、特定の用途に適するよう値の保存場所や管理方法を Box<T>Rc<T> のような型によって明確に選択できる。任意の型に使用できるこうした汎用的なラッパーに加え、いくつかの型は潜在的にコストのある別の切り口を提供している。そうした型の例に String があり、それは通常の str に文字列を拡張する能力を付加している。これには単純な不変文字列には不要な追加情報を保持する必要がある。

これらの型は背後にあるデータへのアクセスをそのデータの型への参照を通して提供する。これはその型「として借用される」と表現される。例えば、Box<T>T として借用され Stringstr として借用される。

型は Borrow<T> を実装し、そのトレイトの borrow メソッド内で T への参照を提供する事により、特定の型 T として借用できる事を表現する。一つの型を幾つかの異なる型として借用するのも自由である。もしその型として可変借用したい、つまり背後のデータの変更を許可したいなら、追加で BorrowMut<T> も実装できる。

さらに、追加のトレイトの実装を提供している場合、それはそれらが背後にある型の代理としての動作の結果、背後にある型と同様の振舞いをすべきかどうかを考慮する必要がある。ジェネリックコードはこれらの追加のトレイト実装で同様の挙動が採用される際、通常は Borrow<T> を使用する。こうしたトレイトは追加のトレイト境界としてよく現れる。

特に、Eq, Ord そして Hash は借用と所有の値が等価でなければならない: x.borrow() == y.borrow()x == y と同じ結果を与えるべきである。

もしジェネリックコードが単純に関係する型 T への参照を提供できる全ての型において機能する必要があるのなら、より多くの型を安全に実装できるため AsRef<T> を使用したほうが良い場合が多い。

データのコレクションとして、HashMap<K, V> はキーと値の両方を所有する。もしキーの実際のデータがある種の管理用の型にラップされていたとして、それでもまだキーデータへの参照を使った値の検索が可能であるべきである。例として、もしキーが文字列なら、ハッシュマップには String として保存される事が多いだろうが、それは &str を使って検索できる。そのため、insertString での操作が必要になるが get&str も使える必要がある。

少し簡略化するが、HashMap<K, V> の関連箇所は以下のように見える:

use std::borrow::Borrow;
use std::hash::Hash;

pub struct HashMap<K, V> {
    // fields omitted
}

impl<K, V> HashMap<K, V> {
    pub fn insert(&self, key: K, value: V) -> Option<V>
    where K: Hash + Eq
    {
        // ...
    }

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

ハッシュマップ全体はキーの型 K についてジェネリックである。これらのキーはハッシュマップに保存されるため、この型はキーのデータを所有する必要がある。キーと値のペアの挿入時、マップにはそうした K が与えられ、正しいハッシュバケットを見つけ、そのキーがすでに存在するかを K に基づいて検査する。そのため K: Hash + Eq が要求される。

とはいえ、マップ内で値を検索する際、検索のためのキーとして K への参照を提供しなければならないとすると、そのような所有された値の作成が常に要求される。文字列キーでは、これは str のみ利用可能な箇所で、検索のためだけに String 値の作成が必要となる事を意味する。

代わりに、get メソッドはキーデータの背後の型、上記メソッドシグネチャでは Q と呼称、についてジェネリックである。それは KQ として借用される事を K: Borrow<Q> を要求する事により宣言する。さらに Q: Hash + Eq を要求する事により、KQHashEq トレイトの実装で同じ結果を生成する要件を示唆している。

get の実装は具体的な Hash の実装が一致する事をあてにしていて、それはキーのハッシュバケットを QHash::hash を呼び出す事により決定するのだが、そのキーは K の値から計算されたハッシュ値に基づいて挿入されている。

結果として、もし Q の値をラップした KQ と異なるハッシュを生成するとハッシュマップは壊れてしまう。例として、文字列をラップするが比較では ASCII 文字の大文字小文字を無視する型があるとする:

pub struct CaseInsensitiveString(String);

impl PartialEq for CaseInsensitiveString {
    fn eq(&self, other: &Self) -> bool {
        self.0.eq_ignore_ascii_case(&other.0)
    }
}

impl Eq for CaseInsensitiveString { }

二つの同じ値は同じハッシュ値を生成する必要があるため、Hash の実装も ASCII の大文字小文字を無視する必要がある:

impl Hash for CaseInsensitiveString {
    fn hash<H: Hasher>(&self, state: &mut H) {
        for c in self.0.as_bytes() {
            c.to_ascii_lowercase().hash(state)
        }
    }
}

CaseInsensitiveStringBorrow<str> を実装できるだろうか? 文字列スライスへの参照を提供する事はそれに含まれる所有された文字列を通して確かにできる。しかしその Hash の実装が異なるため、それは str と異なる挙動をする、ゆえに実際には Borrow<str> を実装してはならない。もし、基盤となる str へのアクセスを他者に許可したいのなら、追加の要件を伴わない AsRef<str> を通してそれを行う事はできる。

要求されるメソッド

1.0.0 · Source fn borrow(&self) -> &Borrowed

所有された値から不変借用をする。

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);