37、Nullability(可空性)和元组

Nullability(可空性)

 

我们对assigned方法的实现相当直率,对to-dodone状态的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::

 

这一节的学习就先到这了。

阅读剩余
THE END