Rust入坑指南:千人千构

坑愈来愈深了,在坑里的同窗让我看到大家的双手!html

前面咱们聊过了Rust最基本的几种数据类型。不知道你还记不记得,若是不记得能够先复习一下。上一个坑挖好之后,有同窗私信我说坑太深了,下来的时候差点崴了脚。我只能对他说抱歉,下次还有可能更深。不过这篇文章不会那么深了,本文我将带你们探索Structs和Enums这两个坑,没错,是双坑。是否是很惊喜?好了,言归正传。咱们先来介绍Structs。git

Structs

Structs在许多语言里都有,是一种自定义的类型,能够类比到Java中的类。Rust中使用Structs使用的是struct关键字。例如咱们定义一个用户类型。github

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: emailusername: username这两行代码,有没有以为有点麻烦?,若是User的全部属性值都是从函数参数传进来,那么咱们每一个参数名都要重复一遍。还好Rust为咱们提供了语法糖,能够省去一些代码。编程语言

初始化Struct时省去变量名

对于上面的初始化代码,咱们能够作一些简化。函数

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}
复制代码

你能够认为这是Rust的一个语法糖,当变量名和字段名相同时,初始化Struct的时候就能够省略变量名。让开发者没必要作过多无心义的重复工做(写两遍email)。ui

在其余实例的基础上建立Struct

除了上面的语法糖之外,在建立Struct时,Rust还提供了另外一个语法糖,例如咱们新建一个user2,它只有邮箱和用户名与user1不一样, 其余属性都相同,那么咱们可使用以下代码:spa

#![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相同。code

Tuple Struct

接下来再来介绍两个特殊形式的Struct,一种是Tuple Struct,定义时与Tuple类似htm

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这两种流程控制语法。

最后,按照国际惯例,我仍是要诚挚的邀请你早日入坑。坑里真的是冬暖夏凉~

相关文章
相关标签/搜索