在几乎所有的语言中,都有两个问题需要处理。一个是返回值,特别是错误的返回值;另外一个是异常。而有过开发经验的都知道,在低级语言中,如C/c++中,大多数异常,往往意味着整个线程甚至整个进程的崩溃。所以一般都是使用错误返回值来处理程序的问题。而Rust号称是要搞定c++的,所以它也得面临这两个问题。
这次先说一下Rust返回值,返回值有两个处理方式,一个是Option,一个是Result。这次只谈Option,下一次再谈Result。
在Rust中,Option的定义如下:
#[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[rustc_diagnostic_item = "option_type"]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Option {
/// No value
#[lang = "None"]
#[stable(feature = "rust1", since = "1.0.0")]
None,
/// Some value `T`
#[lang = "Some"]
#[stable(feature = "rust1", since = "1.0.0")]
Some(#[stable(feature = "rust1", since = "1.0.0")] T),
}
看到None没有,其实这个枚举类是为了支持类似NULL(空)这种类型的一种枚举类。在几乎所有的常见语言中,都可以看到类似于NULL的定义,Rust也不能没有啊。但是NULL往往又是引起整个程序异常的根苗,怎么办呢?Rust只好弄这么一个看起来有点别扭的枚举类来搞一下。这就有点类似于其它语言中的打包,把这个东西包装一下,既安全,又好控制。
那这里面又一个有Some(T),这又是何方神圣?在上面说到了判断空,那么一定有不为空的时候儿,这时候儿就得Some出头了。Some,顾名思意,有一些,有部分,对吧。那去除为空的部分,自然就是不为空的部分了。这时候儿你就要取出它来进行数据处理了。看上去可能对初学者有一些麻烦,但包装一下,优势也就出来了,安全。所以说,看待这个Option的好与坏,就得从不同的角度来看问题,以偏盖全可就是抬杠的前奏。
Some的主要用法包括:
1、Some可以=>不同的结构体
2、Some可以使用Map
3、Some可以和Option实现一些特性
看一个例子:
let mut num = Some(9);
let a = num.map(|x| 2 * x);
扯回到Otpion上,看一下Option的定义实现的接口函数:
impl Option {
#[must_use = "if you intended to assert that this has a value, consider `.unwrap()` instead"]
#[inline]
#[rustc_const_stable(feature = "const_option", since = "1.48.0")]
#[stable(feature = "rust1", since = "1.0.0")]
pub const fn is_some(&self) -> bool {
matches!(*self, Some(_))
}
#[must_use = "if you intended to assert that this doesn't have a value, consider \
`.and_then(|| panic!(\"`Option` had a value when expected `None`\"))` instead"]
#[inline]
#[rustc_const_stable(feature = "const_option", since = "1.48.0")]
#[stable(feature = "rust1", since = "1.0.0")]
pub const fn is_none(&self) -> bool {
!self.is_some()
}
#[must_use]
#[inline]
#[unstable(feature = "option_result_contains", issue = "62358")]
pub fn contains(&self, x: &U) -> bool
where
U: PartialEq,
{
match self {
Some(y) => x == y,
None => false,
}
}
#[inline]
#[rustc_const_stable(feature = "const_option", since = "1.48.0")]
#[stable(feature = "rust1", since = "1.0.0")]
pub const fn as_ref(&self) -> Option<&T> {
match *self {
Some(ref x) => Some(x),
None => None,
}
}
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn as_mut(&mut self) -> Option<&mut T> {
match *self {
Some(ref mut x) => Some(x),
None => None,
}
}
/// Converts from [`Pin`]`<&Option>` to `Option<`[`Pin`]`<&T>>`.
#[inline]
#[stable(feature = "pin", since = "1.33.0")]
pub fn as_pin_ref(self: Pin<&Self>) -> Option> {
// SAFETY: `x` is guaranteed to be pinned because it comes from `self`
// which is pinned.
unsafe { Pin::get_ref(self).as_ref().map(|x| Pin::new_unchecked(x)) }
}
/// Converts from [`Pin`]`<&mut Option>` to `Option<`[`Pin`]`<&mut T>>`.
#[inline]
#[stable(feature = "pin", since = "1.33.0")]
pub fn as_pin_mut(self: Pin<&mut Self>) -> Option> {
// SAFETY: `get_unchecked_mut` is never used to move the `Option` inside `self`.
// `x` is guaranteed to be pinned because it comes from `self` which is pinned.
unsafe { Pin::get_unchecked_mut(self).as_mut().map(|x| Pin::new_unchecked(x)) }
}
......
}
里面的一些小细节的实现函数还是比较多的。比如直接可以painc的unwrap,可以自动过滤结果的Map等。这里没有把整个代码拷贝上来,有兴趣可以看看库里这个Option.rs文件。这里只介绍几个常见的用法:
// map是标准库中的方法
fn map(option: Option, f: F) -> Option where F: FnOnce(T) -> A {
match option {
None => None,
Some(value) => Some(f(value)),
}
}
// 使用map
fn extension(file_name: &str) -> Option<&str> {
find(file_name, '.').map(|i| &file_name[i+1..])
}
fn main() {
match extension_explicit("foo.rs") {
None => println!("no extension"),
Some(ext) => assert_eq!(ext, "rs"),
}
}
//and_then 直接返回一个Option
use std::path::Path;
fn file_name(file_path: &str) -> Option<&str> {
let path = Path::new(file_path);
path.file_name().to_str()
}
fn file_path_ext(file_path: &str) -> Option<&str> {
file_name(file_path).and_then(extension)
}
再看一下unwrap_or的用法:
fn main() {
assert_eq!(extension("foo.rs").unwrap_or("rs"), "rs");
assert_eq!(extension("foo").unwrap_or("rs"), "rs");
}
其实说的更清楚一点,就是如果把细节把控的更严格,那么就会更安全。但把控的越严格,反过来也会有一个不好之处,就是一定会失去一定的灵活性。正所谓“二者不可兼”,看设计者如何来决定二者的分寸而已。
Rust的迭代应该是比较快的,而且它的学习曲线也是不友好的。特别对于一些没有什么语言基础或者说只是有一些更上层的语言基础的人来说,简单如梦幻一般的看得到却触碰不着。所以,这也是Rust的一个问题,一个小小的Option和c++比较,甚至和Linux的C返回的错误值比较就可以很清楚的看明白,Rust自然有它的优势,但往往优势,在一些情况,恰恰是劣势。就看应用的场景和应用的人的灵活性了。
努力吧,归来的少年!