37、Nullability(可空性)和元组
Nullability(可空性)
我们对assigned
方法的实现相当直率,对to-do
和done
状态的ticket直接panic还是不太合适。
使用Rust中的Option
类型可以做得更好。
Option
Option
是一种可以表示为null
值的类型。
它是Rust标准库中定义的一个枚举:
enum Option<T> {
Some(T),
None,
}
Option
类型表达了这样一个概念:某个值可能存在(Some(T)
)或可能不存在(None
)。
它还强制我们必须显式处理这两种情况,如果在处理可空值时忘记处理None
这个情况的话,那么编译器就会拒绝编译。
这是对其它语言中“隐式”可空值的重大改进,在其它语言中,我们可能会忘记检查null
从而引发运行时错误。
Option
的定义
Option
的定义使用了我们之前从未见过的Rust结构:tuple-like variants(类元组变体)。
类元组变体
Option
有两个变体:Some(T)
和None
。
Some
是一种类元组变体:它是一种保存未命名字段的变体。
当只有一个字段需要存储,尤其是我们使用Option
这种“封装”类型时,通常会使用类元组变体。
类元组结构体
上边的那个,并不是局限于枚举类型,我们也可以将其应用到结构体上,是类元组结构体:
struct Point(i32, i32);
我们可以使用Point
实例的位置索引访问点实例的两个字段:
let point = Point(3, 4);
let x = point.0;
let y = point.1;
元组
当我们还没有看到元组时,就说某个东西是类元组的,这很奇怪。
元组是原始Rust类型的另一个例子。它将固定数量的值和类型(可能有所不同)组合在一起。
// Two values, same type
let first: (i32, i32) = (3, 4);
// Three values, different types
let second: (i32, u32, u8) = (-42, 3, 8);
语法很简单,使用.操作符
和数字位置索引即可访问到。
assert_eq!(second.0, -42);
assert_eq!(second.1, 3);
assert_eq!(second.2, 8);
当懒得去定义专门的数据结构时,元组是一种将值组织起来的好方法。
练习
终于遇到了一个正常一点的题目了
题目:
// TODO: Implement `Ticket::assigned_to` using `Option` as the return type.
#[derive(Debug, PartialEq)]
pub struct Ticket {
title: String,
description: String,
status: Status,
}
#[derive(Debug, PartialEq)]
pub enum Status {
ToDo,
InProgress { assigned_to: String },
Done,
}
impl Ticket {
pub fn new(title: String, description: String, status: Status) -> Ticket {
if title.is_empty() {
panic!("Title cannot be empty");
}
if title.len() > 50 {
panic!("Title cannot be longer than 50 bytes");
}
if description.is_empty() {
panic!("Description cannot be empty");
}
if description.len() > 500 {
panic!("Description cannot be longer than 500 bytes");
}
Ticket {
title,
description,
status,
}
}
pub fn assigned_to(&self) -> Option<&String> {
/* TODO */
}
}
答案:
// TODO: Implement `Ticket::assigned_to` using `Option` as the return type.
#[derive(Debug, PartialEq)]
pub struct Ticket {
title: String,
description: String,
status: Status,
}
#[derive(Debug, PartialEq)]
pub enum Status {
ToDo,
InProgress { assigned_to: String },
Done,
}
impl Ticket {
pub fn new(title: String, description: String, status: Status) -> Ticket {
if title.is_empty() {
panic!("Title cannot be empty");
}
if title.len() > 50 {
panic!("Title cannot be longer than 50 bytes");
}
if description.is_empty() {
panic!("Description cannot be empty");
}
if description.len() > 500 {
panic!("Description cannot be longer than 500 bytes");
}
Ticket {
title,
description,
status,
}
}
pub fn assigned_to(&self) -> Option<&String> {
let Status::InProgress { assigned_to } = &self.status else{ return None};
Some(assigned_to)
}
}
如果是Status::InProgress
变体的话,那么就返回Some(assigned_to)
,就是Option
枚举。
如果不是的话,就返回None
,也是Option
枚举。
最最正规的写法应该这样
pub fn assigned_to(&self) -> Option<&String> {
let Status::InProgress { assigned_to } = &self.status else{ return Option::None};
Option::Some(assigned_to)
}
不过Rust为我们简化了,所以实际上也不需要特意加上Option::
。
这一节的学习就先到这了。