不存在的 null
null
虽然存在多种主流语言当中,但是从一些方面上来说,它是存在问题的,只是已经成为了一种传统形式,无法改变了。
Tony Hoare,null
的发明者,在他 2009 年的演讲“Null References: The Billion Dollar Mistake”中曾经说到:
我称之为我十亿美元的错误。当时,我在为一个面向对象语言设计第一个综合性的面向引用的类型系统。我的目标是通过编译器的自动检查来保证所有引用的使用都应该是绝对安全的。不过我未能抵抗住引入一个空引用的诱惑,仅仅是因为它是这么的容易实现。这引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数十亿美元的苦痛和伤害。
对此,Rust 在设计之初便放弃了 null
值,而是使用 Option<T>
枚举来模拟这一点。
enum Option<T> {
Some(T),
None,
}
对比
以链表结构体为例,在 Java 当中的声明:
class ListNode {
int val;
ListNode next;
}
对于 next
属性来说,它可能是 null
, 但没有一个地方以显式形式表示出来,只是开发者心里面明白,不过并不是所有时候。
再看 Rust 中的表现形式:
struct ListNode {
val: i32,
next: ListNode,
}
这种写法,是保证 next
一定有值。若 next
并不一定值,则需要使用 Option<T>
表现出来:
为了 Rust 示例合法化,同时加上 Box 指针
struct ListNode {
val: i32,
next: Option<Box<ListNode>>,
}
而这层嵌套的存在,也让人无法直接进行对比与操作,因为 Option<Box<ListNode>>
与 Box<ListNode>
是两种不同的数据类型。
就连
10i32 + 20i64
都是不可行的
使用
通过 Some()
来创建 Option<T>
对象。
let a = Some(10);
let b = Some(10);
// true
println!("{}", a == b);
再一次强调,非同一种类型的数据无法进行比较。
let a: Option<i32> = Some(10);
let b: i32 = 10;
// panic
println!("{}", a == b);
获取值
若想获取 Option<T>
当中的 T
,有几种方式:
match
let a = Some(10);
let b = 10;
match a {
Some(num) => println!("{}", num == b),
None => println!("{}", false)
}
if let
let a = Some(10);
let b = 10;
if let Some(num) = a {
println!("{}", num == b)
} else {
println!("{}", false)
}
unwrap()
并不安全,但快捷的操作,如果目标值为 None
,会引起 panic
let mut a = Some(10);
let b = 10;
// true
println!("{}", a.unwrap() == b);
// 改动
a = None;
// panic
println!("{}", a.unwrap() == b);
unwrap_or()
在 unwrap()
基础上升级,是一种较为安全的方法(与 unwrap()
相比),若是出现提取失败(目标为 None
),则使用参数代替输出
let a: Option<i32> = None;
let b: i32 = 10;
// true
println!("{}", a.unwrap_or(10) == b);
其他
与 unwrap()
同门,简易的流程更的人心。
unwrap_or_else()
unwrap_or_default()
小结
Rust 独特的设计哲学确实让人眼前一亮,整体的安全性表现极高,同时又提供了简便方式进行常规操作,该方面的整体把控确实均衡到位了。