Option
Option是rust非常好用的数据结构,用来解决 Null 空指针问题,是rust安全基石重要一环。其本质是一个 Enum 结构。本文对option进行模式匹配用于获取 option 包装的值的简单用法。
Option的声明:
pub enum Option<T> {
None,
Some(T),
}
例子:
let opt = Some("hello".to_string());
println!("{:?}", opt); // 输出: Some("hello")
模式匹配
Option 是一个Enum,通过模式匹配获取其变体
Some(T) 变体
let opt = Some("hello".to_string());
match opt {
Some(x) => println!("Some: x={}", x), // Some: x=hello
None => println!("None")
}
None 变体
let opt:Option<String> = None;
match opt {
Some(x) => println!("Some: x={}", x),
None => println!("None") // None
}
变量 opt 可以是 None 变体。上面的 opt 需要指定类型,不然这段代码编译器无法推断 x 的类型。
unwarp方法
Option 有很多有用的方法。unwarp 方法用于获取 Some(x) 中 的 x 值。如果 Option是 None 变体,则该方法会 pannic。
let opt = Some("hello".to_string());
let s = opt.unwrap();
println!("{}", s); // s
opt 通过 unwarp 方法获取变体 Some(x) 中的 x。若 opt 是 None 变体,unwarp 方法会pannic
uwranp的源码:
pub const fn unwrap(self) -> T {
match self {
Some(val) => val,
None => panic!("called `Option::unwrap()` on a `None` value"),
}
}
从 unwarp的源码可以看出,它本质也是模式匹配的一种简写。
所有权
Option的模式匹配和 unwarp 方法涉及所有权move语义。(x指没有实现 Copy trait的类型)
就像赋值,参数传递一样。直接模式匹配会涉及所有权move
let opt = Some("hello".to_string());
match opt {
Some(x) => println!("Some: x={}", x), // 模式匹配,所有权move
None => println!("None")
}
println!("{:?}", opt); // 所有权已move
上面的代码会编译错误。错误信息如下:
error[E0382]: borrow of partially moved value: `opt`
--> src/main.rs:54:22
|
50 | Some(x) => println!("Some: x={}", x),
| - value partially moved here
...
54 | println!("{:?}", opt);
| ^^^ value borrowed here after partial move
|
= note: partial move occurs because value has type `String`, which does not implement the `Copy` trait
help: borrow this field in the pattern to avoid moving `opt.0`
String 类型是没有实现 Copy trait,它存储在堆上。创建 opt的时候,它的所有权move到 opt,通过模式匹配,所有权move到 x。x在match花括号的作用域后drop了。但是 opt 没有所有权,再次打印会报错。
unwrap 实现基于模式匹配,因此 unwarp 方法也会move 所有权。
let opt = Some("hello".to_string());
let s = opt.unwrap();
println!("{:?}", opt);
编译错误信息:
48 | let opt = Some("hello".to_string());
| --- move occurs because `opt` has type `Option<String>`, which does not implement the `Copy` trait
49 | let s = opt.unwrap();
| -------- `opt` moved due to this method call
50 |
51 | println!("{:?}", opt);
| ^^^ value borrowed here after move
|
引用
move语义会转移所有权,使用借用 borrow语义就能保持所有权。
写法一
let opt = Some("hello".to_string());
match &opt {
Some(x) => println!("{}", x), // 对 &opt 进行模式匹配,此时的 x 是 &String 类型
None => println!("None"),
}
println!("{:?}", opt); // 输出 Some("hello")
写法二
let opt = Some("hello".to_string());
match opt {
Some(ref x) => println!("{}", x),
None => println!("None"),
}
println!("{:?}", opt);
opt 依然是正常的形式,不是其引用,在 Some 中使用 ref 修饰 x,此时 x 是 &String。 即将 opt所有的 String 的引用借给 x
let opt = Some("hello".to_string());
let s = &opt.unwrap();
println!("{:?}", opt);
很不幸,这样还是会编译错误。&opt.unwrap(); 实际是 &(opt.unwrap)。所有权move之后再取引用。那么很容易想到下面的做法
let opt = Some("hello".to_string());
let s = (&opt).unwrap();
println!("{:?}", opt);
这样做依然会编译失败。即使是 &opt,unwarp的签名是 self ,也就是 传递给 unwrap 的是 opt ,而不是 &opt。所有权还是转移了。想要实现 所有权的借用,可以仿照 上面 match表达式的写法。
let opt = Some("hello".to_string());
let s = unwrap(&opt);
println!("{:?}", s); // hello
println!("{:?}", opt); // Some("hello")
fn unwrap(opt: &Option<String>) -> &String {
match opt {
Some(x) => x,
None => panic!("called `Option::unwrap()` on a `None` value"),
}
}
as_ref
既然我们能想到封装一个 unwrap函数,标准库早也想到了。option的as_ref 源码
pub const fn as_ref(&self) -> Option<&T> { // 将 opt 的引用&opt 作为参数
match *self { // 对 opt 进行模式匹配
Some(ref x) => Some(x), // 通过 ref 获取 x 引用,再封装成 Option 返回
None => None,
}
}
上面的 Some(ref x) => Some(x)
就是我们上面展示的引用的写法二。过 as_ref 调用得到的是 Option<&String>。再调用 unwrap方法,就是对其进行模式匹配,就是写法二的方式:
let opt = Some("hello".to_string());
let opt1 = opt.as_ref(); // as_ref 获取 opt x的引用
match opt1 { // 模式匹配
Some(x) => println!("{}", x), // x 是 &String
None => println!("None"),
}
println!("{:?}", opt);
println!("{:?}", opt1);
上面的过程可以连起来写成一行
let opt = Some("hello".to_string());
let s = opt.as_ref().unwrap();
println!("{:?}", s);
println!("{:?}", opt);
总结
Option<T> 是 rust 类型安全重要思想的体现之一。它本质是一个 Enum 类型,有两个变体,Some(x) 和 None。当表示没有值的时候,可以使用 None。其语义类似其他语言如 Python的None,Golang 的 nil, java 的null。但是又跟其他语言有本质的不同。rust 的 None 是 Option 类型。而不是 其他任何类型。而其他语言的 None nil 可以是任何其他类型或引用类型。Null 可以是 字串,也可以是 指针。这就埋下了很多安全隐患。
Rust 的中表示可有可无的时候使用 Option。有值的时候需要使用 Some 变体。解出 Some(x) 中的 x 值方法是模式匹配。同时标注库也提供了便捷方法如 unwrap。
无论是使用 模式匹配 还是一些方法,对于所有权的move还是borrow严格遵循rust的所有权系统。通过上面介绍的几个例子用以说明。