ToOwned trait
支持任意类型的转换,而Clone trait
只支持&T 到 T 的转换.以下先介绍一下基本的定义,最后通过一个简单的例子详细理解一下Borrow trait
和ToOwned trait
的互相转换的过程.
可以将任意类型T
转换为U
类型,其中U
类型实现了Borrow
,
T
: 指的是Self
U
: 指的是Borrow
可以简单理解为ToOwned trait
是Borrow 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
,如下
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 trait
和 ToOwned trait
,实现了学生对象和学号对象之间的互转.
下面来看下如何实现这个例子
使用 HashMap
记录了所有的学生信息.其中 key 表示学号,后续可以通过学号获取学生对象.
#[derive(Debug)]
struct SchoolClass {
students: HashMap<String, Rc<Student>>,
name: String,
}
包含了学号类、学生的基本属性、还有所在的班级.如果从数据库约束的角度考虑,可以理解Student
中包含了名为班级的外键class
.
/// 学生类
#[derive(Debug)]
struct Student {
no: StudentNo, // 学生编号对象
name: String, // 学生名称
age: u8, // 学生年纪
class: Rc<RefCell<SchoolClass>>, // 学生所在的班级对象
}
使用Borrow trait
将 Student
类型转换为&StudentNo
学号类,如下
impl Borrow<StudentNo> for Student {
fn borrow(&self) -> &StudentNo {
&self.no
}
}
包含一个唯一的编号值,还需要说明学号属于哪个班级,用于后续从班级中根据学号查询学生.
/// 学生编号类
#[derive(Debug)]
struct StudentNo {
no: String, // 学生编号值
class: Rc<RefCell<SchoolClass>>, // 学生所在的班级
}
使用ToOwned trait
将StudentNo
类型转换为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),
}
}
}
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());
}