rust - 理解 ToOwned trait

简介

ToOwned trait支持任意类型的转换,而Clone trait只支持&T 到 T 的转换.以下先介绍一下基本的定义,最后通过一个简单的例子详细理解一下Borrow traitToOwned trait的互相转换的过程.

定义

可以将任意类型T转换为U类型,其中U类型实现了Borrow trait,

  • T: 指的是Self
  • U: 指的是Borrow

可以简单理解为ToOwned traitBorrow trait反向操作.

pub trait ToOwned {
    type Owned: Borrow<Self>;

    fn to_owned(&self) -> Self::Owned;
}

str类型

str已经默认支持了ToOwned trait,如下

impl ToOwned for str {
    type Owned = String;

    fn to_owned(&self) -> String {
        unsafe { String::from_utf8_unchecked(self.as_bytes().to_owned()) }
    }

可以将str类型转换为String类型,String需要实现 Borrow trait,如下

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

下面举一个简单的例子:

#[test]
fn test_string_borrow() {
    let s = "hello";
    let t: String = s.to_owned();
    assert_eq!(t, s.to_string());

    let s = "world";
    let t: String = s.to_owned();
    assert_eq!(t, s.to_string());
}

使用场景

一个班级有多名学生,每个学生有一个唯一的学号,根据学号可以唯一确认这名学生.可以使用 trait 来描述学生和学号之间的关系.

  • 学生类使用Borrow trait 可以实现获取学生的唯一学号.
  • 学号类使用ToOwned trait可以实现根据学号获取学生实例.

通过使用Borrow traitToOwned trait,实现了学生对象和学号对象之间的互转.

下面来看下如何实现这个例子

1. 班级类

使用 HashMap 记录了所有的学生信息.其中 key 表示学号,后续可以通过学号获取学生对象.

#[derive(Debug)]
struct SchoolClass {
    students: HashMap<String, Rc<Student>>,
    name: String,
}

2. 学生类

包含了学号类、学生的基本属性、还有所在的班级.如果从数据库约束的角度考虑,可以理解Student中包含了名为班级的外键class.

/// 学生类
#[derive(Debug)]
struct Student {
    no: StudentNo,                   // 学生编号对象
    name: String,                    // 学生名称
    age: u8,                         // 学生年纪
    class: Rc<RefCell<SchoolClass>>, // 学生所在的班级对象
}

使用Borrow traitStudent类型转换为&StudentNo学号类,如下

impl Borrow<StudentNo> for Student {
    fn borrow(&self) -> &StudentNo {
        &self.no
    }
}

3. 学号类

包含一个唯一的编号值,还需要说明学号属于哪个班级,用于后续从班级中根据学号查询学生.

/// 学生编号类
#[derive(Debug)]
struct StudentNo {
    no: String,                      // 学生编号值
    class: Rc<RefCell<SchoolClass>>, // 学生所在的班级
}

使用ToOwned traitStudentNo类型转换为Student类型,如下

/// 根据学生编号值获得对应的学生
impl ToOwned for StudentNo {
    type Owned = Student;

    fn to_owned(&self) -> Self::Owned {
        // 在班级中根据学生编号值查询学生
        let class = self.class.try_borrow().unwrap();
        let student = class.fetch_student(&self.no.to_string()).unwrap();

        // 生成新的学生对象
        Student {
            no: StudentNo {
                no: self.no.clone(),
                class: Rc::clone(&self.class),
            },
            name: student.name.clone(),
            age: student.age,
            class: Rc::clone(&self.class),
        }
    }
}

4.完整的例子

use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::rc::Rc;

#[test]
fn test_to_owned() {
    /// 学生编号类
    #[derive(Debug)]
    struct StudentNo {
        no: String,                      // 学生编号值
        class: Rc<RefCell<SchoolClass>>, // 学生所在的班级
    }

    /// 根据学生编号值获得对应的学生
    impl ToOwned for StudentNo {
        type Owned = Student;

        fn to_owned(&self) -> Self::Owned {
            // 在班级中根据学生编号值查询学生
            let class = self.class.try_borrow().unwrap();
            let student = class.fetch_student(&self.no.to_string()).unwrap();

            // 生成新的学生对象
            Student {
                no: StudentNo {
                    no: self.no.clone(),
                    class: Rc::clone(&self.class),
                },
                name: student.name.clone(),
                age: student.age,
                class: Rc::clone(&self.class),
            }
        }
    }

    /// 学生类
    #[derive(Debug)]
    struct Student {
        no: StudentNo,                   // 学生编号对象
        name: String,                    // 学生名称
        age: u8,                         // 学生年纪
        class: Rc<RefCell<SchoolClass>>, // 学生所在的班级对象
    }

    impl fmt::Display for Student {
        fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
            fmt.pad(self.name.as_str())
        }
    }

    impl Borrow<StudentNo> for Student {
        fn borrow(&self) -> &StudentNo {
            &self.no
        }
    }

    #[derive(Debug)]
    struct SchoolClass {
        students: HashMap<String, Rc<Student>>,
        name: String,
    }

    /// 班级类
    impl SchoolClass {
        fn new(name: String) -> Rc<RefCell<SchoolClass>> {
            Rc::new(RefCell::new(SchoolClass {
                name: name,
                students: HashMap::new(),
            }))
        }

        /// 添加学生到班级
        fn add_student(&mut self, no: String, student: Rc<Student>) {
            self.students.insert(no, student);
        }

        /// 根据学生名称获得学生对象
        fn fetch_student(&self, no: &String) -> Option<&Rc<Student>> {
            self.students.get(no)
        }
    }

    impl fmt::Display for SchoolClass {
        fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
            fmt.pad(self.name.as_str())
        }
    }

    // 创建一个班级对象
    let class_name = "First class";
    let class = SchoolClass::new(class_name.to_string());

    // 创建一个学生对象
    let student_name = "bob";
    let no = "A001";
    let student = Student {
        no: StudentNo { no: no.to_string(), class: Rc::clone(&class) },
        name: student_name.to_string(),
        age: 18,
        class: Rc::clone(&class),
    };

    // 添加学生到班级中
    {
        class.borrow_mut().add_student(no.to_string(), Rc::new(student));
    }

    // 根据学生名称查询学生
    // Note: 在使用了 std::borrow::Borrow的情况下,注意不能用 class.borrow(), 因为与 RefCell 的 borrow()冲突,所以使用try_borrow()替代
    let class_a = class.try_borrow().unwrap();
    let student_bob = class_a.fetch_student(&no.to_string()).unwrap();
    assert_eq!(student_bob.name, student_name.to_string());

    // 使用 Borrow trait 获得学生的学号
    let student = student_bob.as_ref();
    let student_no: &StudentNo = student.borrow(); // 必须显示标注类型,否则会与默认的 Borrow Trait 冲突
    assert_eq!(student_no.no, no.to_string());

    // 使用 ToOwned trait 根据学号获得学生实例
    let student_bob = student_no.to_owned();
    assert_eq!(student_bob.name, student_name.to_string());
}

你可能感兴趣的:(rust,rust)