文章

Rust学习笔记(1) - 常见编程概念

推进Rust的学习,之前有零散学习过,现在系统做些笔记,主要是看Rust程序设计语言

常见编程概念

变量与可变性

不可变(immutable)变量,可变(mutable)变量,常量(constants)

Rust中的变量默认是不可变的(immutable),要声明可变变量,可以在其之前加mut来声明,可以用const关键字声明常量,常量可以在任何作用域,包括全局作用域中声明

1
2
3
4
5
6
7
8
9
// 定义一个常量,命名规范通常为用下划线分隔的大写字母单词,且必须注值的类型
const MAX_POINTS:u32 = 100_000;
fn main() {
    // 简单定义一个变量,定义后,其值不可改变
    let immutable_int = 500;
    // 定义一个可变变量,这里在编译时会有警告,提示没有必要让其可变
    let mut mutable_int  = 6;
    println!("定义了一个可变变量{},一个不可变变量{},一个常量{}",mutable_int,immutable_int,MAX_POINTS);
}

变量隐藏(Shadowing)

可以定义一个与之前变量同名的新变量来隐藏之前的变量,这意味着使用这个变量时会看到第二个值

1
2
3
4
5
6
7
8
9
fn main() {
    // 简单定义一个变量,定义后,其值不可改变
    let immutable_int = 500;
    // 使用相同变量名,隐藏之前定义的变量
    let immutable_int = 501;
    let immutable_int = 502;
    // 编译时,会弹出警告,提示上方两个变量从未使用过
    println!("一个变量{}",immutable_int);
}

当再次使用let时,实际上创建了一个新变量,只是复用了之前变量的名字,这个变量可以是不同类型的变量,使用mut,则不能用不同类型的变量赋值

1
2
3
4
5
6
7
8
fn main() {
    // 定义一个字符串变量
    let spaces = "  ";
    // 用同样的变量名,得到这个字符串的长度
    let spaces = spaces.len();
    // 因为这个字符串有两个空格,最后这个变量的值是2
    println!("最后变量的值是{}",spaces);
}

数据类型

Rust是静态类型(statically typed)语言,在编译时就必须知道所有变量的类型,当可能会是多种类型时,必须增加类型注解,例如:

1
2
let num = "2";
let num = num.parse().expect("这不是一个数字!");

这里在parse时,必须添加类型注解,说明编译器需要更多信息,写成:

1
2
let num = "2";
let num: u32 = num.parse().expect("这不是一个数字!");

就能编译通过了

标量(scalar)类型

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

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

有无符号代表数字能否为负值,有符号数以补码形式(two’s complement representatin)存储,如果变体使用的位数为n,则变体可以用的值包含从2的n-1次方的相反数到2的n-1次方-1在内的数,所以i8可以储存从-128到-127在内的数。无符号的变体可以存储从0到2的n次方-1的数,所以u8可以储存从0到255在内的数

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

除byte以外的所有数字字面量允许使用类型后缀,也允许使用_作为分隔符以方便读数,这里有一个表格

数字字面值例子
Decimal (十进制)98_222
Hex (十六进制)0xff
Octal (八进制)0o77
Binary (二进制)0b1111_0000
Byte (单字节字符)(仅限于u8)b'A'

例子:

1
2
3
4
5
6
7
8
9
10
fn main() {
    let num = 2u8;
    let decimal_num = 9_8222;
    let hex_num = 0xff;
    let octal_num = 0o77;
    let binary_num = 0b1111_0000;
    let byte = b'A';
    // 将输出2,98222,255,63,240,65
    println!("{},{},{},{},{},{}",num,decimal_num,hex_num,octal_num,binary_num,byte);
}

Rust的默认数字类型是i32,它通常是最快的,甚至在64位系统上也是,当使用一个整型变量存放超过其容量的值,会发生整型溢出(integer overflow)

浮点型

Rust有两个原生的浮点数(floating-point numbers)类型,它们是带小数点的数字,分别是f32f64,占32位和64位。默认类型是f64,因为在现代CPU中,它与f32速度几乎一样,不过精度更高(f32是单精度浮点数,f64是双精度浮点数)。

数值运算

Rust中的所有数字类型都支持基本数学运算:加减乘除和取余,注意当整数和浮点数一起运算时,运算符两边的数值的类型要保持一致,需要均为浮点型或均为整型

1
2
3
4
5
6
7
8
fn main() {
    let num = 2;
    let num = num + 6;
    let num = num - 1;
    let num = num * 10;
    let num = num as f32 / 2.5 ;
    let num = num % 5.0;
}
布尔型

Rust中有两个布尔型:truefalse,布尔类型用bool表示

字符类型

Rust中的char类型大小为四字节,并代表了一个Unicode标量值(Unicode Scalar Value),这意味着它可以比 ASCII 表示更多内容。在 Rust 中,拼音字母(Accented letters),中文、日文、韩文等字符,emoji(绘文字)以及零长度的空白字符都是有效的 char 值。Unicode 标量值包含从 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 char 并不符合。

1
2
3
4
5
fn main() {
    let c = '1';
    let c = '数';
    let c = '😀';
}

复合类型

复合类型(Compound types)可以将多个值组合成一个类型。Rust有两个原生的复合类型:元组(tuple)数组(array)

元组类型

元组是将多个其他类型的值组合进一个复合类型的主要方式,一旦声明,其长度固定,元组的定义:

1
2
3
let tup = (1.2, 3, 4);
// 添加了类型注解的元组
let tup:(f64,u32,u32) = (1.2,3,4);

可以使用模式匹配(pattern matching)来解构(destructure)元组值,或者用.来直接访问元组内的值:

1
2
3
4
5
6
7
fn main() {
    let tup = (1.2, 3, 4);
    // 解构元组,不需要的值可以用_来充当占位符
    let(_,y,_) = tup;
    println!("The value of y is {}",y);
    println!("元组中的第一个值是{}",tup.0);
}
数组类型

与元组不同,数组(array)中的每个元素的类型必须相同,且Rust中的数组是固定长度的,一旦声明,其不能增长或缩小,几种声明数组的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fn main() {
    let months = [
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December",
    ];

    // 在定义时规定数组元素的类型和元素个数
    let nums: [u32; 5] = [1, 2, 3, 4, 5];
    // 创建一个每个元素都相同的数组,分号前面是数组中元素的值,后面是元素的个数
    let repeat_nums = [3; 5];
}

访问数组元素:

1
2
3
4
fn main() {
    let nums = [1, 2, 3, 4, 5];
    println!("数组的第一个元素是{}", nums[0]);
}

函数如何工作

Rust中的函数和变量名使用snake case规范风格,所有字母都是小写并使用下划线分隔单词

定义函数

Rust中函数定义是以fn开头的,并且必须声明每个参数的的类型

1
2
3
fn print_one_number(number_be_printed: i32) {
    println!("{}", number_be_printed);
}

包含语句和表达式的函数体

在了解这个概念之前,要先理解Rust是一门基于表达式(expression-based)的语言,具体来说,语句(Statements)是执行一些操作但不返回值的命令,表达式(Expressions)计算并产生一个值,直接来看一些例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
    // 这是一个语句,我们不能将其赋值给另一个变量,比如写成let x = (let num=6)
    // 这里5是一个表达式,其计算出的值是6
    let num = 6;
    // 5 + 6是一个表达式,其计算出的值是11
    let num = 5 + 6;
    // 大括号{}也是一个表达式,其包含的代码块被绑定到y上
    // 表达式的结尾没有分号,如果在表达式的结尾加上分号,它就变成了语句
    let y = {
        let x = 3;
        x + 1
    };
}

虽然在例子中已经写了,但是还是要强调,表达式的结尾是没有分号的,其表示一个计算的过程,如果加上分号,就变成了语句,不会返回值

具有返回值的函数

在Rust中,函数的返回值等同于函数体最后一个表达式的值,并且要在箭头(->)后声明它的类型

1
2
3
4
5
6
7
8
fn main() {
    let num = plus_one(5);
    println!("The value of num is:{}", num);
}

fn plus_one(num: i32) -> i32 {
    num + 1
}

注释

关于注释,没有特别的地方,在前面加上//即可,放在行前,会持续到那一行结束,也可以放在代码行的行尾

控制流

if与else if

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
    // 计算if表达式的值并返回给变量
    let num = if 1 + 1 > 2 { 5 } else { 6 };

    // else if的使用
    if num % 2 == 0 {
        print!("0")
    } else if num % 2 == 1 {
        print!("1")
    } else {
        print!("no match condition");
    }
}

循环

使用loops重复执行代码

loop关键字会让Rust重复执行代码到明确要求停止

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
    let mut nums = 0;

    // 循环直到break表达式被触发,或程序结束
    loop {
        println!("continue");
        nums += 1;
        if nums > 5 {
            break;
        }
    }
}
从循环返回结果

通过break表达式,可以从循环中返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fn main() {
    let mut num = 0;

    // 循环直到break语句被触发,或程序结束
    loop {
        println!("continue");
        num += 1;
        if num > 5 {
            break;
        }
    }

    let result = loop {
        println!("not yet");
        num += 1;
        if num > 10 {
            break num
        }
    };

    print!("the result is {}", result);
}
本文由作者按照 CC BY 4.0 进行授权