谈谈rust的Option枚举和空指针

谈谈rust的Option枚举和空指针

由于之前工作一直都在用c/c++,最近在学rust的时候发现rust里面没有空值(null),而是采用了一个Option枚举,感觉非常有意思。

空值(Null )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。

这句话有点绕。实际上用过c++的同学们应该知道c++内的NULL的值是0,例如int *ptr = NULL,这里ptr的值就是0,这里的0不是实际意义上的0,而是用来表示一个空值,ptr是一个空指针。但是我们知道计算机里没有空这种东西,所以c++取了0来表示逻辑意义上的空。之所以取0,是因为在大多数操作系统里,程序是不允许访问地址为0的内存的,因为该内存是操作西用保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。当然现在c++有nullptr了,关于nullptr和NULL,0的关系我这里就不讲了…不是我的重点。

之前提到,rust里面是没有空值的,那么空值有什么问题呢?它最大的问题就在于如果像使用非空值一样使用一个空值,会导致错误。然而空和非空的属性无处不在,所以非常容易出现这种错误。但是空值的概念仍然是有意的:

空值是一个因为某种原因目前无效或缺失的值。

因此虽然rust没有空值,但是仍然存在一个可以表达存在或不存在的概念的枚举,Option<T>

enum Option<T> {
    Some(T),
    None,
}

我们注意到,这里使用了范型。也就是说Option枚举的Some成员可以是任意的数据类型。需要注意的是如果使用None而不是Some时,需要告诉rustOption<T>的类型。因为编译器只通过None是无法推断出Some的类型的。即

let x: Option<i32> = None;

这里的None就是和空值表达类似的东西。那为什么要使用None而不是空值呢?这里有一个很重要的一点,Option<T>T不是同一个类型。不要小看着一个简单的区别,这意味着Option<T>T是不能直接进行运算的,即Option<i32>i32是不能直接相加的。实际上,更进一步的,在对Option<T>进行T的运算时,必须先将Option<T>转化成T类型。如此一来就可以帮助我们避免以为值非空而实际为空的情况。例如下面这段代码:

fn main() {
    let a: i32 = 1;
    let b: Option<i32> = Some(5);
    let c = a + b;
    println!("{}", c);
}

编译器会报如下错误

error[E0277]: cannot add `std::option::Option<i32>` to `i32`
 --> src/main.rs:4:15
  |
4 |     let c = a + b;
  |               ^ no implementation for `i32 + std::option::Option<i32>`
  |
  = help: the trait `std::ops::Add<std::option::Option<i32>>` is not implementedfor `i32`

error: aborting due to previous error

我们必须先将b从Option<i32>类型转化为i32才能进行i32类型的运算。如下

fn main() {
    let a: i32 = 1;
    let b: Option<i32> = Some(5);
    let c = a + b.unwrap();
    println!("{}", c);
}

另外,每当我们引入一个可能为空的值时,我们必须先把它放到Option<T>里。当我们使用这个值时,我们必须先处理值为空的情况。也就是说,只要一个值不是Option<T>类型的,我们就可以认定它的值不为空。

这个设计相当有意思,我又查了一些资料发现scala里就存在这个设计,rust应该就是借鉴的scala的做法,无怪乎有人说rust参考了c/c++和haskell/scala两类语言。如果有机会应该多见识一下其他的语言,开阔下思路(虽然工作估计还是c/c++,(-_-||)

    原文作者:readlnh
    原文地址: https://blog.csdn.net/readlnh/article/details/86613747
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞