Типажи `Borrow` и `AsRef`

Типажи Borrow и AsRef очень похожи, но в то же время отличаются. Ниже приводится небольшая памятка об этих двух типажах.

Типаж Borrow

Типаж Borrow используется, когда вы пишете структуру данных и хотите использовать владение и заимствование типа как синонимы.

Например, HashMap имеет метод get, который использует Borrow:

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() еще раз. Использовать get() возможно, когда ключ реализует Borrow<Q>. Таким образом, мы можем сделать HashMap, который использует ключи String, но использовать &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 заимствовано: {}", 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 заимствовано: {}", a);
}

let mut i = 5;

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

Это выведет a заимствовано: 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();
}

Что в каком случае следует использовать?

Мы видим, что они вроде одинаковы: имеют дело с владением и заимствованием значения некоторого типа. Тем не менее, эти типажи немного отличаются.

Используйте Borrow, когда вы хотите абстрагироваться от различных видов заимствований, или когда вы строите структуру данных, которая использует владеющие и заимствованные значения как эквивалентные. Например, это может пригодиться в хэшировании и сравнении.

Используйте AsRef, когда вы пишете обобщённый код и хотите непосредственно преобразовать что-либо в ссылку.