Option 不是结构体声明,而是枚举类型。印象中的枚举类型都是固定的常量,这里 Rust 的枚举支持了泛型。枚举了两种可能:有值、或没值。None表示没有值,Some(T) 表示存在 T 类型的值。
enum Option<T> {Some(T),None,
}
简单示例
使用一个简单的函数来体会一下 Option 的用法:返回变量 a 和 b 中最小的那个值,如果两者相等,返回 None。
fn min(a: u32, b: u32) -> Option<u32> {if a > b {return Some(b);}if a < b {return Some(a);}None
}
Rust 支持单测的能力,我们在函数的声明后追加单测用例。这个#[test]属于属性标记,Rust 中有各种各样的属性标记,后续会专门介绍。同 Go 一样,cargo build 的时候同样会忽略单测的方法。
#[test]
fn test_min() {assert_eq!(min(12u32, 10u32), Some(10u32));
}
包裹 tuple 类型
我们尝试使用 Option 返回一个 tuple 类型,tuple 也被叫做元组,用一个括号表示,由2个、3个…各种类型的值组成。Go 中没有这种类型,之所以提到这个类型,是想带着大家去考虑一下这种情景:如果返回值是包含 2 个值的 tuple 类型,我们应该使用 Option 还是应该使用 Option<(T,T)>?
我们给 (T, T) 来设定一个业务场景,表示一个点位数据:经度和纬度。多个点可以组成一条线,点和点之间使用分号分隔,点内部使用逗号分隔。我们构造一个简单的字符串:“116.42,39.89
;116.46,38.68”,先用一个更简单的例子来看一下:
fn parse_loc<T: FromStr>(s: &str, separator: char) -> Option<(T, T)> {match s.find(separator) {None => None, Some(index) => match (T::from_str(&s[..index]), T::from_str(&s[index+1..])) {(Ok(lng), Ok(lat)) => Some((lng, lat)),_ => None,}}
}#[test]
fn test_parse_loc() { assert_eq!(parse_loc::<f64>("116.42,39.89", ','), Some((116.42, 39.89)))
}
如果将 Option<(T, T)> 修改成 Option,编译时不能通过的:mismatched types expected type parameter T found tuple (T, T)。由此可见,T 和 Go 语言中的 interface 或者泛型还是有区别的,Rust 也省去了后续类型断言的工作。
绑定关系
我们对 parse_loc 方法体中 <T: FromStr> 声明有些模陌生,FromStr 类似于 Go 语言中接口的声明,也定义了一组行为,约束类型 T 必须实现这样的行为。只不多 rust 中通过 trait 关键字来声明来实现。我们看官方文档对 FromStr 的解释:
pub trait FromStr: Sized {type Err;// Required methodfn from_str(s: &str) -> Result<Self, Self::Err>;
}
Result<>
也属于泛型枚举类型,听到枚举类型,总觉得和印象中的枚举有些出入,rust 的枚举类型设计的过于丰富了。而解析就得用到 match 匹配模式。有点类似正则表达式 ,match 后面实际产生的值,要和 match 列举的模式相匹配,个数、类型都要一一对应。模式 _ 表示通配符,能匹配到任意值,但不会保存匹配到的结构。
总之,match 虽然是新的表达式,但还是比较容易理解的。
Self 关键字
我们继续看 FromStr 中声明的 Self 关键字,第一次接触也会困惑,其实还有 self 关键字,挺神奇的吧。Self 表示类型本身,比如 test_parse_loc 单测方法中,传递的 T 类型是 f64,那 Self 表示的就是 f64 类型。
我们扩展一个例子,来认识一下 self 关键字,通过 self 关键字来继续引申 Self 关键字。我们使用 rust 定义一个点类型结构体,如果结构体或者其中的字段要想在模块外部可见,需要在定义中加上 pub 关键字,默认都是私有属性。反观 go 语言,通过首字母大小写来控制访问权限,比 rust 要简洁不少。
pub struct Point {pub x: f64,pub y: f64
}
和 go 给结构体绑定方法一样,rust 也支持给结构体实现方法,我们现在给 Point 绑定一个方法,用来计算两个点之间的距离。hypot 方法是 f64 类型已经实现的,用来计算两个点之间的距离。
impl Point {pub fn distance(self, p: Point) -> f64 {(self.x - p.x).hypot(self.y - p.y)}
}
方法体中第一个参数 self 就是用来指代结构体本身,这又是 rust 专门声明的简写方式,帮助我们不需要特别声明 self 的类型。除了 self ,其实还有 &self 和 &mut self 的声明方式。我们在后续的文章中进行解释。
go 语言底层也做了这样的设计,方法的第一个参数是自己本身,只不过封装在了实现内部,降低了我们开发的负担。这么说来,感觉 go 确实好用。
我们继续说 Self 关键字,我们上面有提到 FromStr 的特性实现,类似 go 语言中的接口实现,我们也实现一个 rust 接口设计,同时,联动调整了 impl 语句,关键还是 for 表示 Point 类型实现了 Distance 特性。其实,distance 方法还可以添加默认的实现,这里也不展开。
在 trait Distance 中,我们使用 Self 来指代实现 Distance 的类型。说白了,就是 self 指代当前结构体,Self 指代当前结构体的类型。可能你已经敏锐的发现,Self 做了泛型要做的事情。
pub trait Distance {fn distance(self, p: Self) -> f64;
}impl Distance for Point {fn distance(self, p: Point) -> f64 {(self.x - p.x).hypot(self.y - p.y)}
}
Result
上面例子中,from_str 返回值类型为 Result,rust 给这个枚举类型提供了很多便捷的操作方法。