Rust入门 - Part 1 - 通用编程概念

1、变量 & 可变性

Rust 代码中的函数和变量名使用下划线命名法(*snake case*,直译为蛇形命名法)规范风格。

1.1、创建一个不可变的变量

当变量不可变时,这意味着一旦一个值绑定到一个变量名后,就不能更改该值了。

let x = 5; // 不可变
x = 6; // 这里会报错

1.2、创建一个可变的变量

let mut x = 5;
x = 6; // 这里可以完成赋值

1.3、常量

Rust 常量的命名约定是全部字母都使用大写,并使用下划线分隔单词。

与不可变变量类似,常量(*constant*)是绑定到一个常量名且不允许更改的值。常量不允许使用 mut。常量使用 const 关键字而不是 let 关键字来声明,并且值的类型必须注明。

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

1.4、遮蔽

可以声明和前面变量具有相同名称的新变量。第一个变量被第二个变量 遮蔽(*shadow*),这意味着当我们使用变量时我们看到的会是第二个变量的值。我们可以通过使用相同的变量名并重复使用 let 关键字来遮蔽变量。常量在程序运行的整个过程中都有效。

mut 和遮蔽之间的另一个区别是,因为我们在再次使用 let 关键字时有效地创建了一个新的变量,所以我们可以改变值的类型(使用 mut 的话变量值类型不可变),但重复使用相同的名称。

fn main() {
    let x = 5;

    let x = x + 1; // x = 6

    {// 这是一个作用域
        let x = x * 2;
        println!("The value of x in the inner scope is: {}", x);// x = 12
    }

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

2、数据类型

2.1、标量类型

标量(*scalar*)类型表示单个值。Rust 有 4 个基本的标量类型:整型、浮点型、布尔型和字符。

2.1.1、整型Integer

长度 有符号类型 无符号类型
8 位 i8 u8
16 位 i16 u16
32 位 i32 u32
64 位 i64 u64
128 位 i128 u128
arch isize usize

isizeusize 类型取决于程序运行的计算机体系结构,在表中表示为“arch”:若使用 64 位架构系统则为 64 位,若使用 32 位架构系统则为 32 位。

2.1.2、整型溢出

比方说有一个 u8 ,它可以存放从 0 到 255 的值。那么当你将其修改为范围之外的值,比如 256,则会发生整型溢出(*integer overflow*),这会导致两种行为的其中一种。

  1. 当在调试(debug)模式编译时,Rust 会检查整型溢出,若存在这些问题则使程序在编译时 *panic*。
  2. 在当使用 --release 参数进行发布(release)模式构建时,Rust 检测会导致 panic 的整型溢出。相反当检测到整型溢出时,Rust 会进行一种被称为二进制补码包裹(*two’s complement wrapping*)的操作。比如在 u8 的情况下,256 变成 0,257 变成 1,依此类推。

2.1.3、浮点类型

浮点数(*floating-point number*)是带有小数点的数字,在 Rust 中浮点类型(简称浮点型)数字也有两种基本类型。Rust 的浮点型是 f32f64,它们的大小分别为 32 位和 64 位。默认浮点类型是 f64,因为在现代的 CPU 中它的速度与 f32 的几乎相同,但精度更高。所有浮点型都是有符号的。

fn main() {
    let x = 2.0; // f64

    let y: f32 = 3.0; // f32
}

2.1.4、数字运算

Rust 的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取模运算。整数除法会向下取整。

fn main() {
    // addition 加
    let sum = 5 + 10; // 15

    // subtraction 减
    let difference = 95.5 - 4.3; // 91.2

    // multiplication 乘
    let product = 4 * 30; // 120

    // division 除
    let quotient = 56.7 / 32.2; // 1.76
    let floored = 2 / 3; // 0

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

2.1.5、布尔类型

和大多数编程语言一样,Rust 中的布尔类型也有两个可能的值:truefalse。布尔值的大小为 1 个字节。Rust 中的布尔类型使用 bool 声明。

fn main() {
    let t = true;

    let f: bool = false; // with explicit type annotation
}

2.1.6、字符类型

Rust 的 char(字符)类型是该语言最基本的字母类型。注意,我们声明的 char 字面量采用单引号括起来,这和字符串字面不同,字符串字面量是用双引号扩起来。Rust 的字符类型大小为 4 个字节,表示的是一个 Unicode 标量值,这意味着它可以表示的远远不止是 ASCII。标音字母,中文/日文/韩文的文字,emoji,还有零宽空格(zero width space)在 Rust 中都是合法的字符类型。

fn main() {
    let c = 'z';
    let z = 'ℤ';
    let heart_eyed_cat = '?';
}

2.2、复合类型

2.2.1、元组类型

元组是将多种类型的多个值组合到一个复合类型中的一种基本方式。元组的长度是固定的:声明后,它们就无法增长或缩小。

我们通过在小括号内写入以逗号分隔的值列表来创建一个元组。元组中的每个位置都有一个类型,并且元组中不同值的类型不要求是相同的。

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
}

变量 tup 绑定到整个元组,因为元组被认作是单个复合元素。 想从元组中获取个别值,我们可以使用模式匹配来解构(destructure)元组的一个值

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

    let (x, y, z) = tup;

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

除了通过模式匹配进行解构外,我们还可以使用一个句点(.)连上要访问的值的索引来直接访问元组元素。

fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);

    let five_hundred = x.0; // 500

    let six_point_four = x.1; // 6.4

    let one = x.2; // 1
}

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

fn main() {
    let a = guess();
    if a == () {
        println!("a==()")
    }
}
fn guess(){}

2.2.2、数组类型

将多个值组合在一起的另一种方式就是使用数组(*array*)。与元组不同,数组的每个元素必须具有相同的类型。Rust 中的数组具有固定长度。

当你希望将数据分配到栈(stack)而不是堆(heap)时,或者当你希望确保始终具有固定数量的元素时,数组特别有用。

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

数组是可以在栈上分配的已知固定大小的单个内存块。可以使用索引访问数组的元素

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

    let first = a[0]; // 1
    let second = a[1]; // 2
}

如果尝试访问超出数组末尾的数组元素,将导致运行时(*runtime*)错误。程序退出并显示错误消息。

3、函数

Rust 代码中的函数和变量名使用下划线命名法(*snake case*,直译为蛇形命名法)规范风格。

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

    another_function();
}

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

main 函数中的代码会按顺序执行。首先,打印 “Hello, world!” 信息,然后调用 another_function 函数并打印它的信息。

3.1、参数

函数也可以被定义为拥有参数(*parameter*),参数是特殊变量,是函数签名的一部分。当函数拥有参数(形参)时,可以为这些参数提供具体的值(实参)。技术上讲,这些具体值被称为实参(*argument*),但是在日常交流中,人们倾向于不区分使用 parameterargument 来表示函数定义中的变量或调用函数时传入的具体值。

fn main() {
    another_function(5);// 实参 argument
}

fn another_function(x: i32) {// 形参 parameter
    println!("The value of x is: {}", x);
}

3.2、语句和表达式

函数体由一系列语句组成,也可选地以表达式结尾。表达式是语句的一部分。因为 Rust 是一门基于表达式(expression-based)的语言,所以这是一个需要理解的 重要区别

语句(*statement*)是执行一些操作但不返回值的指令。

表达式(*expression*)计算并产生一个值。

fn main() {
    let y = 6; // 这是一条语句 , 6 是表达式
  	let y = {
        let x = 3;
        x + 1 // 这是表达式
    };

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

表达式 的结尾 没有分号

如果在表达式的末尾加上 分号,那么它就转换为 语句,而语句没有返回值。

3.3、带有返回值的函数

函数可以向调用它的代码返回值。我们并不对返回值命名,但要在箭头(->)后声明它的类型。在 Rust 中,函数的 返回值等同于函数体最后一个表达式的值。使用 return 关键字和指定值,可从函数中提前返回;但大部分函数 隐式的 返回 最后的表达式

fn five() -> i32 {
    5
}

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

    println!("The value of x is: {}", x); // 5
  
	  let x = plus_one(5); // 报错
}


fn plus_one(x: i32) -> i32 {
    x + 1; // 已分号结尾是语句 则没有返回值
	  // 这里会报错
//  |
//7 | fn plus_one(x: i32) -> i32 {
//  |    --------            ^^^ expected `i32`, found `()`
//  |    |
//  |    implicitly returns `()` as its body has no tail or `return` expression
//8 |     x + 1;
}

主要的错误信息 “mismatched types”(类型不匹配)揭示了这段代码的核心问题。函数 plus_one 的定义说明它要返回一个 i32 类型的值,不过语句并不会返回值,此值由单位类型 () 表示,表示不返回值。因为不返回值与函数定义相矛盾,从而出现一个错误。

4、控制流

根据条件是否为真来决定是否执行某些代码,或根据条件是否为真来重复运行一段代码,是大部分编程语言的基本组成部分。Rust 代码中最常见的用来控制执行流的结构是 if 表达式和循环。

4.1、if 表达式

所有的 if 表达式都以 if 关键字开头,其后跟一个条件。

fn main() {
    let number = 3;

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

4.1.1、在 let 语句中使用 if

因为 if 是一个表达式,我们可以在 let 语句的右侧使用它来将结果赋值给一个变量。

整个 if 表达式的值取决于哪个代码块被执行。这意味着 if 的每个分支的可能的返回值都必须是相同类型。

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

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

4.2、使用循环

Rust 有三种循环:loopwhilefor

4.2.1、loop

loop 关键字告诉 Rust 一遍又一遍地执行一段代码直到你明确要求停止。如:使用 break 关键字来告诉程序何时停止循环。

循环中的 continue 关键字告诉程序跳过这个循环迭代中的任何剩余代码,并转到下一个迭代。

fn main() {
    loop {
        println!("again!");
    }
}

如果存在嵌套循环,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);
}

4.2.2、从循环返回

loop 的一个用例是重试可能会失败的操作,比如检查线程是否完成了任务。然而你可能会需要将操作的结果从循环中传递给其它的代码。为此,你可以在用于停止循环的 break 表达式添加你想要返回的值;该值将从循环中返回,以便您可以使用它

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {}", result);
}
// 在循环之前,我们声明了一个名为 counter 的变量并初始化为 0。接着声明了一个名为 result 来存放循环的返回值。在循环的每一次迭代中,我们将 counter 变量加 1,接着检查计数是否等于 10。当相等时,使用 break 关键字返回值 counter * 2。循环之后,我们通过分号结束赋值给 result 的语句。最后打印出 result 的值,也就是 20。

4.2.3、while

当条件为真,执行循环。当条件不再为真,调用 break 停止循环。这个模式太常用了,Rust 为此内置了一个语言结构,它被称为 while 循环。

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);

        number -= 1;
    }

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

4.2.4、使用 for 遍历集合

for 循环的安全性和简洁性使得它成为 Rust 中使用最多的循环结构。即使是在想要循环执行代码特定次数时。

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

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

大部分 Rustacean 也会使用 for 循环。这么做的方式是使用 Range,它是标准库提供的类型,用来生成从一个数字开始到另一个数字之前结束的所有数字的序列。

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


好好学习,天天向上