Skip to content

Rust

Rust 官网(简中)

官方文档 英文

官方文档 中文

Rustlings 课程

通过例子学习RUST

安装

# 首次安装
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 升级
rustup update
1
2
3
4
5

Cargo:Rust 的构建工具和包管理器

Cargo 功能

  • cargo build 可以构建项目
  • cargo run 可以运行项目
  • cargo test 可以测试项目
  • cargo doc 可以为项目构建文档
  • cargo publish 可以将库发布到 crates.io
  • cargo clippy: 类似eslint,lint工具检查代码可以优化的地方
  • cargo fmt: 类似go fmt,代码格式化
  • cargo tree: 查看第三方库的版本和依赖关系
  • cargo bench:运行benchmark(基准测试,性能测试)
# 查看 cargo 版本
cargo --version
1
2

创建新项目

# 新建 hello-rust 项目
cargo new hello-rust

# 目录结构
hello-rust
|- Cargo.toml
|- src
  |- main.rs
1
2
3
4
5
6
7
8

目录解析

  • Cargo.toml: Rust 的清单文件。其中包含了项目的元数据和依赖库。
  • src/main.rs:应用代码入口。
# cargo run 输出
Compiling hello-rust v0.1.0 (/Users/elliot/Desktop/helloRust/hello-rust)
    Finished dev [unoptimized + debuginfo] target(s) in 2.23s
     Running `target/debug/hello-rust`
Hello, world!
1
2
3
4
5

添加依赖

在 Rust 中,我们通常把包称作“crates”。可以在 crates.io(Rust 包仓库)

变更 toml 文件

# Cargo.toml
[dependencies]
ferris-says = "0.2"
1
2
3

运行 cargo build 后会开始安装依赖

修改 main.rs

use ferris_says::say;
1

这样我们就可以使用 ferris-says crate 中导出的 say 函数了。

一个 Rust 小应用

修改入口文件

# main.rs
use ferris_says::say; // from the previous step
use std::io::{stdout, BufWriter};

fn main() {
    let stdout = stdout();
    let message = String::from("Hello fellow Rustaceans!");
    let width = message.chars().count();

    let mut writer = BufWriter::new(stdout.lock());
    say(message.as_bytes(), width, &mut writer).unwrap();
}
1
2
3
4
5
6
7
8
9
10
11
12

使用 cargo run 查看输出

# cargo run 输出
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/hello-rust`
----------------------------
< Hello fellow Rustaceans! >
----------------------------
              \
               \
                 _~^~^~_
             \) /  o o  \ (/
               '_   -   _'
               / '-----' \
    
1
2
3
4
5
6
7
8
9
10
11
12
13

输出到命令行

println!() 和 print!() 的区别:println!() 会在输出的最后附加一个换行符

占位符输出

fn main() {
  let a = 12;
  println!("a is {}", a)
}
# 输出:a is 12
1
2
3
4
5

多次输出

fn main() {
  let a = 12;
  println!("a is {0}, a again is {0}", a); 
}
# 输出:a is 12, a again is 12

fn main() {
    let a = 12;
    let b = 24;

    println!("a is {0}, a again is {0}, b is {1} , b again is {1}", a, b);
}
# 输出:a is 12, a again is 12, b is 24 , b again is 24
1
2
3
4
5
6
7
8
9
10
11
12
13

输出符号

fn main() { 
    println!("{{}}"); 
}
# 输出:{}
1
2
3
4

基础语法

变量

let a = 123;

// 以下均有错误
a = "abc"; // a 已经是整形数字,不能再被赋值为字符串类型
a = 4.56; // 精度会有损失,Rust 语言不允许精度有损失的自动数据类型转换
a = 456; // a 不是一个可变变量

1
2
3
4
5
6
7

Rust 语言为了高并发安全而做的设计:在语言层面尽量少的让变量的值可以改变。所以 a 的值不可变。但这不意味着 a 不是"变量"(英文中的 variable),官方文档称 a 这种变量为"不可变变量"。

可变变量

let mut a = 123;
a = 456;
1
2

常量与不可变变量的区别

// 合法赋值
let a = 123;   // 可以编译,但可能有警告,因为该变量没有被使用
let a = 456;

// 非法赋值
const a: i32 = 123;
let a = 456;
1
2
3
4
5
6
7

重影(Shadowing)

重影就是指变量的名称可以被重新使用的机制

fn main() {
    let x = 5;
    let x = x + 1;
    let x = x * 2;
    println!("The value of x is: {}", x);
}
// 输出:The value of x is: 12
1
2
3
4
5
6
7

重影与可变变量的赋值不是一个概念,重影是指用同一个名字重新代表另一个变量实体,其类型、可变属性和值都可以变化。但可变变量赋值仅能发生值的变化。

let mut s = "123";
s = s.len();
// 不能给字符串变量赋整型值
1
2
3

数据类型

整数型

位长度有符号无符号
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize

isize 和 usize 两种整数类型是用来衡量数据大小的,它们的位长度取决于所运行的目标平台,如果是 32 位架构的处理器将使用 32 位位长度整型。

进制

进制例如
十进制98_222
十六进制0xff
八进制0o77
二进制0b1111_0000
字节(仅能表示u8型)b'A'

浮点数型(Floating-Point)

Rust 支持32位浮点数(f32),和64为浮点数(f64)

fn main() {
    let x = 2.0; // f64
    let y: f32 = 3.0; // f32
}
1
2
3
4

数学运算

fn main() {
    let sum = 5 + 10; // 加
    let difference = 95.5 - 4.3; // 减
    let product = 4 * 30; // 乘
    let quotient = 56.7 / 32.2; // 除
    let remainder = 43 % 5; // 求余
}
1
2
3
4
5
6
7

Rust 不支持 ++ 和 --

布尔型

布尔型用 bool 表示,值只能为 true 或 false。

复合类型

元组用一对 ( ) 包括的一组数据,可以包含不同种类的数据:

let tup: (i32, f64, u8) = (500, 6.4, 1);
// tup.0 等于 500
// tup.1 等于 6.4
// tup.2 等于 1
let (x, y, z) = tup;
// y 等于 6.4
1
2
3
4
5
6

数组用一对 [ ] 包括的同类型数据。

let a = [1, 2, 3, 4, 5];
// a 是一个长度为 5 的整型数组

let b = ["January", "February", "March"];
// b 是一个长度为 3 的字符串数组

let c: [i32; 5] = [1, 2, 3, 4, 5];
// c 是一个长度为 5 的 i32 数组

let d = [3; 5];
// 等同于 let d = [3, 3, 3, 3, 3];

let first = a[0];
let second = a[1];
// 数组访问

a[0] = 123; // 错误:数组 a 不可变
let mut a = [1, 2, 3];
a[0] = 4; // 正确
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

注释

// 这是第一种注释方式

/* 这是第二种注释方式 */

/*
* 多行注释
* 多行注释
* 多行注释
*/

/// 说明文档注释 
/// Adds one to the number given.
/// 
/// # Examples 
/// 
/// 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

函数

Rust 函数的基本形式

fn <函数名> ( <参数> ) <函数体>
1

Rust 函数名称的命名风格是小写字母以下划线分割

fn main() {
    println!("Hello, world!");
    another_function();
}

fn another_function() {
    println!("Hello, runoob!");
}

// Hello, world!
// Hello, runoob!
1
2
3
4
5
6
7
8
9
10
11

函数参数

fn main() {
    another_function(5, 6);
}

fn another_function(x: i32, y: i32) {
    println!("x 的值为 : {}", x);
    println!("y 的值为 : {}", y);
}
1
2
3
4
5
6
7
8

Rust 中可以在一个用 {} 包括的块里编写一个较为复杂的表达式:

fn main() {
    let x = 5;

    let y = {
        let x = 3;
        x + 1
    };

    println!("x 的值为 : {}", x);
    println!("y 的值为 : {}", y);
}

1
2
3
4
5
6
7
8
9
10
11
12

函数返回值

fn add(a: i32, b: i32) -> i32 {
    return a + b;
}
1
2
3

Rust 不支持自动返回值类型判断!如果没有明确声明函数返回值的类型,函数将被认为是"纯过程",不允许产生返回值,return 后面不能有返回值表达式。这样做的目的是为了让公开的函数能够形成可见的公报。 函数体表达式并不能等同于函数体,它不能使用 return 关键字。

条件语句

条件表达式 number < 5 不需要用小括号(但语法允许),条件表达式必须是 bool 类型

fn main() {
    let number = 3;
    if number < 5 {
        println!("条件为 true");
    } else {
        println!("条件为 false");
    }
}

1
2
3
4
5
6
7
8
9
fn main() {
    let a = 12;
    let b;
    if a > 0 {
        b = 1;
    }  
    else if a < 0 {
        b = -1;
    }  
    else {
        b = 0;
    }
    println!("b is {}", b);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

类似于三元条件运算表达式 (A ? B : C) 的效果

fn main() {
    let a = 3;
    let number = if a > 0 { 1 } else { -1 };
    println!("number 为 {}", number);
}
1
2
3
4
5

循环

while 循环

fn main() {
    let mut number = 1;
    while number != 4 {
        println!("{}", number);
        number += 1;
    }
    println!("EXIT");
}

1
2
3
4
5
6
7
8
9

for 循环

fn main() {
    let a = [10, 20, 30, 40, 50];
    for i in a.iter() {
        println!("值为 : {}", i);
    }
}
1
2
3
4
5
6

a.iter() 代表 a 的迭代器。

通过下标访问数组

fn main() {
let a = [10, 20, 30, 40, 50];
    for i in 0..5 {
        println!("a[{}] = {}", i, a[i]);
    }
}
1
2
3
4
5
6

loop 循环

Rust 语言原生的无限循环结构 —— loop

fn main() {
    let s = ['R', 'U', 'N', 'O', 'O', 'B'];
    let mut i = 0;
    loop {
        let ch = s[i];
        if ch == 'O' {
            break;
        }
        println!("\'{}\'", ch);
        i += 1;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

可以用来当做查找工具,return 会退出 loop 循环,并返回给外部一个返回值

fn main() {
    let s = ['R', 'U', 'N', 'O', 'O', 'B'];
    let mut i = 0;
    let location = loop {
        let ch = s[i];
        if ch == 'O' {
            break i;
        }
        i += 1;
    };
    println!(" \'O\' 的索引为 {}", location);
}
1
2
3
4
5
6
7
8
9
10
11
12

所有权

所有权规则

  • Rust 中的每个值都有一个变量,称为其所有者。
  • 一次只能有一个所有者。
  • 当所有者不在程序运行范围时,该值将被删除。
// 举例
{
    // 在声明以前,变量 s 无效
    let s = "runoob";
    // 这里是变量 s 的可用范围
}
// 变量范围已经结束,变量 s 无效

1
2
3
4
5
6
7
8

基本数据类型的数据存储在栈中,仅在栈中的数据的"移动"方式是直接复制,基本数据类型有:

  • 所有整数类型,例如 i32 、 u32 、 i64 等。
  • 布尔类型 bool,值为 true 或 false 。
  • 所有浮点类型,f32 和 f64。
  • 字符类型 char。
  • 仅包含以上类型数据的元组(Tuples)。
let x = 5;
let y = x;
1
2

s1 的值赋给 s2 以后 s1 将不可用

let s1 = String::from("hello");
let s2 = s1; 
println!("{}, world!", s1); // 错误!s1 已经失效
1
2
3

克隆

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("s1 = {}, s2 = {}", s1, s2);
}
1
2
3
4
5

涉及函数的所有权机制

如果将变量当作参数传入函数,那么它和移动的效果是一样的。

fn main() {
    let s = String::from("hello");
    // s 被声明有效

    takes_ownership(s);
    // s 的值被当作参数传入函数
    // 所以可以当作 s 已经被移动,从这里开始已经无效

    let x = 5;
    // x 被声明有效

    makes_copy(x);
    // x 的值被当作参数传入函数
    // 但 x 是基本类型,依然有效
    // 在这里依然可以使用 x 却不能使用 s

} // 函数结束, x 无效, 然后是 s. 但 s 已被移动, 所以不用被释放


fn takes_ownership(some_string: String) {
    // 一个 String 参数 some_string 传入,有效
    println!("{}", some_string);
} // 函数结束, 参数 some_string 在这里释放

fn makes_copy(some_integer: i32) {
    // 一个 i32 参数 some_integer 传入,有效
    println!("{}", some_integer);
} // 函数结束, 参数 some_integer 是基本类型, 无需释放
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

函数返回值的所有权机制

被当作函数返回值的变量所有权将会被移动出函数并返回到调用函数的地方,而不会直接被无效释放。

fn main() {
    let s1 = gives_ownership();
    // gives_ownership 移动它的返回值到 s1

    let s2 = String::from("hello");
    // s2 被声明有效

    let s3 = takes_and_gives_back(s2);
    // s2 被当作参数移动, s3 获得返回值所有权
} // s3 无效被释放, s2 被移动, s1 无效被释放.

fn gives_ownership() -> String {
    let some_string = String::from("hello");
    // some_string 被声明有效

    return some_string;
    // some_string 被当作返回值移动出函数
}

fn takes_and_gives_back(a_string: String) -> String { 
    // a_string 被声明有效

    a_string  // a_string 被当作返回值移出函数
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

引用与租借

引用(Reference)可以理解为一种指针

fn main() {
    let s1 = String::from("hello");
    let s2 = &s1;
    println!("s1 is {}, s2 is {}", s1, s2);
}

// s1 is hello, s2 is hello
1
2
3
4
5
6
7

函数参数传递的道理一样

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

// The length of 'hello' is 5.
1
2
3
4
5
6
7
8
9
10
11
12
13

引用不会获得值的所有权。

引用只能租借(Borrow)值的所有权。

引用本身也是一个类型并具有一个值,这个值记录的是别的值所在的位置,但引用不具有所指值的所有权:

fn main() {
    let s1 = String::from("hello");
    let mut s2 = &s1;
    let s3 = s1;
    s2 = &s3; // 重新从 s3 租借所有权
    println!("{}", s2);
}
1
2
3
4
5
6
7

租借的数据不能被修改

fn main() {
    let mut s1 = String::from("run");
    // s1 是可变的

    let s2 = &mut s1;
    // s2 是可变的引用

    s2.push_str("oob");
    println!("{}", s2);
}
1
2
3
4
5
6
7
8
9
10

垂悬引用(Dangling References)

如果放在有指针概念的编程语言里它就指的是那种没有实际指向一个真正能访问的数据的指针(注意,不一定是空指针,还有可能是已经释放的资源)。它们就像失去悬挂物体的绳子,所以叫"垂悬引用"。"垂悬引用"在 Rust 语言里不允许出现,如果有,编译器会发现它。

// 错误示例
fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}


// 输出
error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
  |
5 | fn dangle() -> &'static String {
  |                ~~~~~~~~

For more information about this error, try `rustc --explain E0106`.
error: could not compile `hello-rust` due to previous error

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

切片

切片(Slice)是对数据值的部分引用。

fn main() {
    let s = String::from("broadcast");

    let part1 = &s[0..5];
    let part2 = &s[5..9];

    println!("{}={}+{}", s, part1, part2);
}
1
2
3
4
5
6
7
8

..y 等价于 0..y

x.. 等价于位置 x 到数据结束

.. 等价于位置 0 到结束

被切片引用的字符串禁止更改其值

fn main() {
    let mut s = String::from("runoob");
    let slice = &s[0..3];
    s.push_str("yes!"); // 错误
    println!("slice = {}", slice);
}
1
2
3
4
5
6

在 Rust 中有两种常用的字符串类型:str 和 String。str 是 Rust 核心语言类型,就是本章一直在讲的字符串切片(String Slice),常常以引用的形式出现(&str)。 凡是用双引号包括的字符串常量整体的类型性质都是 &str

切片结果必须是引用类型,且要显示定义

let slice = &s[0..3];
1

String 转换成 &str:

let s1 = String::from("hello");
let s2 = &s1[..];

// 运行结果
// 1
// 3
// 5
1
2
3
4
5
6
7

非字符串切片

fn main() {
    let arr = [1, 3, 5, 7, 9];
    let part = &arr[0..3];
    for i in part.iter() {
        println!("{}", i);
    }
}
1
2
3
4
5
6
7

结构体

结构体定义:

struct Site {
    domain: String,
    name: String,
    nation: String,
    found: u32
}
1
2
3
4
5
6

结构体实例

let runoob = Site {
    domain: String::from("www.runoob.com"),
    name: String::from("RUNOOB"),
    nation: String::from("China"),
    found: 2013
};
1
2
3
4
5
6

可以简化书写

let domain = String::from("www.runoob.com");
let name = String::from("RUNOOB");
let runoob = Site {
    domain,  // 等同于 domain : domain,
    name,    // 等同于 name : name,
    nation: String::from("China"),
    traffic: 2013
};
1
2
3
4
5
6
7
8

结构体更新语法:

let site = Site {
    domain: String::from("www.runoob.com"),
    name: String::from("RUNOOB"),
    ..runoob
};
1
2
3
4
5

至少重新设定一个字段的值才能引用其他实例的值。

元组结构体。

更简单的定义和使用结构体的方式:元组结构体。

struct Color(u8, u8, u8);
struct Point(f64, f64);

let black = Color(0, 0, 0);
let origin = Point(0.0, 0.0);

fn main() {
    struct Color(u8, u8, u8);
    struct Point(f64, f64);

    let black = Color(0, 0, 0);
    let origin = Point(0.0, 0.0);

    println!("black = ({}, {}, {})", black.0, black.1, black.2);
    println!("origin = ({}, {})", origin.0, origin.1);
}

// black = (0, 0, 0)
// origin = (0, 0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

结构体所有权

结构体必须掌握字段值所有权,因为结构体失效的时候会释放所有字段。

输出结构体

#[derive(Debug)]

struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };

    println!("rect1 is {:?}", rect1);
}
1
2
3
4
5
6
7
8
9
10
11
12

一定要导入调试库 #[derive(Debug)] ,之后在 println 和 print 宏中就可以用 {:?} 占位符输出一整个结构体:

rect1 is Rectangle { width: 30, height: 50 }
1

如果属性较多的话可以使用另一个占位符 {:#?} 。

rect1 is Rectangle {
    width: 30,
    height: 50
}
1
2
3
4

结构体方法

方法(Method)和函数(Function)类似,用来操作结构体实例

struct Rectangle {
    width: u32,
    height: u32,
}
   
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    println!("rect1's area is {}", rect1.area());
}

// rect1's area is 1500
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

多参数示例

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn wider(&self, rect: &Rectangle) -> bool {
        self.width > rect.width
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 40, height: 20 };

    println!("{}", rect1.wider(&rect2));
}

// false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

结构体关联函数

之所以"结构体方法"不叫"结构体函数"是因为"函数"这个名字留给了这种函数:它在 impl 块中却没有 &self 参数。

这种函数不依赖实例,但是使用它需要声明是在哪个 impl 块中的。

一直使用的 String::from 函数就是一个"关联函数"。

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn create(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }
}

fn main() {
    let rect = Rectangle::create(30, 50);
    println!("{:?}", rect);
}

// Rectangle { width: 30, height: 50 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

结构体 impl 块可以写几次,效果相当于它们内容的拼接!

单元结构体

没有身体的结构体为单元结构体(Unit Struct)

struct UnitStruct;
1

枚举类

#[derive(Debug)]

enum Book {
    Papery, Electronic
}

fn main() {
    let book = Book::Papery;
    println!("{:?}", book);
}

// Papery
1
2
3
4
5
6
7
8
9
10
11
12
// 枚举类
enum Book {
    Papery(u32),
    Electronic(String),
}

let book = Book::Papery(1001);
let ebook = Book::Electronic(String::from("url://..."));

// 结构体
enum Book {
    Papery { index: u32 },
    Electronic { url: String },
}
let book = Book::Papery{index: 1001};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

match 语法

fn main() {
    enum Book {
        Papery {index: u32},
        Electronic {url: String},
    }
   
    let book = Book::Papery{index: 1001};
    let ebook = Book::Electronic{url: String::from("url...")};
   
    match book {
        Book::Papery { index } => {
            println!("Papery book {}", index);
        },
        Book::Electronic { url } => {
            println!("E-book {}", url);
        }
    }
}

// Papery book 1001
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

match 块也可以当作函数表达式来对待,它可以有返回值

enum Book {
    Papery(u32),
    Electronic {url: String},
}
let book = Book::Papery(1001);

match book {
    Book::Papery(i) => {
        println!("{}", i);
    },
    Book::Electronic { url } => {
        println!("{}", url);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

match 除了能够对枚举类进行分支选择以外,还可以对整数、浮点数、字符和字符串切片引用(&str)类型的数据进行分支选择。其中,浮点数类型被分支选择虽然合法,但不推荐这样使用,因为精度问题可能会导致分支错误。

对非枚举类进行分支选择时必须注意处理例外情况,即使在例外情况下没有任何要做的事 . 例外情况用下划线 _ 表示:

Option 枚举类

Option 是 Rust 标准库中的枚举类,这个类用于填补 Rust 不支持 null 引用的空白。

Rust 在语言层面彻底不允许空值 null 的存在,但无奈null 可以高效地解决少量的问题,所以 Rust 引入了 Option 枚举类

enum Option<T> {
    Some(T),
    None,
}
1
2
3
4

定义一个可以为空值的类

let opt = Option::Some("Hello");
1

针对 opt 执行某些操作,你必须先判断它是否是 Option::None:

fn main() {
    let opt = Option::Some("Hello");
    match opt {
        Option::Some(something) => {
            println!("{}", something);
        },
        Option::None => {
            println!("opt is nothing");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11

初始值为空的 Option 必须明确类型

fn main() {
    let opt: Option<&str> = Option::None;
    match opt {
        Option::Some(something) => {
            println!("{}", something);
        },
        Option::None => {
            println!("opt is nothing");
        }
    }
}

// opt is nothing
1
2
3
4
5
6
7
8
9
10
11
12
13

Option 是一种特殊的枚举类,它可以含值分支选择

fn main() {
        let t = Some(64);
        match t {
                Some(64) => println!("Yes"),
                _ => println!("No"),
        }
}
1
2
3
4
5
6
7

if let 语法

// match 语法
let i = 0;
match i {
    0 => println!("zero"),
    _ => {},
}

// 使用 if let 重写
let i = 0;
if let 0 = i {
    println!("zero");
}

// zero
1
2
3
4
5
6
7
8
9
10
11
12
13
14

if let 语法可以认为是只区分两种情况的 match 语句的"语法糖"(语法糖指的是某种语法的原理相同的便捷替代品)

fn main() {
    enum Book {
        Papery(u32),
        Electronic(String)
    }
    let book = Book::Electronic(String::from("url"));
    if let Book::Papery(index) = book {
        println!("Papery {}", index);
    } else {
        println!("Not papery book");
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13

泛型与特性

泛型示例

fn max<T>(array: &[T]) -> T {
    let mut max_index = 0;
    let mut i = 1;
    while i < array.len() {
        if array[i] > array[max_index] {
            max_index = i;
        }
        i += 1;
    }
    array[max_index]
}

1
2
3
4
5
6
7
8
9
10
11
12

结构体与枚举类中的泛型

struct Point<T> {
    x: T,
    y: T
}

let p1 = Point {x: 1, y: 2};
let p2 = Point {x: 1.0, y: 2.0};

let p = Point {x: 1, y: 2.0};


struct Point<T1, T2> {
    x: T1,
    y: T2
}


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

enum Result<T, E> {
    Ok(T),
    Err(E),
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

结构体与枚举类都可以定义方法,那么方法也应该实现泛型的机制,否则泛型的类将无法被有效的方法操作。

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 1, y: 2 };
    println!("p.x = {}", p.x());
}

// p.x = 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

impl 关键字的后方必须有 T,因为它后面的 T 是以之为榜样的。但我们也可以为其中的一种泛型添加方法

impl Point<f64> {
    fn x(&self) -> f64 {
        self.x
    }
}

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

特性

类似于 接口 的概念

trait Descriptive {
    fn describe(&self) -> String;
}
1
2
3

Descriptive 规定了实现者必需有 describe(&self) -> String 方法。

struct Person {
    name: String,
    age: u8
}

impl Descriptive for Person {
    fn describe(&self) -> String {
        format!("{} {}", self.name, self.age)
    }
}
1
2
3
4
5
6
7
8
9
10

格式

impl <特性名> for <所实现的类型名>
1

默认特性

trait Descriptive {
    fn describe(&self) -> String {
        String::from("[Object]")
    }
}

struct Person {
    name: String,
    age: u8
}

impl Descriptive for Person {
    fn describe(&self) -> String {
        format!("{} {}", self.name, self.age)
    }
}

fn main() {
    let cali = Person {
        name: String::from("Cali"),
        age: 24
    };
    println!("{}", cali.describe());
}

// Cali 24
// 去掉 impl Descriptive for Person 输出: [Object]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

特性做参数

fn output(object: impl Descriptive) {
    println!("{}", object.describe());
}

fn output<T: Descriptive>(object: T) {
    println!("{}", object.describe());
}

fn output_two<T: Descriptive>(arg1: T, arg2: T) {
    println!("{}", arg1.describe());
    println!("{}", arg2.describe());
}
1
2
3
4
5
6
7
8
9
10
11
12

特性作类型表示时如果涉及多个特性,可以用 + 符号表示

fn notify(item: impl Summary + Display)
fn notify<T: Summary + Display>(item: T)
1
2

仅用于表示类型的时候,并不意味着可以在 impl 块中使用

复杂的实现关系可以使用 where 关键字简化

fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U)

// 简写
fn some_function<T, U>(t: T, u: U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
1
2
3
4
5
6

取最大值程序

trait Comparable {
    fn compare(&self, object: &Self) -> i8;
}

fn max<T: Comparable>(array: &[T]) -> &T {
    let mut max_index = 0;
    let mut i = 1;
    while i < array.len() {
        if array[i].compare(&array[max_index]) > 0 {
            max_index = i;
        }
        i += 1;
    }
    &array[max_index]
}

impl Comparable for f64 {
    fn compare(&self, object: &f64) -> i8 {
        if &self > &object { 1 }
        else if &self == &object { 0 }
        else { -1 }
    }
}

fn main() {
    let arr = [1.0, 3.0, 5.0, 4.0, 2.0];
    println!("maximum of arr is {}", max(&arr));
}

// maximum of arr is 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

特性做返回值

fn person() -> impl Descriptive {
    Person {
        name: String::from("Cali"),
        age: 24
    }
}
1
2
3
4
5
6

有条件实现方法

struct A<T> {}

impl<T: B + C> A<T> {
    fn d(&self) {}
}
1
2
3
4
5

错误处理

对于可恢复错误用 Result<T, E> 类来处理,对于不可恢复错误使用 panic! 宏来处理。

fn main() {
    panic!("error occured");
    println!("Hello, Rust");
}

// thread 'main' panicked at 'error occured', src\main.rs:3:5
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
1
2
3
4
5
6
7

设置环境变量

RUST_BACKTRACE=1 cargo run
1

输出

thread 'main' panicked at 'error occured', src\main.rs:3:5
stack backtrace:
  ...
  11: greeting::main
             at .\src\main.rs:3
  ...
1
2
3
4
5
6

回溯是不可恢复错误的另一种处理方式,它会展开运行的栈并输出所有的信息,然后程序依然会退出。上面的省略号省略了大量的输出信息,我们可以找到我们编写的 panic! 宏触发的错误。

可恢复的错误

enum Result<T, E> {
    Ok(T),
    Err(E),
}
1
2
3
4

在 Rust 标准库中可能产生异常的函数的返回值都是 Result 类型的。例如:当我们尝试打开一个文件时:

use std::fs::File;

fn main() {
    let f = File::open("hello.txt");
    match f {
        Ok(file) => {
            println!("File opened successfully.");
        },
        Err(err) => {
            println!("Failed to open the file.");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

如果 hello.txt 文件不存在,会打印 "Failed to open the file."。

可恢复的错误的传递

fn f(i: i32) -> Result<i32, bool> {
    if i >= 0 { Ok(i) }
    else { Err(false) }
}

fn main() {
    let r = f(10000);
    if let Ok(v) = r {
        println!("Ok: f(-1) = {}", v);
    } else {
        println!("Err");
    }
}

// Ok: f(-1) = 10000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn f(i: i32) -> Result<i32, bool> {
    if i >= 0 { Ok(i) }
    else { Err(false) }
}

fn g(i: i32) -> Result<i32, bool> {
    let t = f(i)?;
    Ok(t) // 因为确定 t 不是 Err, t 在这里已经是 i32 类型
}

fn main() {
    let r = g(10000);
    if let Ok(v) = r {
        println!("Ok: g(10000) = {}", v);
    } else {
        println!("Err");
    }
}

// Ok: g(10000) = 10000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

kind 方法

判断 Result 的 Err 类型,获取 Err 类型的函数是 kind()

use std::io;
use std::io::Read;
use std::fs::File;

fn read_text_from_file(path: &str) -> Result<String, io::Error> {
    let mut f = File::open(path)?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

fn main() {
    let str_file = read_text_from_file("hello.txt");
    match str_file {
        Ok(s) => println!("{}", s),
        Err(e) => {
            match e.kind() {
                io::ErrorKind::NotFound => {
                    println!("No such file");
                },
                _ => {
                    println!("Cannot read the file");
                }
            }
        }
    }
}

// No such file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

参考资料

菜鸟教程 Rust

Released under the MIT License. Thanks to WebStorm software support.