坑越来越深了, 在坑里的同学让我看到你们的双手!
前面我们聊过了 Rust 最基本的几种数据类型. 不知道你还记不记得, 如果不记得可以先复习一下. 上一个坑挖好以后, 有同学私信我说坑太深了, 下来的时候差点崴了脚. 我只能对他说抱歉, 下次还有可能更深. 不过这篇文章不会那么深了, 本文我将带大家探索 Structs 和 Enums 这两个坑, 没错, 是双坑. 是不是很惊喜? 好了, 言归正传. 我们先来介绍 Structs.
Structs
Structs 在许多语言里都有, 是一种自定义的类型, 可以类比到 Java 中的类. Rust 中使用 Structs 使用的是 struct 关键字. 例如我们定义一个用户类型.
- struct User {
- username: String,
- email: String,
- sign_in_count: u64,
- active: bool,
- }
初始化时可以直接将上面对应的数据类型替换为正确的值.
- fn build_user(email: String, username: String) -> User {
- User {
- email: email,
- username: username,
- active: true,
- sign_in_count: 1,
- }
- }
下面仔细观察这 email: email 和 username: username 这两行代码, 有没有觉得有点麻烦?, 如果 User 的所有属性值都是从函数参数传进来, 那么我们每个参数名都要重复一遍. 还好 Rust 为我们提供了语法糖, 可以省去一些代码.
初始化 Struct 时省去变量名
对于上面的初始化代码, 我们可以做一些简化.
- fn build_user(email: String, username: String) -> User {
- User {
- email,
- username,
- active: true,
- sign_in_count: 1,
- }
- }
你可以认为这是 Rust 的一个语法糖, 当变量名和字段名相同时, 初始化 Struct 的时候就可以省略变量名. 让开发者不必做过多无意义的重复工作 (写两遍 email).
在其他实例的基础上创建 Struct
除了上面的语法糖以外, 在创建 Struct 时, Rust 还提供了另一个语法糖, 例如我们新建一个 user2, 它只有邮箱和用户名与 user1 不同, 其他属性都相同, 那么我们可以使用如下代码:
- #![allow(unused_variables)]
- fn main() {
- struct User {
- username: String,
- email: String,
- sign_in_count: u64,
- active: bool,
- }
- let user1 = User {
- email: String::from("someone@example.com"),
- username: String::from("someusername123"),
- active: true,
- sign_in_count: 1,
- };
- let user2 = User {
- email: String::from("another@example.com"),
- username: String::from("anotherusername567"),
- ..user1
- };
- }
这里的 ..user1 表示剩下的字段的值都和 user1 相同.
Tuple Struct
接下来再来介绍两个特殊形式的 Struct, 一种是 Tuple Struct, 定义时与 Tuple 相似
- struct Color(i32, i32, i32);
- struct Point(i32, i32, i32);
它与 Tuple 的不同在于, 你可以赋予 Tuple Struct 一个有意义的名字, 而不只是无意义的一堆值. 需要注意的是, 这里我们定义的 Color 和 Point 是两种不同的类型, 它们之间不能互相赋值. 另外, 如果你想要取得 Tuple Struct 中某个字段的值, 和 Tuple 一样, 使用 . 即可.
空字段 Struct
这里还有一种特殊的 Struct, 即没有字段的 Struct. 它叫做类单元结构 (unit-like structs). 这种结构体一般用于实现某些特征, 但又没有需要存储的数据.
Struct 方法
方法和函数非常相似, 不同之处在于, 定义方法时, 必须有与之关联的 Struct, 并且方法的第一个参数必须是 self. 我们先来看一下如何定义一个方法:
- struct Rectangle {
- width: u32,
- height: u32,
- }
- impl Rectangle {
- fn area(&self) -> u32 {
- self.width * self.height
- }
- }
我们提到, 方法必须与 Struct 关联, 这里使用 impl 关键字定义一段指定 Struct 的实现代码, 然后在这个代码块中定义 Struct 相关的方法, 注意我们的 area 方法符合规则, 第一个参数是 self. 调用时只需要用 . 就可以.
- fn main() {
- let rect1 = Rectangle { width: 30, height: 50 };
- rect1.area();
- }
这里的 &self 其实是代替了 rectangle: &Rectangle , 至于这里为什么要使用 & 符号, 我们在前文已经做了介绍. 当然, 这里 self 也不是必须要加 & 符号, 你可以认为它是一个正常的参数, 根据需要来使用.
有些同学可能会有些困惑, 我们已经有了函数了, 为什么还要使用方法? 这其实主要是为了代码的结构. 我们需要将 Struct 实例可以做的操作都放到 impl 实现代码块中, 方便修改和查找. 而使用函数则可能存在开发人员随便找个位置来定义的尴尬情况, 这对于后期维护代码的开发人员来讲将是一种灾难.
现在我们已经知道, 方法必须定义在 impl 代码块中, 且第一个参数必须是 self, 但有时你会在 Impl 代码块中看到第一个参数不是 self 的, 而且 Rust 也允许这种行为.
- impl Rectangle {
- fn square(size: u32) -> Rectangle {
- Rectangle { width: size, height: size }
- }
- }
这是什么情况? 刚才说的不对? 其实不然, 这种函数叫做相关函数 (associated functions). 它仍然是函数, 而不是方法并且直接和 Struct 相关, 类似于 Java 中的静态方法. 调用时直接使用双冒号 ( :: ), 我们之前见过很多次的 String::from("Hi") 就是 String 的相关函数.
最后提一点, Rust 支持为一个 Struct 定义多个实现代码块. 但是我们并不推荐这样使用.
至此, 第一个坑 Struct 就挖好了, 接下来就是第二个坑 Enum.
Enum
很多编程语言都支持枚举类型, Rust 也不例外. 因此枚举对于大部分开发人员来说并不陌生, 这里我们简单介绍一些使用方法及特性.
先来看一下 Rust 中如何定义枚举和获取枚举值.
- enum IpAddrKind {
- V4,
- V6,
- }
- let six = IpAddrKind::V6;
- let four = IpAddrKind::V4;
这里的例子只是最简单的定义枚举的方法, 每个枚举的值也可以关联其他类型的的值. 例如
- enum Message {
- Quit,
- Move { x: i32, y: i32 },
- Write(String),
- ChangeColor(i32, i32, i32),
- }
此外, Enum 也可以像 Struct 拥有 impl 代码块, 你也可以在里面定义方法.
Option 枚举
Option 是 Rust 标准库中定义的一个枚举. 如果你用过 Java8 的话, 一定知道一个 Optional 类, 专门用来处理 null 值. Rust 中是不存在 null 值的, 因为它太容易引起 bug 了. 但如果确实需要的时候怎么办呢, 这就需要 Option 枚举登场了. 我们先来看一看它的定义:
- enum Option<T> {
- Some(T),
- None,
- }
很简单对不对. 它是一个枚举, 只有两个值, 一个是 Some, 一个是 None, 其中 Some 还关联了一个类型 T 的值, 这个 T 类似于 Java 中的泛型, 即它可以是任意类型.
在使用时, 可以直接使用 Some 或 None, 前面不用加 Option:: . 当你使用 None 时, 必须要指定 T 的具体类型.
- let some_number = Some(5);
- let some_string = Some("a string");
- let absent_number: Option<i32> = None;
需要注意的是 Option<T\> 与 T 并不是相同的类型. 你可以在官方文档中查看从 Option<T\> 中提取出 T 的方法.
match 流程控制
Rust 有一个很强大的流程控制操作叫做 match, 它有些类似于 Java 中的 switch. 首先匹配一系列的模式, 然后执行相应的代码. 与 Java 中 switch 不同的是, switch 只能支持数值 / 枚举类型 (现在也可以支持字符串),match 可以支持任意类型.
- enum Coin {
- Penny,
- Nickel,
- Dime,
- Quarter,
- }
- fn value_in_cents(coin: Coin) -> u8 {
- match coin {
- Coin::Penny => 1,
- Coin::Nickel => 5,
- Coin::Dime => 10,
- Coin::Quarter => 25,
- }
- }
此外, match 还可以支持模式中绑定值.
- enum UsState {
- Alabama,
- Alaska,
- // --snip--
- }
- enum Coin {
- Penny,
- Nickel,
- Dime,
- Quarter(UsState),
- }
- fn value_in_cents(coin: Coin) -> u8 {
- match coin {
- Coin::Penny => 1,
- Coin::Nickel => 5,
- Coin::Dime => 10,
- Coin::Quarter(state) => {
- println!("State quarter from {:?}!", state);
- 25
- },
- }
- }
match 与 Option<T\>
前面我们聊到了从 Option<T\> 中提取 T 的值, 我们来介绍一种通过 match 提取的方法.
- fn plus_one(x: Option<i32>) -> Option<i32> {
- match x {
- None => None,
- Some(i) => Some(i + 1),
- }
- }
- let five = Some(5);
- let six = plus_one(five);
- let none = plus_one(None);
这种方法在参数中必须声明 T 的具体类型, 这里再思考一个问题, 如果我们确定 x 一定不会是 None, 那么可不可以去掉 None 的那个条件?
_占位符
答案是不可以, Rust 要求 match 必须列举出所有可能的条件. 例如, 如果一个 u8 类型的, 就需要列举 0 到 255 这些条件. 这样做的话, 可能一天也写不了几个 match 语句吧. 所以 Rust 又给我们准备了一个语法糖.
针对上述情况, 就可以写成下面这样:
- let some_u8_value = 0u8;
- match some_u8_value {
- 1 => println!("one"),
- 3 => println!("three"),
- 5 => println!("five"),
- 7 => println!("seven"),
- _ => (),
- }
我们只需要列举我们关心的几种情况, 然后用占位符 _ 表示剩余所有情况. 看到这我只想感叹一句, 这糖真甜啊.
if let
对于我们只关心一个条件的 match 来讲, 还有一种更加简洁的语法, 那就是 if let.
举个栗子, 我们只想要 Option<u8\> 中值为 3 时打印相关信息, 利用我们已经掌握的知识, 可以这样写.
- let some_u8_value = Some(0u8);
- match some_u8_value {
- Some(3) => println!("three"),
- _ => (),
- }
如果用 if let 呢, 就会更加简洁一些.
- if let Some(3) = some_u8_value {
- println!("three");
- }
这里要注意, 当 match 只有一个条件时, 才可以使用 if let 替代.
有同学可能会问, 既然叫 if let, 那么有没有 else 条件呢? 答案是有的. 对于下面这种情况
- let mut count = 0;
- match coin {
- Coin::Quarter(state) => println!("State quarter from {:?}!", state),
- _ => count += 1,
- }
如果替换成 if let 语句, 应该是
- let mut count = 0;
- if let Coin::Quarter(state) = coin {
- println!("State quarter from {:?}!", state);
- } else {
- count += 1;
- }
总结
第二个坑也挖好了, 来总结一下吧. 本文我们首先介绍了 Struct, 它类似于 Java 中的类, 可以供开发人员自定义类型. 然后介绍了两种初始化 Struct 时的简化代码的方法. 接着是定义 Struct 相关的方法. 在介绍完 Struct 以后, 紧接着又介绍了大家都很熟悉的 Enum 枚举类型. 重点说了 Rust 中特殊的枚举 Option, 然后介绍了 match 和 if let 这两种流程控制语法.
最后, 按照国际惯例, 我还是要诚挚的邀请你早日入坑. 坑里真的是冬暖夏凉~
来源: http://www.tuicool.com/articles/QNfIf2I