• 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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()) }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    impl Borrow<str> for String {
        #[inline]
        fn borrow(&self) -> &str {
            &self[..]
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    下面举一个简单的例子:

    #[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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用场景

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

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

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

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

    1. 班级类

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

    #[derive(Debug)]
    struct SchoolClass {
        students: HashMap<String, Rc<Student>>,
        name: String,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2. 学生类

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

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

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

    impl Borrow<StudentNo> for Student {
        fn borrow(&self) -> &StudentNo {
            &self.no
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3. 学号类

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

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

    使用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),
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    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());
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
  • 相关阅读:
    SQLServer 主体设置镜像伙伴失败:服务器网络地址 “TCP://server. domain.com:5022“ 无法访问或不存在
    C++功能模块5:在图像里截取矩形子图
    Svelte Ui Admin后台管理系统|svelte3+svelteUI中后台前端解决方案
    板块一 Servlet编程:第三节 HttpServletRequest对象全解与请求转发 来自【汤米尼克的JAVAEE全套教程专栏】
    【如何设置环境变量(环境变量在哪里)】
    美瞳小程序经营配送商城的作用是什么
    内网渗透系列之Pivoting跳板攻击与自动路由的配置以及使用ProxyChains进行代理扫描并获取内网服务器权限
    Jenkins 带参数执行shell脚本
    机器学习--Transformer 2
    MybatisX快速开发插件模版扩展
  • 原文地址:https://blog.csdn.net/liuyuan_jq/article/details/134096394