rust语言基础学习: 使用AsRef和AsMut trait实现不同引用之间的转换
2020-07-22
昨天我们学习了Default trait,通过实现Default trait可以为类型提供缺省值。如果类型中的包含的其他类型都实现了Default trait,就可以通过derive宏#[derive(Default)]
来为类型自动实现Default trait,当然也可以手工实现Default trait。
到目前为止我们已经学习了Rust标准库中的8个常用的trait:
- std::str::FromStr
- std::convert::From
- std::convert::TryFrom
- std::convert::Into
- std::convert::TryInto
- std::ops::Deref
- std::ops::DerefMut
- std::default::Default
其中From, TryFrom, Into, TryInto可以实现值类型到值类型之间的转换,今天将要学习的AsRef<T>和AsMut<T> trait可以实现引用类型到引用类型之间的转换。
AsRef<T>和AsMut<T> #
std::convert::AsRef和std::convert::AsMut这两个trait用于实现从引用到引用的转换。
它们的定义如下:
1pub trait AsRef<T>
2where
3 T: ?Sized,
4{
5 fn as_ref(&self) -> &T;
6}
7
8pub trait AsMut<T>
9where
10 T: ?Sized,
11{
12 fn as_mut(&mut self) -> &mut T;
13}
从定义可以看出以下规则:
- 如果类型
U
实现了AsRef<T>
,则as_ref可以实现&U
到&T
的转换 - 如果类型
U
实现了AsMut<T>
,则as_ref可以实现从&U
到&mut T
的转换
注意AsRef和AsMut的定义中都加了T: ?Sized
的trait bound。
std::marker::Sized是Rust标准库中的一个标记trait,用于标记在编译时就能确定占用内存大小的类型。当使用泛型参数时,Rust编译器会自动为泛型参数加上
T: Sized
trait bound。例如struct Foo<T>
等价于struct Foo<T: Sized>
。 如果在某些情况下,无法在编译时确定泛型参数T类型的大小,就需要使用?Sized
标记,即如果显式加了T: ?Sized
的trait bound,那么T就可以是任意大小的。
AsRef和AsMut的定义中泛型参数T都加了T: ?Sized
,所以它们都允许T使用大小可变的类型。
对于AsRef和AsMut,Rust还给了如下的泛型实现:
1// As lifts over &
2#[stable(feature = "rust1", since = "1.0.0")]
3impl<T: ?Sized, U: ?Sized> AsRef<U> for &T
4where
5 T: AsRef<U>,
6{
7 fn as_ref(&self) -> &U {
8 <T as AsRef<U>>::as_ref(*self)
9 }
10}
11
12// As lifts over &mut
13#[stable(feature = "rust1", since = "1.0.0")]
14impl<T: ?Sized, U: ?Sized> AsRef<U> for &mut T
15where
16 T: AsRef<U>,
17{
18 fn as_ref(&self) -> &U {
19 <T as AsRef<U>>::as_ref(*self)
20 }
21}
22
23
24// AsMut lifts over &mut
25#[stable(feature = "rust1", since = "1.0.0")]
26impl<T: ?Sized, U: ?Sized> AsMut<U> for &mut T
27where
28 T: AsMut<U>,
29{
30 fn as_mut(&mut self) -> &mut U {
31 (*self).as_mut()
32 }
33}
对泛型实现可以用下面的规则描述:
- 如果
T
实现了AsRef<U>
,那么&T
就实现了AsRef<U>
- 如果
T
实现了AsRef<U>
,那么&mut T
就实现了AsRef<U>
- 如果
T
实现了AsMut<U>
,那么&mut T
就实现了AsMut<U>
下面看两个例子。
例1:
1fn print_ref(v: impl AsRef<[i32]>) {
2 println!("{:?}", v.as_ref());
3 }
4
5 fn print(s: &[i32]) {
6 println!("{:?}", s);
7 }
8
9
10 fn main() {
11 let vec = vec![1, 2, 3];
12
13 // 因为Vec<i32>实现了AsRef<[i32]>,则&Vec<i32>也实现了AsRef<[i32]>
14 print_ref(&vec); // [1, 2, 3]
15
16 // Vec<T>实现了AsRef<[T]>, as_ref可以完成从&Vec<T>到&[T]的转换
17 print(&vec); // [1, 2, 3]
18
19
20 // 即Vec<i32>实现了AsRef<[i32]>
21 print_ref(vec); // [1, 2, 3]
22
23 }
例2:
1fn print_ref(v: impl AsRef<str>) {
2 println!("{}", v.as_ref());
3}
4
5
6 #[allow(dead_code)]
7 enum Msg {
8 Hello,
9 World,
10 }
11
12 impl AsRef<str> for Msg {
13 fn as_ref(&self) -> &str {
14 match self {
15 Msg::Hello => "hello",
16 Msg::World => "world",
17 }
18 }
19 }
20
21
22 fn main() {
23 // s1是&str类型, str类型实现了AsRef<str>,&str也实现了AsRef<str>
24 let s1 = "hello";
25 // s2是String类型, 实现了AsRef<str>, &String也实现了AsRef<str>
26 let s2 = String::from("word");
27 // msg是Msg类型, 实现了AsRef<str>, &Msg也实现了AsRef<str>
28 let msg = Msg::Hello;
29
30 // str类型实现了AsRef<str>
31 print_ref(s1); // hello
32 // &str类型实现了AsRef<str>
33 print_ref(&s1); // hello
34
35 // &String实现了AsRef<str>
36 print_ref(&s2);// world
37 // String实现了AsRef<str>
38 print_ref(s2); // world
39
40 // &Msg实现了AsRef<str>
41 print_ref(&msg); // hello
42 // Msg实现了AsRef<str>
43 print_ref(msg); // hello
44 }
使用场景 #
AsRef一般用于轻量级的且不会失败的不同类型的引用之间的转换,如果所需转换的开销很大,最好用&T类型实现From trait或者写一个自定义函数。 AsMut除了用于可变引用之间的转换外,其它和AsRef类似。
我们在设计函数的时候,可以将AsRef作为函数参数,可以让我们在调用函数时传参更灵活。例如标准库的std::fs::File::open
函数,它的签名是:
1pub fn open<P: AsRef<Path>>(path: P) -> Result<File>
open函数的参数path的类型要求是实现AsRef<Path>
的类型,去查看文档的话,会发现str
, String
, Path
等都实现了AsRef<Path>,所以在调用这个函数时我们可以直接传入&str, String和&Path类型。