rust - 理解borrow trait

简介

borrow trait 是处理借用(即其它语言中的引用)的 trait,变量的所有权不会转移.泛型定义如下:

pub trait Borrow<Borrowed: ?Sized> {
    /// Immutably borrows from an owned value.
    fn borrow(&self) -> &Borrowed;
}

其中包含一个 borrow(&self)的方法,将变量的类型 T 转换为 &Borrowed 类型.

默认实现

rust 提供了类型 T 的默认实现,如下:

impl<T: ?Sized> Borrow<T> for T {
    fn borrow(&self) -> &T {
        self
    }
}

impl<T: ?Sized> Borrow<T> for &T {
    fn borrow(&self) -> &T {
        &**self
    }
}

impl<T: ?Sized> Borrow<T> for &mut T {
    fn borrow(&self) -> &T {
        &**self
    }
}

这里创建一个自定义类型来说明 borrow 的默认实现,如下

    #[test]
    fn test_default_borrow() {
        struct MyType;

        let my_type = MyType;
        let _: &MyType = my_type.borrow();
        let _: &MyType = (&my_type).borrow();

        let my_type = &MyType;
        let _: &MyType = my_type.borrow();
        let _: &MyType = (*my_type).borrow();

        let my_type = &mut MyType;
        let _: &MyType = my_type.borrow();
        let _: &MyType = (*my_type).borrow();
    }

自定义 borrow trait

String 类型就实现了自己的 borrow trait,如下:

impl Borrow<str> for String {
    #[inline]
    fn borrow(&self) -> &str {
        &self[..]
    }
}

即将 String 类型转换为了 &str 类型.

可以自己对类型实现 Borrow trait,如下:

#[allow(dead_code)]
struct Person {
    name: String,
    age: u8,
}

impl Borrow<str> for Person {
    fn borrow(&self) -> &str {
        self.name.as_str()
    }
}

实现Borrow的一个要求是,借用值的HashEqOrd与拥有值的HashEqOrd相等。这里可以解释为,如果两个用户 Person 的借用值 name 相同,则认为是同一个人.

使用场景

1. 对函数参数进行类型扩展

    #[test]
    fn test_origin_check() {
        fn origin_check(s: &String) {
            assert_eq!("Hello", s);
        }

        let s = "Hello".to_string();
        origin_check(&s);
    }

例如,上面的函数origin_check接受 String 类型的参数,但是如果想复用这个函数,需要将其它类型的参数也传给origin_check函数该怎么实现呢?

先修改成第一个版本, 添加s.borrow(), 如下

    #[test]
    fn test_origin_check_1() {
        fn origin_check(s: &String) {
            let borrowed: &str = s.borrow();
            assert_eq!("Hello", borrowed);
        }

        let s = "Hello".to_string();
        origin_check(&s);
    }

这样只要确保类型 s,都实现了 borrow()方法,且返回值的类型都为 &str就可行了.

再修改成第二个版本, 这样就可以将任何实现了Borrow<str>类型的变量作为参数传递给函数check,如下:

    fn check<K>(s: K)
    where
        K: Borrow<str>,
    {
        let borrowed: &str = s.borrow();
        assert_eq!("Hello", borrowed);
    }

    #[test]
    fn test_borrow_as_param() {
        // 支持 String 类型,因为 String 实现了 Borrow trait,可以转换为 &str
        let s = "Hello".to_string();
        check(s);

        // 支持 &str 类型,因为 rust 实现了类型 T 的默认 borrow 实现, 转换为 &str 类型
        let s = "Hello";
        check(s);

        // 支持 Person 类型,因为 Person 实现了 Borrow trait, 可以转换为用户名属性的 &str 类型
        let s = Person { name: "Hello".to_string(), age: 18 };
        check(s);
    }