Learning Rust - 1 基本概念
基本概念
1 数据类型(data type)
Rust
是静态类型语言(statically typed)
,每一个值都属于某一个数据类型,编译时必须知道所有变量常量的类型。通常编译器可以推断出数据类型,但有时必须添加类型注解。
接下来将介绍两类数据类型子集:标量(scalar)
和复合(compound)
。
1.1 标量
标亮类型代表一个单独的值。Rust
有四种基本的标量类型:整型(int)
、浮点型(float)
、布尔类型(bool)
和字符类型(char)
。
整形
整型是一个没有小数部分的数字。
表1-1: Rust中的整型
Length | Signed | Unsigned |
---|---|---|
8-bit | i8 |
u8 |
16-bit | i16 |
u16 |
32-bit | i32 |
u32 |
64-bit | i64 |
u64 |
128-bit | i128 |
u128 |
arch-bit | isize |
usize |
每一个有符号的数据类型可以存储 $-(2^{n-1})$ ~ $2^{n-1}-1$ 在内的数字,无符号的数据类型则是 $0$ ~ $2^{n}-1$。
isize
和usize
类型依赖运行程序的计算机架构:64位架构上它们是64位的,32位架构上它们是32位的。
Rust
默认使用i32
类型。
在 Debug 模式下,可以检测出溢出的数据,并且使得程序 panic,但是在 release 模式下并不会检测溢出,而是使用回环操作(wrapping),例如在 u8 数据类型下,值 256 变成 0,257 变成 1。
表1-2: Rust中的整型字面值
Number literals | Example |
---|---|
Decimal(十进制) | 98_222 |
Hex(十六进制) | 0xff |
Octal(八进制) | 0o77 |
Binary(二进制) | 0b1111_0000 |
Byte(单字节字符,仅限于u8) | b'A' |
数字字面值允许使用类型后缀,例如57u8来指定类型,同时也允许使用 _
做为分隔符以方便读数,例如 1_000
浮点型
Rust
中浮点数类型为f32
与f64
,分别占32位与64位,默认类型为f64
,采用IEEE-754
标准。
例1.1fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
布尔型
Rust
中的布尔类型有两个可能的值:true
和false
,使用 bool 表示。
例1.2fn main() {
let t = true;
let f: bool = false; // with explicit type annotation
}
字符类型
Rust
使用单引号声明char
字面量,大小为四个字节,并代表了一个Unicode 标量值(Unicode Scalar Value)
。
例1.3fn main() {
let c = 'z';
let z: char = 'ℤ'; // with explicit type annotation
let heart_eyed_cat = '🥰';
}
1.2 复合
Rust
有两个原生的复合类型:元组(tuple)
和数组(array)
。
元组类型
元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:使用tup
声明,一旦声明,其长度不会增大或缩小。
例1.4fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
可以使用.
来访问指定元素:
例1.5fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let a1 = x.0;
let a2 = x.1;
let a3 = x.2;
}
不带任何值的元组有个特殊的名称,叫做单元(unit)
元组。这种值以及对应的类型都写作 (),表示空值或空的返回类型。
数组类型
与元组不同,数组中的每个元素的类型必须相同,并且数组长度是固定的。
例1.6fn main() {
let a = [1, 2, 3, 4, 5];
let b: [i32; 5] = [1, 2, 3, 4, 5];
let c = [3; 5]; // let c = [3, 3, 3, 3, 3];
}
使用索引访问指定数组元素:
例1.7fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
Rust
会自动检查索引是否小于数组长度,如果超出了则会 panic,这是其安全性的一个体现。
2 变量(variables)
Rust
中,变量默认是不可改变的:immutable
,或许让人非常奇怪,不可变的变量还能叫做变量?当然Rust
中可以使用可变变量,但是默认不可变能够提升代码的安全性。
例2.1:fn main (){
let x = 5;
println!("the value of x is {}", x);
x = 6;
println!("the value of x is {}", x);
}
在这里编译器会进行报错:
cannot assign twice to immutable variable ‘x’ scannot assign twice to immutable variable
执行cargo build
会显示出错信息,并且还会告知如何解决。
error[E0384]: cannot assign twice to immutable variable `x` |
在Rust
中,在变量名前添加mut
来声明此变量可变:
例2.2:fn main (){
let mut x = 5;
println!("the value of x is {}", x);
x = 6;
println!("the value of x is {}", x);
}
此代码将正确执行:
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.40s |
3 常量(constants)
类似于不可变变量,常量
是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。
首先,不允许对常量使用mut
。常量不光默认不可变,它总是不可变。声明常量使用const
关键字而不是let
,并且必须注明值的类型。
例3.1:const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
4 隐藏(Shadowing)
重新使用let
声明一个已经出现过的变量时,编译器将看到新的变量。实际上,新的变量“遮蔽”了原先的变量,此时任何使用该变量名的行为中都会视为是在使用新的变量,直到新的变量自己也被隐藏或作用域结束。
fn main() { |
隐藏与mut
的一个区别是,当再次使用let
时,实际上创建了一个新变量,我们可以改变值的类型,并且复用这个名字。
let spaces = " "; // str |
5 语句与表达式
语句(Statements)
是执行一些操作但不返回值的指令。表达式(Expressions)
计算并产生一个值。
函数调用是一个表达式,宏调用是一个表达式,用大括号创建的一个新的块作用域也是一个表达式。
例5.1:fn main() {
let x = 5; // Statements
let y = { // Expression
let x = 3;
x + 1 // no `;`
};
}
6 函数
Rust
中通过输入fn
后面跟着函数名和一对圆括号来定义函数。大括号告诉编译器哪里是函数体的开始和结尾。
Rust
代码中的函数和变量名使用snake case
规范风格,所有字母都是小写并使用下划线分隔单词。
例6.1:fn main(){
println!("hello world!");
}
fn some_function(){
println!("some function");
}
具有返回值的函数需要使用箭头->
指明其返回值类型,可以使用return
进行返回,也可以隐式返回最后一个表达式(注意表达式不加;
)。
例6.2:fn some_function() -> i32{
5
}
7 if 表达式
Rust
中,if表达式
与其他语言类似,具体形式如下:
例7.1:fn main() {
let number = 3;
if number < 5 {
println!("smaller than 5");
} else if number >5 {
println!("greater than 5");
} else {
println!("equal 5")
}
}
可以在let
语句中配合使用if表达式
:
例7.2:fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {number}");
}
if let 表达式
if let
表达式的代替条件操作数的是一个关键字let
,再后面是一个模式、一个=
和一个检验对象(scrutinee)操作数
。 如果检验对象操作数的值与模式匹配,则执行相应的块。 否则,如果存在else
块,则继续处理后面的else
块。
例7.3:let dish = ("Ham", "Eggs");
// 此主体代码将被跳过,因为该模式被反驳
if let ("Bacon", b) = dish {
println!("Bacon is served with {}", b);
} else {
// 这个块将被执行。
println!("No bacon will be served");
}
// 此主体代码将被执行
if let ("Ham", b) = dish {
println!("Ham is served with {}", b);
}
if let _ = 5 {
println!("不可反驳型的模式总是会匹配成功的");
}
if
与if let
表达式可以混合使用:
例7.4:fn main() {
let x = Some(3);
let a = if let Some(1) = x {
1
} else if x == Some(2) {
2
} else if let Some(y) = x {
y
} else {
-1
};
}
8 循环控制
Rust
除了while
,for
以外,还提供loop
循环,无限执行直到明确的停止。
例8.1:fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {result}");
}
如果存在嵌套循环,break
和continue
应用于此时最内层的循环。你可以选择在一个循环上指定一个循环标签(loop label)
,然后将循环标签
与break
或continue
一起使用,使这些关键字应用于已标记的循环而不是最内层的循环。
例8.2: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
只退出内层循环,带标签的break
将退出外层循环。
# output |
在for
语言中,可以使用一种更简洁的方式来遍历一个集合中的元素:
例8.3:fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {element}");
}
}