什么是Some和None?

90

我使用了 Vec::get 并且遇到了一些我不理解的输出。这是代码:

fn main() {
    let command = [('G', 'H'), ('H', '5')];

    for i in 0..3 {
        print!(" {} ", i);
        println!("{:?}", command.get(i));
    }
}

输出结果为

 0 Some(('G', 'H'))
 1 Some(('H', '5'))
 2 None

我之前尝试过Haskell,但只看了10分钟教程就逃回了C++。不过我记得在Haskell中有关于SomeNone的介绍。我很惊讶地发现在Rust中也有这个。能否有人解释一下为什么.get()返回SomeNone

5个回答

80

get的签名(针对切片,而不是Vec,因为您正在使用数组/切片)是:

fn get(&self, index: usize) -> Option<&T>

换句话说,它返回一个Option,这是一个枚举类型的定义如下:

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

NoneSome 是枚举的变种,也就是说,类型为 Option<T> 的值可以是 None,也可以是包含类型为 T 的值的 Some。你也可以使用这些变种来创建 Option 枚举:

let foo = Some(42);
let bar = None;

这与Haskell中核心的data Maybe a = Nothing | Just a类型相同;两者都表示可选值,它要么存在(Some/Just),要么不存在(None/Nothing)。
这些类型通常用于表示失败,当某件事情失败时只有一种可能性,例如,.get使用Option来提供类型安全的边界检查数组访问:当索引超出范围时返回None(即没有数据),否则返回包含所请求指针的Some
另请参见:

12

SomeNone视为规范的“安全”方式,以解决Rust语言不支持“安全”使用NULL指针的问题。由于您的Vec长度为3,而您只指定了两个对,第三个对就是有效的NULL;它不会返回NULL,而是返回None

Rust通过强制我们在编译时使用Some/None来提供安全保证,以始终处理返回None的可能性。


4
Rust语言不支持(不安全的)NULL指针,但这并非完全正确——实际上可以通过使用ptr::null()函数创建一个空指针。不过你回答的精神是正确的。 - Shepmaster
谢谢;我可以看出它们在与C进行接口时是必要的。 - John Hinrichsen
1
不仅如此,而且在需要不安全操作时也是如此。数据结构通常会在内部使用原始指针。 - Steve Klabnik
另一个好的观点。我已经编辑了我的答案,使其更准确。 - John Hinrichsen
回到 Haskell 的世界,那么 Rust 中的 "Some | None" 是否像 Haskell 中一样是一个单子? - John Miller

8

command 不是一个向量(类型为 Vec<T>),而是一个固定大小的数组(在你的情况下是类型为 [(char, char); 2]),并且数组自动借用到片段(数组的视图)中,因此您可以使用所有在片段上定义的方法,包括 get:

返回给定索引处的片段元素,如果索引越界则返回 None

其行为非常明显:当给定索引有效时,它会返回该索引下的元素,并返回 Some ,否则返回 None

还有另一种访问片段中元素的方法-索引运算符,这应该很熟悉:

let nums = [1, 2, 3];
let x = nums[1];

它直接返回切片的元素,但如果索引超出范围,则会导致当前任务失败:
fn main() {
    let x = [1, 2];
    for i in 0..3 {
        println!("{}", x[i]);
    }
}

这个程序失败了:
% ./main2
1
2
task '<main>' failed at 'index out of bounds: the len is 2 but the index is 2', main2.rs:4
get()方法是为了方便而需要的;它可以避免事先检查给定的索引是否有效。
如果您不知道SomeNone到底是什么以及为什么它们通常是必需的,您应该阅读官方 教程,因为它解释了这个非常基本的概念。

5

Option枚举有两个变体。

1- None用于表示失败或没有值

2- Some是一个包装值的元组结构体

如果您需要在OOB中编写此结构,例如在typescript中,您应该这样写。这将使情况更容易可视化

  • Define Option interface as derived class

    interface Option<T = any> {
      // pass all the methods here 
      // unwrap is used to access the wrapped value
      unwrap(): T;
    }
    
  • write Some class which inherits from Option

Some类返回一个值。

class Some<T> implements Option<T> {
  private value: T;
 
  constructor(v: T) {
    this.value = v;
  }
 
  unwrap(): T {
    return this.value
}}
  • 编写继承自OptionNone

None类返回null

class None<T> implements Option<T> {
 // you do not need constructor here     
  unwrap(): T {
    return null as T;
  }
}

1
这是一个非常有趣的例子,谢谢! - silicakes

1
其他讨论 get() 返回类型为 选项枚举 的答案是准确的,但我认为有用的是如何去除打印中的某些内容。为了做到这一点,一个快速的方法是只需在选项上调用 unwrap,尽管这不是生产推荐的方式。关于选项的讨论,请查看 rust 书籍 此处

使用 playground 中的 unwrap 代码进行更新(如下)

fn main() {
    let command = [('G', 'H'), ('H', '5')];

    for i in 0..3 {
        print!(" {} ", i);
        println!("{:?}", command.get(i).unwrap());
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接