说明

本项目为学习rust的程序例子集合,主要来源自《Rust程序设计语言》

参考:https://kaisery.github.io/trpl-zh-cn/

程序1

hello-world

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

猜数版本1

获取用户输入

use std::io;

fn main() {
    println!("Guess the number!");

    println!("Please input your guess.");

    let mut guess = String::new();

    // 获取用户输入
    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {}", guess);
}

猜数版本2

生成随机数

use std::io;
use rand::Rng; // 使用rand包的Rng接口定义

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..101);  // 生成

    println!("The secret number is: {}", secret_number);

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {}", guess);
}

猜数版本3

处理比较输入

use rand::Rng;
use std::cmp::Ordering;  // 要用到标准包里面的比较接口
use std::io;

fn main() {
    println!("猜数!");

    let secret_number = rand::thread_rng().gen_range(1..101);

    // println!("神秘数字是: {}", secret_number.to_string());

    println!("请输入你猜测的数字:");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("读取数据失败");

    println!("你猜测的数是: {}", guess);

    // 针对字符串进行比较
    match guess.cmp(&secret_number.to_string()){
        Ordering::Less => println!("太小了!"),
        Ordering::Greater => println!("太大了!"),
        Ordering::Equal => println!("你赢了!"),
    }
}

猜数版本4

转换输入为数字

use rand::Rng;
use std::cmp::Ordering;  // 要用到标准包里面的比较接口
use std::io;

fn main() {
    println!("猜数!");

    let secret_number = rand::thread_rng().gen_range(1..101);

    // println!("神秘数字是: {}", secret_number.to_string());

    loop {
        println!("请输入你猜测的数字:");

        let mut guess = String::new();

        io::stdin().read_line(& mut guess).expect("不能读取行!");

        let guess: u32 = guess.trim().parse().expect("请输入数字!");

        println!("你输入的是: {}", guess);

        match guess.cmp(&secret_number){
            Ordering::Less => println!("太小了"),
            Ordering::Greater => println!("太大了"),
            Ordering::Equal => {
                println!("你赢了");
                break;
            }

        }
    }
}

猜数版本5

处理程序崩溃情况

use rand::Rng;
use std::cmp::Ordering;  // 要用到标准包里面的比较接口
use std::io;

fn main() {
    println!("猜数!");

    let secret_number = rand::thread_rng().gen_range(1..101);

    // println!("神秘数字是: {}", secret_number.to_string());

    loop {
        println!("请输入你猜测的数字:");

        let mut guess = String::new();

        io::stdin().read_line(& mut guess).expect("不能读取行!");

        // 是一个Result,有 OK 和 Err 两种情况
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,         // 可以转化为数字
            Err(_) => continue,     // 进入下一次循环
        };

        println!("你输入的是: {}", guess);

        match guess.cmp(&secret_number){
            Ordering::Less => println!("你输入的是: {} 太小了", guess),
            Ordering::Greater => println!("你输入的是: {} 太大了", guess),
            Ordering::Equal => {
                println!("你输入的是: {} 你赢了", guess);
                break; // 退出循环
            }

        }
    }
}

变量与可变性

不可变变量

  1. 变量默认是不可改变的
  2. 默认 let 声明的变量为不可变的。
fn main() {
    let x = 5; // 声明变量,默认不可变
    println!("The value of x is: {}", x);
    x = 6; // 因为 x 是不可变变量,所以此处会报错
    println!("The value of x is: {}", x);
}

可变变量

  1. 使用 mut 关键字声明为可变的。
fn main() {
    let mut x = 5; // 使用 mut 声明可变变量
    println!("The value of x is: {}", x);
    x = 6; // 因为x是可变变量,所以不会报错。
    println!("The value of x is: {}", x);
}

常量

  1. 使用 const 关键字声明常量。
  2. 不允许对常量 使用 mut。
  3. 常量可以在任何作用域中声明,包括全局作用域。
  4. 常量只能被设置为常量表达式,而不能是只能在运行时计算出的值。

fn main() {
    const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
}

隐藏(Shadowing)

  1. 可以定义一个与之前变量同名的新变量。
fn main() {
    let x = 5;  // 第一次声明的变量 x

    let x = x + 1;  // 第二次声明,隐藏第第一个声明的变量 x

    {
        let x = x * 2;  // 第三次声明在本作用域内隐藏第二次声明的变量x
        println!("The value of x in the inner scope is: {}", x);
    }

    println!("The value of x is: {}", x);  // 使用第二次声明的变量 x
}

隐藏版本2

  1. 使用 let 时,实际上创建了一个新变量,我们可以 改变值的类型,并且复用这个名字。
fn main() {
    let spaces = "   ";  // string类型
    let spaces = spaces.len();  // 隐藏并更改为 u32 类型
}

对于 使用 mut 的可变变量, 隐藏上一个变量时,不可改变类型

fn main() {
    let mut spaces = "   "; // 可变的,string类型变量
    spaces = spaces.len(); // 转化为u32类型,会报错。
}

数据类型

Rust 是 静态类型(statically typed)语言,也就是说在编译时就必须知道所有变量的类型。

根据值及其使用方式,编译器通常可以推断出我们想要用的类型。当多种类型均有可能时,必须增加类型注解,例如:

fn main() {
    let guess: u32 = "42".parse().expect("Not a number!");  // 注解guess的值为 u32 的类型。
}

标量类型

标量(scalar)类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。

整型

整数 是一个没有小数部分的数字。

Rust 中的整型:

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

isizeusize 类型依赖运行程序的计算机架构:64 位架构上它们是 64 位的, 32 位架构上它们是 32 位的。

Rust 中的整型字面值:

数字字面值例子
Decimal (十进制)98_222
Hex (十六进制)0xff
Octal (八进制)0o77
Binary (二进制)0b1111_0000
Byte (单字节字符)(仅限于u8)b'A'
  1. 允许使用 _ 做为分隔符以方便读数,例如1_000,它的值与你指定的 1000 相同。
  2. 数字类型默认是 i32
  3. isizeusize 主要作为某些集合的索引。

浮点型

  1. 浮点数类型是 f32f64,分别占 32 位和 64 位。
  2. 默认类型是 f64,因为在现代 CPU 中,它与 f32 速度几乎一样,不过精度更高。
  3. 所有的浮点型都是有符号的。
fn main() {
    let x = 2.0; // f64

    let y: f32 = 3.0; // f32
}
  1. 浮点数采用 IEEE-754 标准表示。
  2. f32 是单精度浮点数,f64 是双精度浮点数。

数值运算

  1. 支持基本数学运算:加法、减法、乘法、除法和取余。
  2. 整数除法会向下舍入到最接近的整数。
fn main() {
    // 加法
    let sum = 5 + 10;

    // 减法
    let difference = 95.5 - 4.3;

    // 乘法
    let product = 4 * 30;

    // 除法
    let quotient = 56.7 / 32.2;  //  浮点数相除
    let floored = 2 / 3; // 结果为 0, 向下舍入。

    // 取余
    let remainder = 43 % 5;
}

布尔型

  1. 布尔类型使用 bool 表示。
  2. 有两个可能的值:truefalse
fn main() {
    let t = true;

    let f: bool = false; // 显式指定类型注解
}

字符类型

  1. char 类型是语言中最原生的字母类型.
fn main() {
    let c = 'z';
    let z = 'ℤ';
    let heart_eyed_cat = '😻';
}
  1. 用单引号声明 char 字面量,使用双引号声明字符串字面量。
  2. char 类型的大小为四个字节(four bytes),并代表了一个 Unicode 标量值(Unicode Scalar Value),这意味着它可以比 ASCII 表示更多内容。
  3. 在 Rust 中,拼音字母(Accented letters),中文、日文、韩文等字符,emoji(绘文字)以及零长度的空白字符都是有效的 char 值。
  4. Unicode 标量值包含从 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 char 并不符合。

复合类型

复合类型(Compound types)可以将多个值组合成一个类型。

Rust 有两个原生的复合类型:元组(tuple)和 数组(array)。

元组类型

将多个其他类型的值组合进一个复合类型的主要方式。

  1. 长度固定:一旦声明,其长度不会增大或缩小。
  2. 使用包含在圆括号中的逗号分隔的值列表来创建一个元组。
  3. 元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的。
fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
}

从元组中获取单个值,可以使用模式匹配(pattern matching)来解构(destructure)元组值, 例如:

fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;  // 解构

    println!("The value of y is: {}", y);
}
fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);

    let five_hundred = x.0; // 也可以用 .索引 来访问值

    let six_point_four = x.1; // 也可以用 .索引 来访问值

    let one = x.2;
}

没有任何值的元组 () 是一种特殊的类型,只有一个值,也写成 () 。该类型被称为 单元类型(unit type),而该值被称为 单元值(unit value)。如果表达式不返回任何其他值,则会隐式返回单元值。

数组类型

  1. 数组(array)。与元组不同,数组中的每个元素的类型必须相同。
  2. Rust中的数组长度是固定的。
  3. 将数组的值写成在方括号内,用逗号分隔.
fn main() {
    let a = [1, 2, 3, 4, 5];
}
  1. 数组在栈(stack)而不是在堆(heap)上为数据分配空间。
  2. 数组并不如 vector 类型灵活。【标准库提供的一个 允许 增长和缩小长度的类似数组的集合类型】
fn main() {
    // 当你确定元素个数不会改变时,数组会更有用。比如12个月份
    let months = ["January", "February", "March", "April", "May", "June", "July",
              "August", "September", "October", "November", "December"];
}

在方括号中包含每个元素的类型,后跟分号,再后跟数组元素的数量 -- 声明数组的方式2

fn main() {
    let a: [i32; 5] = [1, 2, 3, 4, 5];  // 
}

在方括号中指定初始值加分号再加元素个数的方式来创建一个每个元素都为相同值的数组 -- 声明数组的方式3

fn main() {
    let a = [3; 5]; // 这种写法与 let a = [3, 3, 3, 3, 3]; 效果相同,但更简洁。
}
fn main() {
    let a = [1, 2, 3, 4, 5];

    let first = a[0];  // 索引方式访问数组
    let second = a[1];
}

越界访问数组时会产生 panic 【这是 Rust 术语,它用于程序因为错误而退出的情况】

use std::io;

fn main() {
    let a = [1, 2, 3, 4, 5];

    println!("Please enter an array index.");

    let mut index = String::new();

    io::stdin()
        .read_line(&mut index)
        .expect("Failed to read line");

    let index: usize = index
        .trim()
        .parse()
        .expect("Index entered was not a number");

    let element = a[index];

    println!(
        "The value of the element at index {} is: {}",
        index, element
    );
}

输出如下:

thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src\main.rs:149:19
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\hello-rust.exe` (exit code: 101)

函数

  1. main 函数,它是很多程序的入口点。
  2. fn 关键字,它用来声明新函数。
  3. Rust 代码中的函数和变量名使用 snake case 规范风格。所有字母都是小写并使用下划线分隔单词。
  4. Rust 不关心函数定义于何处,在使用函数之前或之后,只要定义了就行
fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

参数

  1. 可以定义为拥有 参数(parameters)的函数,参数是特殊变量,是函数签名的一部分。
  2. 在函数签名中,必须 声明每个参数的类型。【要求在函数定义中提供类型注解,意味着编译器再也不需要你在代码的其他地方注明类型来指出你的意图。】
  3. 当定义多个参数时,使用逗号分隔。
fn main() {
    another_function(5);
}

fn another_function(x: i32) {  // 参数时函数签名的一部分
    println!("The value of x is: {}", x);
}
fn main() {
    print_labeled_measurement(5, 'h');
}

fn print_labeled_measurement(value: i32, unit_label: char) {  // 必须声明每个参数的类型
    println!("The measurement is: {}{}", value, unit_label);
}

语句和表达式 【函数体】

函数体由一系列的语句和一个可选的结尾表达式构成。

  • 语句(Statements)是执行一些操作但不返回值的指令。
  • 表达式(Expressions)计算并产生一个值。

Rust 是一门基于 表达式(expression-based)的语言。 重点参考:https://kaisery.github.io/trpl-zh-cn/ch03-03-how-functions-work.html#%E8%AF%AD%E5%8F%A5%E5%92%8C%E8%A1%A8%E8%BE%BE%E5%BC%8F

fn main() {
    let y = 6;  // 这是一个语句,不返回值
}
fn main() {
    let x = (let y = 6);  // let y = 6 是一个语句,不返回值,所以将其结果赋值给 x 产生错误。
}
fn main() {
    // 用大括号创建的一个新的块作用域也是一个表达式
    let y = {
        let x = 3;
        x + 1  // 当结尾有 分号 “;” 时就表示是一个语句,而语句不会返回值,所以这里 表示 x + 1 为表达式,会作为这块作用域的返回值
    };

    println!("The value of y is: {}", y);
}

具有返回值的函数

函数可以向调用它的代码返回值。在函数括号后面使用箭头(->)并在剪头后声明它的类型。

在 Rust 中,函数的返回值等同于函数体最后一个表达式的值。

使用 return 关键字和指定值,可从函数中提前返回;但 大部分函数隐式的返回最后的表达式

fn five() -> i32 {  // 这里声明了返回值的类型。
    5  // 最后的表达式作为返回值,即便只是一个数字
}

fn main() {
    let x = five();

    println!("The value of x is: {}", x);
}
fn main() {
    let x = plus_one(5);

    println!("The value of x is: {}", x);
}

fn plus_one(x: i32) -> i32 {  // 同时声明参数类型和返回值类型
    x + 1 // 最后表达式作为返回值 , 如果此处加上分号“;”则会变成语句,则编译会报错。
}

注释

注释对任何程序都不可缺少,同样 Rust 支持几种不同的注释方式。

  • 普通注释,其内容将被编译器忽略掉:
    • // 单行注释,注释内容直到行尾。
    • /* 块注释,注释内容一直到结束分隔符。 */
  • 文档注释,其内容将被解析成 HTML 帮助文档:
    • /// 为接下来的项生成帮助文档。
    • //! 为注释所属于的项(译注:如 crate、模块或函数)生成帮助文档。
fn main() {
    // 这是行注释的例子
    // 注意有两个斜线在本行的开头
    // 在这里面的所有内容都不会被编译器读取

    // println!("Hello, world!");

    // 请运行一下,你看到结果了吗?现在请将上述语句的两条斜线删掉,并重新运行。

    /*
     * 这是另外一种注释——块注释。一般而言,行注释是推荐的注释格式,
     * 不过块注释在临时注释大块代码特别有用。/* 块注释可以 /* 嵌套, */ */
     * 所以只需很少按键就可注释掉这些 main() 函数中的行。/*/*/* 自己试试!*/*/*/
     */

    /*
    注意,上面的例子中纵向都有 `*`,这只是一种风格,实际上这并不是必须的。
    */

    // 观察块注释是如何简单地对表达式进行修改的,行注释则不能这样。
    // 删除注释分隔符将会改变结果。
    let x = 5 + /* 90 + */ 5;
    println!("Is `x` 10 or 100? x = {}", x);
}

参见:

文档注释

控制流

根据条件是否为真来决定是否执行某些代码,以及根据条件是否为真来重复运行一段代码的能力是大部分编程语言的基本组成部分。

if 表达式

if 表达式允许根据条件执行不同的代码分支。

fn main() {
    let number = 3;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}

代码中的条件 必须bool 值。如果条件不是 bool 值,我们将得到一个错误。

fn main() {
    let number = 3;

    if number {  // 必须是bool 值, 这里会报错。
        println!("number was three");
    }
}

不像 RubyJavaScript 这样的语言,Rust 并 不会尝试自动地将非布尔值转换为布尔值。 必须总是显式地使用布尔值作为 if 的条件。

使用 else if 处理多重条件

fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

只会执行第一个条件为真的代码块,并且一旦它找到一个以后,甚至都不会检查剩下的条件了。

在 let 语句中使用 if

因为 if 是一个表达式,我们可以在 let 语句的右侧使用它

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };  // 代码块的值是其最后一个表达式的值,而数字本身就是一个表达式

    println!("The value of number is: {}", number);
}
fn main() {
    let condition = true;

    let number = if condition { 5 } else { "six" };  // 如果它们的类型不匹配,则会出现一个错误:

    println!("The value of number is: {}", number);
}

if 代码块中的表达式返回一个整数,而 else 代码块中的表达式返回一个字符串。这不可行,因为变量必须只有一个类型。Rust 需要在编译时就确切的知道 number 变量的类型,这样它就可以在编译时验证在每处使用的 number 变量的类型是有效的。如果number的类型仅在运行时确定,则 Rust 无法做到这一点;且编译器必须跟踪每一个变量的多种假设类型,那么它就会变得更加复杂,对代码的保证也会减少。

使用循环重复执行

Rust 有三种循环:loopwhilefor

loop 循环

loop 关键字告诉 Rust 一遍又一遍地执行一段代码直到你明确要求停止。

fn main() {
    loop {
        println!("again!");
    }
}
  1. break 关键字来告诉程序何时停止循环.
  2. continue 关键字告诉程序跳过这个循环迭代中的任何剩余代码,并转到下一个迭代。

如果存在嵌套循环,breakcontinue 应用于此时最内层的循环。你可以选择在一个循环上指定一个 循环标签(loop label),然后将标签与 breakcontinue 一起使用,使这些关键字应用于已标记的循环而不是最内层的循环。

fn main() {
    let mut count = 0;
    'counting_up: loop {  // 定义一个循环标签
        println!("count = {}", count);
        let mut remaining = 10;

        loop {
            println!("remaining = {}", remaining);
            if remaining == 9 {
                break;  // 退出内层循环
            }
            if count == 2 {
                break 'counting_up;  // 退出 循环标签 处的外层循环
            }
            remaining -= 1;
        }

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

从循环返回值

将返回值加入你用来停止循环的 break 表达式,它会被停止的循环返回.

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;  // 会结束循环并将 结果 返回。
        }
    };

    println!("The result is {}", result);
}

while 条件循环

当条件为真就执行,否则退出循环。

这种结构消除了很多使用 loopifelsebreak 时所必须的嵌套,这样更加清晰。

fn main() {
    let mut number = 3;

    while number != 0 { // 当条件不为 真 时,则退出。
        println!("{}!", number);

        number -= 1;
    }

    println!("LIFTOFF!!!");
}

for 循环

使用 for 循环来对一个 集合 的每个元素执行一些代码。

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("the value is: {}", element);
    }
}

使用 for 循环来倒计时的例子,它还使用了一个方法,rev,用来反转 range:

fn main() {
    for number in (1..4).rev() {
        println!("{}!", number);
    }
    println!("LIFTOFF!!!");
}

结构体

struct,或者 structure,是一个自定义数据类型,允许你包装和命名多个相关的值,从而形成一个有意义的组合。

  1. 结构体需要命名各部分数据以便能清楚的表明其值的意义。
  2. 不需要依赖顺序来指定或访问实例中的值。
  3. 实例中字段的顺序不需要和它们在结构体中声明的顺序一致。
  4. 从结构体中获取某个特定的值,可以使用点号;如果结构体的实例是可变的,我们可以使用点号并为对应的字段赋值。【赋值时,注意整个实例必须是可变的,Rust 不允许只将某个字段标记为可变】
  5. 结构体可以使用 字段初始化简写语法
  6. 可以通过 结构体更新语法(struct update syntax)实现。使用旧实例的大部分值但改变其部分值来创建一个新的结构体实例.
// 定义结构体
struct User {
    active: bool,   // 定义字段名 和 类型 
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    
    // 实例化结构体
    // 实例中字段的顺序不需要和它们在结构体中声明的顺序一致。
    let mut user1 = User { // 注意整个实例必须是可变的;Rust 并不允许只将某个字段标记为可变。
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    // 如果结构体的实例是可变的,我们可以使用点号并为对应的字段赋值。
    user1.email = String::from("anotheremail@example.com");
}
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn build_user(email: String, username: String) -> User {
    //  字段初始化简写语法
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

fn main() {
    let user1 = build_user(
        String::from("someone@example.com"),
        String::from("someusername123"),
    );
}

从其他实例创建实例

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    // --snip--

    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    // 使用旧实例的大部分值但改变其部分值来创建一个新的结构体实例
    let user2 = User {
        active: user1.active,
        username: user1.username,
        email: String::from("another@example.com"),
        sign_in_count: user1.sign_in_count,
    };

    // .. 语法指定了剩余未显式设置值的字段应有与给定实例对应字段相同的值。
    let user3 = User {
        email: String::from("another@example.com"),
        ..user1
    };
}

使用未命名字段的元组结构体来创建不同的类型

可以定义与元组类似的结构体,称为 元组结构体(tuple structs)。元组结构体有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。

struct Color(i32, i32, i32);  // 元组结构体
struct Point(i32, i32, i32);

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

没有任何字段的类单元结构体

可以定义一个没有任何字段的结构体!它们被称为 类单元结构体(unit-like structs)因为它们类似于 (),即 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型中存储数据的时候发挥作用。

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

示例程序

版本1 调用函数

获取以像素为单位的长方形的宽度和高度,并计算出长方形的面积。

fn main() {
    let width1 = 30;
    let height1 = 50;

    println!(
        "The area of the rectangle is {} square pixels.",
        area(width1, height1)
    );
}

fn area(width: u32, height: u32) -> u32 {
    width * height
}

版本2 使用元组重构

fn main() {
    let rect1 = (30, 50);

    println!(
        "The area of the rectangle is {} square pixels.",
        area(rect1)
    );
}

// 元组并没有给出元素的名称,所以计算变得更费解了,因为不得不使用索引来获取元组的每一部分:
fn area(dimensions: (u32, u32)) -> u32 {
    dimensions.0 * dimensions.1
}

使用结构体重构

使用结构体为数据命名来为其赋予意义。

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

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

    println!(
        "The area of the rectangle is {} square pixels.",
        area(&rect1)
    );
}

// 明宽高是相互联系的,并为这些值提供了描述性的名称而不是使用元组的索引值 0 和 1 。结构体胜在更清晰明了。
fn area(rectangle: &Rectangle) -> u32 {
    rectangle.width * rectangle.height
}

通过派生 trait 增加实用功能

// 加上外部属性, 使其可以打印
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

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

    println!("rect1 is {}", rect1);
}

输出如下:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished dev [unoptimized + debuginfo] target(s) in 0.48s
     Running `target/debug/rectangles`
rect1 is Rectangle {
    width: 30,
    height: 50,
}

使用 Debug 格式打印数值

另一种使用 Debug 格式打印数值的方法是使用 dbg! 宏。dbg! 宏接收一个表达式的所有权,打印出代码中调用 dbg! 宏时所在的文件和行号,以及该表达式的结果值,并返回该值的所有权。

注意:调用 dbg! 宏会打印到标准错误控制台流(stderr),与 println! 不同,后者会打印到标准输出控制台流(stdout)。

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

fn main() {
    let scale = 2;
    let rect1 = Rectangle {
        width: dbg!(30 * scale),
        height: 50,
    };

    dbg!(&rect1);
}

输出如下:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished dev [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/rectangles`
[src/main.rs:10] 30 * scale = 60
[src/main.rs:14] &rect1 = Rectangle {
    width: 60,
    height: 50,
}

解构方法语法

方法(method)与函数类似:它们使用 fn 关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文),并且它们第一个参数总是 self,它代表调用该方法的结构体实例。

定义方法

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

// impl 是 implementation 的缩写
impl Rectangle {
    // 定义方法
    // self 代表结构体实例本身
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

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

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}

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

impl Rectangle {
    // 我们可以在 Rectangle 上定义一个方法,并命名为 width:
    fn width(&self) -> bool {
        self.width > 0
    }
}

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

    // 在 main 中,
    // 当我们在 rect1.width 后面加上括号时。Rust 知道我们指的是方法 width。
    // 当我们不使用圆括号时,Rust 知道我们指的是字段 width。
    if rect1.width() {
        println!("The rectangle has a nonzero width; it is {}", rect1.width);
    }
}

带有更多参数的方法

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

// 所有在 impl 块中定义的函数被称为 关联函数(associated functions),
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    let rect2 = Rectangle {
        width: 10,
        height: 40,
    };
    let rect3 = Rectangle {
        width: 60,
        height: 45,
    };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}

关联函数

所有在 impl 块中定义的函数被称为 关联函数(associated functions),因为它们与 impl 后面命名的类型相关。

我们可以定义不以 self 为第一参数的关联函数(因此不是方法),因为它们并不作用于一个结构体的实例。

String 类型上定义的 String::from 函数 就是这样的一个关联函数

不是方法的关联函数经常被用作返回一个结构体新实例的构造函数:

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

impl Rectangle {
    // 非方法的关联函数
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

fn main() {
    let sq = Rectangle::square(3);
}

多个 impl 块

每个结构体都允许拥有多个 impl 块。

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

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

// impl 块2
impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    let rect2 = Rectangle {
        width: 10,
        height: 40,
    };
    let rect3 = Rectangle {
        width: 60,
        height: 45,
    };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}