学习 Rust 中的笔记

本文档使用和 Rust 官方文档同样的 mdbook 来编写,文中大部分代码可直接运行。

主要参考资料是官方文档:https://doc.rust-lang.org/book/

持续更新中...

开始学习精彩的 Rust 吧!

变量的声明

先来看看下面的代码:

#![allow(unused)]
fn main() {
let hello = "早上好";
hello = "晚上好";
println!("{}",hello);
}

以我多年的编程经验,看了又看,好像没什么问题,但是运行时却报如下错误

   Compiling playground v0.0.1 (/playground)
error[E0384]: cannot assign twice to immutable variable `hello`
 --> src/main.rs:4:1
  |
3 | let hello = "早上好";
  |     -----
  |     |
  |     first assignment to `hello`
  |     help: consider making this binding mutable: `mut hello`
4 | hello = "晚上好";
  | ^^^^^^^^^^^^^^^^ cannot assign twice to immutable variable

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

编译器告诉我们,这个变量不能定义两次,如果要多次定义的话,需要使用 mut 关键字,于是我们按照编译器的要求,改成如下代码:

#![allow(unused)]
fn main() {
let mut hello = "早上好";
hello = "晚上好";
println!("{}",hello);
}

编译通过并成功输出晚上好

自动推断变量类型: 可以看到我们在声明变量时,使用了 let 关键字,这个关键字告诉编译器,这个变量是一个可变的(mutable)变量,这样编译器就可以自动推断出这个变量的类型。

变量遮蔽

Rust 允许声明相同的变量名,在后面声明的变量会遮蔽掉前面声明的,如下所示:

#![allow(unused)]
fn main() {
let x = 9;
let x = x+1;
println!("{}",x);
}

上面的代码中,会先将 x 赋值为 9,然后再声明一个相同的变量 x, 将当前的 x9 的变量加 1 后在重新赋值给新定义的 x 上,最后输出 10

不同于 mut 关键字,这中声明变量的方法会在第二次定义 x 变量时,在内存空间中开辟出一个新的空间。这样做有好处也有坏处,好处就是可以减少变量名的定义,比如当你只想要知道一个字符串的长度而不需要知道字符串的内容时,我们可以直接这样定义:

#![allow(unused)]
fn main() {
let x = "hello";
let x = x.len();
println!("字符串长度为: {x}");
}

⚠️ 当后一个变量遮蔽前一个变量时,我们就无法访问到前一个变量的值。

当你用 mut 关键字来实现相同的代码时,编译器就会报错:

#![allow(unused)]
fn main() {
let mut x = "hello";
x = x.len();
println!("字符串长度为: {x}");
}

发生错误:

      Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
 --> src/main.rs:4:5
  |
3 | let mut x = "hello";
  |             ------- expected due to this value
4 | x = x.len();
  |     ^^^^^^^ expected `&str`, found `usize`

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

因为第一个 let 已经推断出为们这个变量为字符串类型,但是第二次赋值时是一个 usize 类型,与预期类型不符,所以编译器就报错了。

变量遮蔽的坏处就如上面所说的,第二次定义变量时,就会在内存中在开辟一个空间来存放新的变量,因为它们本质上就是两个完全不同的变量,只是名字相同而已,然后性能上会有所损失。

常量

常量遵循如下规则:

  • 常量的值是不可变的
  • 常量可以在任何范围内声明,包括全局范围
  • 常量定义时必须指定数据类型
  • 常量命名约定是在单词之间使用全大写和下划线
#![allow(unused)]
fn main() {
const TEST_NUMBER:i32 = 123;
println!("{}",TEST_NUMBER);
}

基础数据类型

Rust 有四种主要的基础类型:整数、浮点数、布尔值和字符。

整数

直接用一张表格来说明 Rust 的整数类型:

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

其中 iu 开头分别代表有符号和无符号类型的数据。有符号类型就是说明这个整数包括负数,而无符号类型就是说明这个整数不包括负数。 其中有 isizeusize 这两个类型,它可以根据你当前运行代码的系统来自动定义整数的类型长度,如果您使用的是 64 位架构,则数据长度为 64-bit;如果您使用的是 32 位,则数据长度为 32-bit。

我们可以和使用其他类型来定义数据一样来使用它:

#![allow(unused)]
fn main() {
let x:isize = 5;
println!("{}",x);
}

如果你不知道怎么选择数据的长度,那么我们一般可以不指定整数的类型,这时它的默认类型为 i32

#![allow(unused)]
fn main() {
let x = 5;
println!("{}",x);
}

更多表示方式

当然除了上面那些常规的表示方式,Rust 还支持如下表示方式:

数据例子
十进制98_222
十六进制0xff
八进制0o77
二进制0b1111_0000
Byte (u8 only)b'A'

如果十进制数据太长的话可以用 _ 符号来分割,比如上面的例子中的 98_222,其实还是 98222,我们还可以换成我们比较能一眼读出的 9_8222

#![allow(unused)]
fn main() {
let a = 98_222;
let b = 9_8222;
println!("a: {}\nb: {}",a,b);
}

如果要直接是用其他进制的值,只需在对应进制数据前加上一个常用的标识符即可,例如:

  • 十六进制:0x
  • 八进制: 0o
  • 二进制: 0b
#![allow(unused)]
fn main() {
let a = 0xFF;
let b = 0o77;
let c = 0b1111_0000;

println!("a: {}\nb: {}\nc: {}",a,b,c);
}

当然如果你想直接输出进制数据,我们可以直接这样写:

输出大小十六进制用::#X,输出小写十六进制::#x

#![allow(unused)]
fn main() {
let a = 0xFF;
let b = 0o77;
let c = 0b1111_0000;

println!("a: {:#X}\nb: {:#o}\nc: {:#b}",a,b,c);
}

当我们想输出一个字节的数据时,我们可以用 b 标识符来表示,例如:

#![allow(unused)]
fn main() {
let a = b'A';
println!("a: {}",a);

// 输出十六进制值
println!("a HEX: {:#X}",a);
}

浮点数

Rust 支持两种浮点数类型:f32f64

f32 代表 32 位浮点数,f64 代表 64 位浮点数。

#![allow(unused)]
fn main() {
let x = 20.0;
let y:f32 = 21.0;
println!("x: {:.1}\ny: {:.1}",x,y);
}

浮点数根据 IEEE-754 标准实现。f32 类型是单精度浮点型,f64 为双精度。

布尔值

Rust 当然也和其他语言一样支持布尔类型啦。 用法:

#![allow(unused)]
fn main() {
let x = true;
// or
let y:bool = false;
println!("x: {}\ny: {}",x,y);
}

字符

#![allow(unused)]

fn main() {
let a = 'a';
let b: char = 'q'; // with explicit type annotation
let c = '😻';

println!("a: {}\nb: {}\nc: {}",a,b,c);
}

请注意,我们 char 使用单引号指定文字,而不是使用双引号的字符串文字。Rust 的char类型大小为 4 个字节,表示一个 Unicode 标量值,这意味着它可以表示的不仅仅是 ASCII。在 Rust 中,重音字母、中文、日文、韩文字符、表情符号和零宽度空格都是 char 的有效值。Unicode 标量值的范围从 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF 在内。 这个 char 只能表示单个字符,如果用它来表示多个字符的话就会发生错误

#![allow(unused)]
fn main() {
let hello = '你好';
println!("hello: {}",hello);
}
   Compiling playground v0.0.1 (/playground)
error: character literal may only contain one codepoint
 --> src/main.rs:3:13
  |
3 | let hello = '你好';
  |             ^^^^^^
  |
help: if you meant to write a `str` literal, use double quotes
  |
3 | let hello = "你好";
  |             ~~~~~~

error: could not compile `playground` due to previous error

编译器提示这是字符串,可以用双双引号来表示,例如:

#![allow(unused)]
fn main() {
let hello = "你好";
println!("hello: {}",hello);
}

复合数据类型

除了基础数据类型,Rust 还支持两种复合数据类型,如:元组(Tuple)和数组(Array)。

元组

元组是将具有多种类型的多个值组合成一个复合类型的一般方法。元组具有固定长度:一旦声明,它们的大小就不能增长或缩小。

元组里面可以包含不同类型的数据,然后用括号 () 包围,比如:

#![allow(unused)]
fn main() {
let tup = (1,2.0,true,"hello");
println!("tup: {:?}",tup);
println!("tup[0]: {}",tup.0);
println!("tup[1]: {:.1}",tup.1);
println!("tup[2]: {}",tup.2);
println!("tup[3]: {}",tup.3);
}

当然我们在定义元组时也可以指定元组中每个位置数据的类型,例如:

#![allow(unused)]
fn main() {
let tup:(i32,f32,bool,&str)=(1,2.0,true,"hello");
println!("tup: {:?}",tup);
}

我们也可以对元组进行结构,将元组中的值分别放入不同的变量中,例如:

#![allow(unused)]
fn main() {
let tup = (1,2.0,true,"hello");
let (x,y,z,w) = tup;
println!("x: {}\ny: {:.1}\nz: {}\nw: {}",x,y,z,w);
}

数组

与元组不同,数组的每个元素都必须具有相同的类型。与其他一些语言中的数组不同,Rust 中的数组具有固定长度,也就是说,一旦声明,它们的大小就不能增长或缩小。(如果需要增大或缩小可以使用 Rust 中的向量类型)

在数组中,用方括号 [] 来包围数据,用逗号 , 来分割每个数据,比如:

#![allow(unused)]
fn main() {
let arr = [1,2,3,4,5];
println!("arr: {:?}",arr);
}

我们也可以指定数组的类型和大小:

#![allow(unused)]
fn main() {
let arr:[i32;5] = [1,2,3,4,5];
println!("arr: {:?}",arr);
}

此时就必须严格按照数组的大小和类型来声明数组,比如下面这些声明都是错误的:

// ❌ 预定义的数组大小不匹配
let arr:[i32;5] = [1,2,3,4];

// ❌ 预定义的数组类型不匹配
let arr:[i32;5] = [1.0,2.0,3.0,4.0,5.0];

也可以初始化一拥有相同默认值和指定长度的数组:

#![allow(unused)]
fn main() {
// 初始化长度为 5 ,默认值为 0 的数组
let arr = [0;5];
println!("arr: {:?}",arr);
}

函数

函数定义

在 Rust 中,用 fn 关键字来定义函数。

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

下面是一个带参数的函数,在函数入参区,先定义参数名,然后在定义参数的类型:

fn my_function(name: &str){
    println!("hello,{}",name);
}
fn main(){
    my_function("world");
}

函数返回值

函数可以将值返回给调用它们的代码。我们不命名返回值,但我们必须在箭头 -> 之后声明它们的类型。在 Rust 中,函数的返回值与函数体块中的最终表达式的值同义。可以通过使用 return 关键字并指定一个值来提前从函数返回,但大多数函数会隐式返回最后一个表达式,如下面的例子:

fn my_function() -> i32{
    9
}
fn main(){
    let x=my_function();
    println!("{x}");
}

可以看到,我们的 my_function() 函数并没有使用 return 关键字来返回指定的值,这样的话 Rust 会隐式返回最后一个表达式的值。注意这边最后一个表达式的值末尾不能带 ; 符号。

函数返回多个值

如果我们想返回多个值的时候,我们可以用元组来返回,然后在调用的时候进行解构:

fn my_function() -> (i32,f32){
    (9,3.14)
}
fn main(){
    let (x,y)=my_function();
    println!("x: {}\ny: {}",x,y);
}

if 语句

if 语句中的判断条件不需要用括号包围,但是代码块需要用大括号包围。

#![allow(unused)]
fn main() {
let x = 3;

if x == 7 {
    println!("x is 7");
} else {
    println!("x is not 7");
}
}

也可以使用 else if 语句来添加判断条件

#![allow(unused)]
fn main() {
let x = 3;

if x == 7 {
    println!("x is 7");
} else if x == 8 {
    println!("x is 8");
}else {
    println!("x is not 7 or 8");
}
}

在让我们看看下面的例子:

#![allow(unused)]
fn main() {
// ❌ 错误写法
let condition = true;
let mut x = condition?3:5;
println!("x is {}",x);
}

如果熟悉其他语言的朋友就知道这是一个标准的三元表达式,是不是感觉这样写没什么问题?但是当我们执行代码时却会发生错误。

在 Rust 中,可以用 if 语句来实现类似的表达式

#![allow(unused)]
fn main() {
let condition = true;
let y = if condition { 3 } else { 5 };
println!("y is {}",y);
}

循环

Rust 中有三种循环:loop、while 和 for。 这三种循环各有各的侧重点:

  • loop:如果需要无限循环,且不需要退出循环时(当然在 loop 中可以用 breakcontinue 来退出当前循环或整个循环 ),可以使用 loop 循环。像比如端口监听,我们可以使用 loop 循环来监听端口,直到收到一个新的连接。loop 循环还可以拥有返回值,可以将它赋值给一个变量。
  • while:如果需要循环,但是当满足某个条件要退出时,可以使用 while 循环。
  • for:如果要遍历一个数组时,可以用 for 循环。

loop 语句

最基础的用法:

loop {
    println!("more and more!")
}

上面代码会不断的输出 more and more!,如果要停止输出,只能终止这个程序运行。

跳出 loop 循环

当然我们也可以在 loop 循环体里添加 if 语句,然后用 break 跳出循环。

#![allow(unused)]
fn main() {
let mut x = 0;
loop {
    if x >= 5{
        break;
    }
    println!("more and more!");
    x += 1;
}
}

上面代码只会输出 5 遍 more and more!,然后就跳出循环。

loop 循环返回值

loop 还有一特性是它可以在结束循环时,使用 break 返回一个值

#![allow(unused)]
fn main() {
let mut x = 0;
let y = loop {
    if x >= 5{
        break "kill";
    }
    println!("more and more!");
    x += 1;
};
println!("{y}");
}

loop 循环嵌套标签

当我们有多个 loop 循环嵌套时,我们可以为 loop 循环定义一个标签,然后使用 break 来跳出指定标签下的循环。

fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {count}");
        let mut remaining = 10;

        'remain:loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {count}");
}

标签的命名以符号 ' 开头。

while 语句

在上面的 loop 循环中,我们通过在循环体中编写 if 语句来判断是否跳出循环体,如果你不想编写 if 语句,那么可以考虑使用 while 循环。

#![allow(unused)]
fn main() {
let mut x = 0;
while x < 5 {
    println!("hello");
    x+=1;
}
}

while 循环也支持嵌套写:

#![allow(unused)]
fn main() {
let mut x = 0;
while x < 2 {
    println!("hello");
    x += 1;
    let mut y = 0;
    while y < 3{
        println!("world!");
        y += 1;
    }
}
}

while 循环也可以使用循环标签,通过 break 跳出指定循环:

#![allow(unused)]
fn main() {
let mut x = 0;
'is_x:while x < 2 {
    println!("hello");
    x += 1;
    let mut y = 0;
    'is_y: while y < 3{
        println!("world!");
        if y == 1{
            break 'is_x;
        }
        y += 1;
    }
}
}

for 语句

如果要遍历一个数组或字符串时,可以用 for 循环。

#![allow(unused)]
fn main() {
let arr = [1,3,5,7,9];
for i in arr {
    println!("curr is {}",i);
}
}

或者这样

#![allow(unused)]
fn main() {
for i in 1..5 {
    println!("curr is {}",i);
}
}

所有权

rust 用来管理内存的一套机制,称为所有权(ownership)。 其他语言,例如 Java 管理内存的方式是垃圾回收(garbage collection),rust 选择了另一种方式,即所有权。 所有权的好处是,rust 编译器可以在编译时就发现内存错误,而不是在运行时才发现。 所有权的缺点是,rust 程序员需要手动管理内存,这可能会增加开发成本。

The stack stores values in the order it gets them and removes the values in the opposite order. This is referred to as last in, first out

所有权规则

  • 在 Rust 中每个值都有一个所有权(owner)。
  • 一个值在任意时刻有且只有一个所有权。
  • 当所有权被移出一个作用域时,这个值将被丢弃。

变量作用域

#![allow(unused)]
fn main() {
{                   // s 在这里无效,它还没有被声明
    let s = "hello"; // s 从现在开始有效
    // do stuff with s
} // s 的作用域到里结束,s 不再有效
}

移动

因为 Rust 在移动一个值时,前一个变量会失效,所以这个常规的浅拷贝不一样 trpl04-04jGV7ap

克隆

直接调用 clone 方法,能够复制堆上的数据。

复制

以下是一些类型实现了 Copy trait:

  • 所有整数类型,例如 u32.
  • 布尔类型, bool, 有值 true和 false.
  • 所有浮点类型,例如 f64.
  • 字符类型, char.
  • 元组,如果它们只包含也实现的类型 Copy. 例如, (i32, i32)工具 Copy, 但 (i32, String)才不是。

可以省略 clone,因为调用了也不会做任何事情

#![allow(unused)]
fn main() {
let x = 5;
let y = x; //和 let y = x.clone(); 等效
println!("x = {}, y = {}", x, y);
}

借用

“这种通过引用传递参数给函数的方法也被称为借用 (borrowing)。在现实生活中,假如一个人拥有某件东西,你可以从他那里把东西借过来。但是当你使用完毕时,就必须将东西还回去。” 比如当我们想写一个获取一个计算字符串长度的函数时,如果我们这样写的话

fn main() {
   let s = String::from("hello");
   let l = get_len(s); // 变量 s 这里将所有权移交给了 get_len 函数

   println!("s:{},len:{}",s,l) // 所以这里变量 s 是无效的
}

fn get_len(s:String) -> usize {
   s.len()
}
error[E0382]: borrow of moved value: `s`
--> src/main.rs:5:28
 |
2 |     let s = String::from("hello");
 |         - move occurs because `s` has type `String`, which does not implement the `Copy` trait
3 |     let l = get_len(s);
 |                     - value moved here
4 |
5 |     println!("s:{},len:{}",s,l)
 |                            ^ value borrowed here after move

Struct 结构体

定义一个结构体

使用 struct 关键字定义一个结构体

#![allow(unused)]
fn main() {
struct User{
    name: String,
    age:    u8,
}
}

使用 struct

struct User{
    name: String,
    age:    u8,
}

fn main(){
    let user = User{
        name: String::from("greycode"),
        age:20
    };
    println!("name:{},age:{}",user.name,user.age);
}

可变结构体

使用 mut 关键字定义一个可变结构体

struct User{
    name: String,
    age:    u8,
}

fn main(){
    let mut user = User{
        name: String::from("greycode"),
        age:20
    };
    println!("name:{},age:{}",user.name,user.age);
    user.age = 30;
    println!("name:{},age:{}",user.name,user.age);
}

结构体初始化简写

当变量名和结构体中的 field 字段名称一致时,可以使用简写

struct User{
    name: String,
    age:    u8,
}

fn main(){
    let name = String::from("greycode");
    let age = 22;
    let user = build_user(name, age);
    println!("name:{},age:{}",user.name,user.age);
}

fn build_user(name:String,age:u8) -> User{
    User {
        name, // 使用简写赋值
        age, // 使用简写赋值
    }
}

使用结构 Update 语法从其他实例创建实例

当我们要创建两个相同类型的结构体实例但是有小部分字段值不同的实例时,通常我们这样创建:

struct User{
    name: String,
    age:    u8,
    sex:    String,
    email: String,
}

fn main(){
    let user1 = User{
        name:String::from("greycode"),
        age:20,
        sex:String::from("man"),
        email:String::from("ssss@gmail.com"),
    };
    let user2 = User{
        name:String::from("jack"),
        age:user1.age, // 因为 age 字段是 u8 类型,实现了 Copy trait,所以这里是复制操作,user1.age 还可以访问
        sex:user1.sex, // 这里是移动操作,所以 user1.sex 所有权移交到这里,user.sex 失效
        email:user1.email, // 这里是移动操作,所以 user1.email 所有权移交到这里,user.email 失效
    };
    // 到这里时,user1 实例里只有 user1.name 和 user1.age 有效
}

会出现很多从 user1 实例中取值的代码 这时候我们可以直接使用下面这种写法:

struct User{
    name: String,
    age:    u8,
    sex:    String,
    email: String,
}

fn main(){
    let user1 = User{
        name:String::from("greycode"),
        age:20,
        sex:String::from("man"),
        email:String::from("ssss@gmail.com"),
    };
    let user2 = User{
        name: String::from("greycode"),
        ..user1 // 移交 user1.name 字段以外所有 user1 字段的所有权
    };
}

元组结构体

struct Color(i32,i32,i32);
struct Point(i32,i32,i32);

fn main(){
    let black = Color(0,0,0);
    let origin = Point(0,0,0);
}

类单元结构体

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

打印结构体

在开发时,可以在结构体上加上 #[derive(Debug)] ,然后使用 :? 来打印一个结构体

#[derive(Debug)]
struct User{
    name: String,
    age:    u8,
}

fn main(){
    let user = User{
        name: String::from("greycode"),
        age:20
    };
    println!("{:?}",user)
}

使用impl定义方法

可以使用 impl 关键字来定义结构体的方法,同时可以同时定义多个 impl

struct User {
    name: String,
    age: u32,
}

impl User {
    fn new(name: String, age: u32) -> User {
        User { name, age }
    }
}
impl User {
    fn say_hello(&self) {
        println!("Hello, my name is {} and I am {} years old.", self.name, self.age);
    }
}
    
fn main() {
    // 当方法返回的是结构体本身时,要用 :: 来调用
    let user = User::new(String::from("John"), 30);
    println!("{} is {} years old", user.name, user.age);

    user.say_hello();
}

enum 和 match

定义一个 enum 枚举

常规枚举

#![allow(unused)]
fn main() {
enum IpAddrKind {
    V4,
    V6,
}
}

带数据的枚举

  • 枚举的每个成员可以有不同的数据类型
#![allow(unused)]
fn main() {
enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}


enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

}
  • 也可以带srtuct结构体
#![allow(unused)]
fn main() {
struct Info {
    addr: String,
    desc: String,
}
enum IpAddr {
    V4(Info),
    V6(Info),
}
}

使用枚举

使用枚举值

enum IpAddr {
    V4,
    V6,
}
    
fn main() {
    let v4 = IpAddr::V4;
}

打印枚举和打印结构体一样,要在枚举上加上 #[dervice(Debug)]

#[derive(Debug)]
enum IpAddr {
    V4,
    V6,
}
    
fn main() {
    let v4 = IpAddr::V4;
    let v6 = IpAddr::V6;
    println!("{:?}", v4);
    println!("{:?}", v6);
}

Option枚举

这是定义在 rust 标准库中的枚举,用于处理可能不存在的值

源码:

#![allow(unused)]
fn main() {
pub enum Option<T> {
    None,
    Some(T),
}
}

使用:

fn main() {
    let user = Option::Some("i am a user");
    match user {
        Some(user) => println!("{}", user),
        None => println!("none"),
    }
}

match

使用 Result 来处理异常

在 Rust 中,可以使用 Result 这个 enum 来处理异常

源码如下:

#![allow(unused)]
fn main() {
pub enum Result<T, E> {
    /// Contains the success value
    #[lang = "Ok"]
    #[stable(feature = "rust1", since = "1.0.0")]
    Ok(#[stable(feature = "rust1", since = "1.0.0")] T),

    /// Contains the error value
    #[lang = "Err"]
    #[stable(feature = "rust1", since = "1.0.0")]
    Err(#[stable(feature = "rust1", since = "1.0.0")] E),
}
}

它是一个泛型,有两个类型参数,第一个是成功的类型 T,第二个是失败的类型 E

使用案例

我们可以这样使用它,例如我定义了一个函数,返回 Result

#![allow(unused)]
fn main() {
fn test(flag: bool) -> Result<String,io::Error> {
    if flag {
        let s = String::from("greycode");
        return Ok(s);
    }else{
        return Err(io::Error::new(io::ErrorKind::Other, "customize error"));
    }
}
}

当函数参数传入 true 时,就返回 OK,否则就返回 Err

使用 match 来处理

可以使用 match 匹配 Result 的两种情况

fn main(){
    let s = match test(false) {
        Ok(s) => s,
        Err(e) => panic!("error: {}", e),
    };
    println!("{}", s);
}

使用 unwrap 来处理

可以使用 unwrap 来处理 Result,如果是 Ok,就返回 Ok 的值,如果是 Errunwrap 会自动帮我们调用 panic

fn main(){
    let s = test(false).unwrap();
    println!("{}", s);
}

完整代码

use std::io;

fn main(){
    let s = test(true).unwrap();
    println!("{}", s);

    // let s = match test(true) {
    //     Ok(s) => s,
    //     Err(e) => panic!("error: {}", e),
    // };
    // println!("{}", s);
}

fn test(flag: bool) -> Result<String,io::Error> {
    if flag {
        let s = String::from("greycode");
        return Ok(s);
    }else{
        return Err(io::Error::new(io::ErrorKind::Other, "customize error"));
    }
}

符号 ? 的作用

?unwrap() 类似,可以简化 match 的写法,它的作用是,如果是 Ok,就返回 Ok 的值,如果是 Err,就返回 Err 的值,不会调用 panic

只能用于返回值为 Result 的函数

简化前:

#![allow(unused)]
fn main() {
use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}
}

简化后:

#![allow(unused)]
fn main() {
use std::io;
use std::io::Read;
use std::fs::File;

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

智能指针

Box<T>

使用场景:

  • 当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候
  • 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
  • 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候

没有使用 Box<T>

#![allow(unused)]
fn main() {
    let b = 9;
    println!("b = {}", b);
}

使用 Box<T> 将数据存到堆上

#![allow(unused)]
fn main() {
    let b = Box::new(9);
    println!("b = {}", b);
}

Rc<T>

  • 引用计数智能指针
  • 允许相同数据有多个所有者
  • 数据在最后一个所有者离开作用域时才会被清理
    
use std::rc::Rc;

fn main(){
    let b = Rc::new(9);
    let a = Rc::clone(&b);
    println!("a = {}, b = {}",a,b);
}

查看引用到数量

每次执行 Rc::clone() 方法时,都会增加引用计数。可以使用 Rc::strong_count() 方法来查看引用到数量。

use std::rc::Rc;

fn main(){
    let b = Rc::new(9);
    let _a = Rc::clone(&b);
    let _c = Rc::clone(&b);
    println!("count = {}", Rc::strong_count(&b));
}