[教程]Option的正确打开方式

作为一款把安全发挥到极致的现代化语言,Rust怎会轻易放过程序运行中的错误呢,简单地抛出异常?不存在的,给老子都处理了去。
不同于c#,python等传统语言采用抛出异常的方式处理错误,Rust基于强大的类型,泛型系统,采用Option枚举的方式,在程序运行过程中表达“有”和“无”的概念。

What?

假定你有Rust语法基础,再回顾一下,Option是一个枚举类型,它长这样:

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

它的位置:std::option::Option

使用场景

根据std::option的介绍,Option<T>有以下7种用法。

  1. 初始化值
  2. 作为在整个输入范围没有定义的函数的返回值
  3. 如果函数可能有错误,可用 Option<T>作为返回值,用None代表可能出现的错误。
  4. 用作struct的可选字段
  5. 用作函数的可选参数
  6. 空指针
  7. 用作复杂情况的返回值

初始化值
如果一个变量可能有值,也可能没有,那么可以使用Option<T>初始化该变量。

let s = Some(3);
if let Some(ref s) = s{
  println!("{:?}",*s);
}

有或没有值所造成的影响,只有在使用该变量时才体现出来,否则只是println!()没必要初始化一个Option,能使用一个Option<T>的情况有几种,分别是:

  • 作为函数返回值
  • struct有带有Option<T>的字段时,初始化该字段需要创建Option<T>
  • 作为函数参数

通过一个set_score()struct Student设置值

#[derive(Debug)]
struct Student {
    name:String,
    year:u8,
    score:Option<f32>//可选的字段
}
impl Student{
  fn new(n:String,y:u8)->Self{
    Self{
      name:n,
      year:y,
      score:None,
    }
  }
  //接收Option作为参数
  fn set_score(&mut self,s:Option<f32>){
    self.score = s;
  }
}
  //返回Option
  fn compute_score(s:f32)->Option<f32>{
        let d = s*0.75;
        Some(d)
  }
fn main(){
    let mut d = Student::new("xiaoming".to_string(),18);
    dbg!(&d.score);
    let score = compute_score(100.0);
    d.set_score(score);
    dbg!(&d.score);
}
[Running: cargo run --message-format=json]
   Compiling study v0.1.0 (/home/banapy/projects/study)
    Finished dev [unoptimized + debuginfo] target(s) in 0.65s
     Running `target/debug/study`
[src/main.rs:25] &d.score = None
[src/main.rs:28] &d.score = Some(
    75.0
)
[Finished in 0.8s]

空指针
此处搬运标准库的例子
Rust的指针指向一定是个合法的内存地址,因此Rust没有空指针,取而代之的是可选指针(optional pointer),比如Option<Box<T>>
下面的栗子使用Option创建一个Option<Box<i32>>,为了使用i32的值,check_option函数使用了模式匹配,判断box里是否有值,因为Option也可能没值嘛。

let optional = None;
check_optional(optional);

let optional = Some(Box::new(9000));
check_optional(optional);
//接收Option作为参数
fn check_optional(optional: Option<Box<i32>>) {
    match optional {
        Some(ref p) => println!("has value {}", p),
        None => println!("has no value"),
    }
}

上面的例子使用Option创建一个安全的可空指针(nullable pointer),这种Option的用法在Rust中很常见,因此Rust对此做了优化,确保Option<Box<T>>像一个很普通的指针那样高效率,且不带额外的开销。
复杂情况的返回值
Rust是个静态强类型的语言,特性trait是编译期采用静态分发的方式为每个struct添加方法,因此Rust的抽象没有额外的开销。
当我们调用某个复杂的库,实现其一个带有类型占位符的trait时,我们要指定一个类型,否则编译不过,但是此类型不是随便指定的,它可能需要实现某种trait,因为后续的操作可能要用到某种trait的方法,这时库会给我们提供一些实现某种trait的基础类型,比如Option<T>,Result<T,E>等。
来看actix中的一个例子:
不知道什么是actix,actix中文
actix文档,actixDoc
创建一个Actor需要为其结构体实现 trait Actor

extern crate actix;
use actix::prelude::*;
struct MyActor;
impl Actor for MyActor {
    type Context = Context<Self>;
}

Actor之间通过消息进行交流,当Actor接收一条消息后,我们要定义一个handle处理这条消息。

struct WhoAmI;

impl Message for WhoAmI {
    //此处指定为Option<Addr<MyActor>>类型
    type Result = Option<actix::Addr<MyActor>, ()>;
}

impl Handler<WhoAmI> for MyActor {
    type Result = Option<actix::Addr<MyActor>, ()>;

    fn handle(&mut self, msg: WhoAmI, ctx: &mut Context<Self>) -> Self::Result {
        Ok(ctx.address())
    }
}

消息处理都有一个结果,由于Rust的静态强类型,我们必须为结果指定一个数据类型type Result=Option<T>,这条数据类型不是随便指定的,它要实现trait MessageResponse,但是我们太懒了,不想费时间实现这个trait,不实现又不行,这时可以使用actix库提供的基础类型,这些类型都实现了MessageResponse,比如Option<T>,这时,Option<T>就能在这种复杂情况下带出要返回的值。
当然复杂情况不止这一种,欢迎总结。
说完Option的几个使用场景,下面来看看针对Option可用的操作。

可用操作

  1. match
  2. if let
  3. unwrap()等组合器

match是个很强大的模式匹配,很适合匹配Option等枚举类型。

fn main(){
    let d = Student::new("xiaoming".to_string(),18);
    let d = Some(d);
   match d{
    Some(s)=>{
      println!("{:?}", s.name);
    }
    None=>{
      println!("None");
    }
  }
}

match简单易操作,只是有点冗长,众所周知,程序员是相当的“懒”,不想写一点多余的代码,在很多情况下我们并不关心错误值是什么,只想用Option里的值,这时一个简单的if let语句给我们省下很多事。

fn main(){
    let d = Student::new("xiaoming".to_string(),18);
    let d = Some(&d);
    let num = if let Some(s) = d{
    println!("{:?}", s.name);
    Some(3)
  }
}

Option的一些基本操作,相信大家一看就懂,这里不再赘述,下面重点看下Option的组合器。这种写法属于函数式编程,刚接触的人肯定对map, map_or,map_or_else很困惑。
所谓的函数式是一种编程范式,Rust有函数式编程的支持,不代表Rust是函数式编程语言。
想了解函数式编程?
函数式编程--百度百科
下面以说明,源码,例子的顺序,一一介绍这些好玩的东东:
注意:断言assert_eq!(a,b),如果a==b,什么都不发生,否则中断程序。

  1. expect()
  • 说明:
    有值,返回值,否则,中断程序,打印msg错误信息。

  • 源码:

    pub fn expect(self, msg: &str) -> T {
        match self {
            Some(val) => val,
            None => expect_failed(msg),
        }
    }
  • 栗子:
let x = Some("value");
assert_eq!(x.expect("the world is ending"), "value");
let x: Option<&str> = None;
x.expect("the world is ending"); // panics with `the world is ending`
  1. unwrap()
  • 说明:
    有值,返回值,否则,中断程序。

  • 源码:

    pub fn unwrap(self) -> T {
        match self {
            Some(val) => val,
            None => panic!("called `Option::unwrap()` on a `None` value"),
        }
    }
  • 栗子:
let x = Some("air");
assert_eq!(x.unwrap(), "air");
let x: Option<&str> = None;
assert_eq!(x.unwrap(), "air"); // fails
  1. unwrap_or()
  • 说明:
    有值,返回值,否则返回一个默认值。
  • 源码:
    pub fn unwrap_or(self, def: T) -> T {
        match self {
            Some(x) => x,
            None => def,
        }
    }
  • 栗子:
assert_eq!(Some("car").unwrap_or("bike"), "car");
assert_eq!(None.unwrap_or("bike"), "bike");
  1. unwrap_or_else()
  • 说明:
    有值,返回值,否则,执行闭包。
  • 源码:
    pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
        match self {
            Some(x) => x,
            None => f(),
        }
    }
  • 栗子:
let k = 10;
assert_eq!(Some(4).unwrap_or_else(|| 2 * k), 4);
assert_eq!(None.unwrap_or_else(|| 2 * k), 20);
  1. map()
  • 说明:
    改变值,并返回另一个Option。
  • 源码:
    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Option<U> {
        match self {
            Some(x) => Some(f(x)),
            None => None,
        }
    }
  • 栗子:
let maybe_some_string = Some(String::from("Hello, World!"));
// `Option::map` takes self *by value*, consuming `maybe_some_string`
let maybe_some_len = maybe_some_string.map(|s| s.len());

assert_eq!(maybe_some_len, Some(13));
  1. map_or()
  • 说明:
    有值,则执行闭包返回值,否则返回一个自定义的默认值。
  • 源码:
    pub fn map_or<U, F: FnOnce(T) -> U>(self, default: U, f: F) -> U {
        match self {
            Some(t) => f(t),
            None => default,
        }
    }
  • 栗子:
let x = Some("foo");
assert_eq!(x.map_or(42, |v| v.len()), 3);

let x: Option<&str> = None;
assert_eq!(x.map_or(42, |v| v.len()), 42);
  1. map_or_else()
  • 说明:
    有值,执行闭包,否则执行另一个闭包。
  • 源码:
    pub fn map_or_else<U, D: FnOnce() -> U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U {
        match self {
            Some(t) => f(t),
            None => default(),
        }
    }
  • 栗子:
let k = 21;

let x = Some("foo");
assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 3);

let x: Option<&str> = None;
assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 42);
  1. ok_or()
  • 说明:
    有值,返回Result,否则返回自定义的错误。
  • 源码:
    pub fn ok_or<E>(self, err: E) -> Result<T, E> {
        match self {
            Some(v) => Ok(v),
            None => Err(err),
        }
    }
  • 栗子:
let x = Some("foo");
assert_eq!(x.ok_or(0), Ok("foo"));

let x: Option<&str> = None;
assert_eq!(x.ok_or(0), Err(0));
  1. ok_or_else()
  • 说明:
    有值,返回Result,否则执行代表错误的闭包。
  • 源码:
    pub fn ok_or_else<E, F: FnOnce() -> E>(self, err: F) -> Result<T, E> {
        match self {
            Some(v) => Ok(v),
            None => Err(err()),
        }
    }
  • 栗子:
let x = Some("foo");
assert_eq!(x.ok_or_else(|| 0), Ok("foo"));

let x: Option<&str> = None;
assert_eq!(x.ok_or_else(|| 0), Err(0));
  1. iter()
  • 说明:
    把Option转换为迭代器。
  • 源码:
    pub fn iter(&self) -> Iter<T> {
        Iter { inner: Item { opt: self.as_ref() } }
    }
  • 栗子:
let x = Some(4);
assert_eq!(x.iter().next(), Some(&4));

let x: Option<u32> = None;
assert_eq!(x.iter().next(), None);
  1. and()
  • 说明:
    有值,返回另一Option,否则返回None。
  • 源码:
    pub fn and<U>(self, optb: Option<U>) -> Option<U> {
        match self {
            Some(_) => optb,
            None => None,
        }
    }

  • 栗子:
let x = Some(2);
let y: Option<&str> = None;
assert_eq!(x.and(y), None);

let x: Option<u32> = None;
let y = Some("foo");
assert_eq!(x.and(y), None);

let x = Some(2);
let y = Some("foo");
assert_eq!(x.and(y), Some("foo"));

let x: Option<u32> = None;
let y: Option<&str> = None;
assert_eq!(x.and(y), None);
  1. and_then()
  • 说明:
    有值,执行闭包,否则返回None。
  • 源码:
    pub fn and_then<U, F: FnOnce(T) -> Option<U>>(self, f: F) -> Option<U> {
        match self {
            Some(x) => f(x),
            None => None,
        }
    }
  • 栗子:
fn sq(x: u32) -> Option<u32> { Some(x * x) }
fn nope(_: u32) -> Option<u32> { None }

assert_eq!(Some(2).and_then(sq).and_then(sq), Some(16));
assert_eq!(Some(2).and_then(sq).and_then(nope), None);
assert_eq!(Some(2).and_then(nope).and_then(sq), None);
assert_eq!(None.and_then(sq).and_then(sq), None);
  1. filter()
  • 说明:
    过滤器,过滤出自己想要的值。
  • 源码:
    pub fn filter<P: FnOnce(&T) -> bool>(self, predicate: P) -> Self {
        if let Some(x) = self {
            if predicate(&x) {
                return Some(x)
            }
        }
        None
    }
  • 栗子:
fn is_even(n: &i32) -> bool {
    n % 2 == 0
}

assert_eq!(None.filter(is_even), None);
assert_eq!(Some(3).filter(is_even), None);
assert_eq!(Some(4).filter(is_even), Some(4));
  1. or()
  • 说明:
    有值,返回自身,否则返回自定义的Option。
  • 源码:
    pub fn or(self, optb: Option<T>) -> Option<T> {
        match self {
            Some(_) => self,
            None => optb,
        }
    }
  • 栗子:
let x = Some(2);
let y = None;
assert_eq!(x.or(y), Some(2));

let x = None;
let y = Some(100);
assert_eq!(x.or(y), Some(100));

let x = Some(2);
let y = Some(100);
assert_eq!(x.or(y), Some(2));

let x: Option<u32> = None;
let y = None;
assert_eq!(x.or(y), None);
  1. or_else()
  • 说明:
    有值,返回自身,否则执行闭包。
  • 源码:
    pub fn or_else<F: FnOnce() -> Option<T>>(self, f: F) -> Option<T> {
        match self {
            Some(_) => self,
            None => f(),
        }
    }
  • 栗子:
fn nobody() -> Option<&'static str> { None }
fn vikings() -> Option<&'static str> { Some("vikings") }

assert_eq!(Some("barbarians").or_else(vikings), Some("barbarians"));
assert_eq!(None.or_else(vikings), Some("vikings"));
assert_eq!(None.or_else(nobody), None);
  1. take()
  • 说明:
    取出一个值。
  • 源码:
    pub fn take(&mut self) -> Option<T> {
        mem::replace(self, None)
    }
  • 栗子:
let mut x = Some(2);
let y = x.take();
assert_eq!(x, None);
assert_eq!(y, Some(2));

let mut x: Option<u32> = None;
let y = x.take();
assert_eq!(x, None);
assert_eq!(y, None);

欢迎指正

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341

推荐阅读更多精彩内容

  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,706评论 0 38
  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,357评论 0 5
  • 函数式编程 引言 Scala中的函数是Java中完全没有的概念。因为Java是完全面向对象的编程语言,没有任何面向...
    义焃阅读 1,264评论 2 5
  • 最近一直咳嗽了好几天,说话一直嗓子哑,说不出口。可偏偏在这个时候我要给孩子们上课,心有余而力不足的感觉,对孩子们抱...
    源源哒阅读 218评论 0 0
  • 当我们每个人出生时,开始了第一声啼哭,我们的生命就开始了倒计时。时间的钟摆滴滴答答地摇过,我们开始逐渐长大...
    惟昔阅读 377评论 0 0