本文对应《Rust权威指南》的第3章,主要介绍Rust中的通用编程概念,如变量、数据类型、函数、程序控制流等。
注意,此处的可变性(Mutability)与Python变量中的可变变量所指相同,而不是与常量相对的变化性。
Rust中的变量默认不可变, 使用mut关键字可以创建一个可变变量。
let x = 123; // immutable
let mut x = 123; // mutable
PS: 关于这样设计的原因,笔者看到了一个编码习惯方面的解释:**在实现同样功能的情况下,程序员倾向于使用更短的语法。**在Rust中,大多数情况下,不可变变量可以胜任工作。如果可变变量的声明比不可变变量短,那么程序员倾向直接使用可变变量,**而这产生了不安全性。**将不可变设计得比可变短,体现了语言鼓励不可变变量。
常量与不可变变量的概念也不相同。我们使用const关键字修饰一个常量
const MAX_POINTS: u32 = 100;
Rust提供隐藏机制,允许用户使用同一个变量名绑定不同的变量。
使用let声明的新变量可以**覆盖旧的同名变量。这被称为旧变量被新变量隐藏(shadow)**了。我们可以不停使用let同一变量名来隐藏变量。
Rust 中,数据类型可以分为两类:标量类型(Scalar), 复合类型(Compound)
注意:Rust是静态类型语言,所有变量的具体类型都需要在编译过程中确定。(这和变量类型是否被显式声明无关,使用隐式声明,编译器也能在一定条件下推导变量类型)。
标量类型是单个值类型的同城,Rust 内建(builtin)了4中基础标量类型:
整型下,根据长度和有无符号位,分为以下几种细分类型
| Size | Signed | Unsigned |
|---|---|---|
| 8b | i8 | u8 |
| 16b | i16 | u16 |
| 32b | i32 | u32 |
| 64b | i64 | u64 |
| 128b | i128 | u128 |
| arch-depend | isize | usize |
i32,运行效率最高整型溢出
panicpanic,替换为补码环绕(类似C)Wrapping手动启用环绕共有两种浮点数类型,f32单精度和f64双精度,两者效率接近。Rust默认将浮点数推导为f64。
Rust支持基本数学运算符。但是,Rust不支持不同类型的运算(整型内部也不可),需要将字面量指定成对应类型(在后面添加即可)
let a = 5 / 2; // correct, a == 2
let a = 5.0 / 2; // error
let a = 5.0 / 2.0; // correct, a == 2.5, f64
let a = 5f64 / 2f32;// error, type mismatch
let a = 5f32 / 2.0; // correct, a == 2.5, f32
布尔型只有两个值,true & false,占用1Byte
用于表示最基础的单个字符,使用单引号指定''(与C类似)
Attention: char类型占4字节,使用unicode编码,提供了外文、表情支持
Compound Type 将多个不同类型的值组合为一个类型。Rust提供两种Builtin基础复合类型
元组可以将不同类型的值组合进一个符合类型。元组拥有一个固定长度,长度无法在声明结束后修改。
元组使用圆括号()包含,逗号,分隔
let tup = (500, 6.4, 'a', true);
let (x, y, z, a) = tup;
.后跟索引,使用对应位置上的值Rust数组拥有固定长度,声明后无法改变。使用方括号[]来声明。数组的每个元素必须是相同类型。另外,可以用类型标注,指定元素类型
let a = [1, 2, 3, 4];
let a: [u64; 4] = [1, 2, 3, 4];
数组批量声明方法
使用批量声明,可以快速创建含有相同元素的数组
let a: [u64; 3] = [1; 3]; // assert_eq!(a, [1, 1, 1]);
let a = [1u64; 3]; // same as line 1
Accessing array elements
使用方括号[]与索引访问元素。
let a = [1, 2, 3, 4];
assert_eq!(a[0], 1);
index out of bounds使用fn关键字声明函数。
Rust采用snake case(蛇形命名法)规范函数命名,所有字母小写,单词采用下划线连接
Rust不关心函数定义位置,只关心该函数是否在范围内可见
fn test_function() {
println!("This is a test function");
}
在英文中区分了函数的形参(Parameter)和实参 (Argument),但在中文中常常混淆为“参数”。应当区分。函数参数在括号中填充
fn test1(x: i32) {
println!("the value of x is {}", x);
}
语句不会返回值,表达式会。Rust中的大部分代码都是表达式
普通赋值语句不会返回值,这不同于C
表达式本身作为语句的一部分
以下都是表达式
调用函数
调用宏
花括号{} 除了能新建作用域,也能作为表达式使用。最后一行语句如果没有分号;,则被认为是{}表达式的返回值
let y = {
let x = 3;
x + 1 // no semi-colon
}
()Return Value 是函数向调用它的代码返回的值。需要在箭头->后声明其类型
同样,使用表达式(最后一行)隐式传递返回值,或使用return关键字提前返回。
fn five -> i32 {
5 // return 5 implicitly
}
fn three -> i32 {
return 3; // return explicitly
}
除了之前提到的match匹配,Rust同样提供了正常的控制流,如分支结构if和循环结构等
if表达式的判断条件(condition)不需要使用括号包含。但是所有代码必须用大括号包装为代码块。
Condition必须产生一个bool类型的值,这与其他语言中非零即true的设计不相同。
let number = 3;
if number { // error
todo!();
}
if number != 0 { // correct
todo!();
}
代码格式规范:
if y == 1 {
todo!()
} else if y == 2 {
todo!()
} else {
todo!()
}
由于todo!宏返回空,所以加不加分号不影响
当if表达式独立出现,代码块必须只能返回空,否则编译错误
过多的if-else导致代码杂乱无章,可以用之前介绍的match分支结构(在后面介绍)
if 可作为表达式使用(返回特定类型的值)
let x = if y == 1 {
1
} else if y == 2 {
2
} else {
'a' // error, type mismatch
};
我们可以使用if let语句精简代码,将赋值和匹配连在一起
使用loop语句实现循环(死循环),通过break关键字退出
loop {
todo!();
if condition {
break;
}
}
通过在break后加参数以返回值(用于表达式)
let mut counter = 0;
let mut sum = 0;
let result = loop {
counter += 1;
sum += counter;
if counter == 10 {
break sum * 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}");
}
while condition {
todo!()
}
使用for语句来遍历一个集合
let a = [10, 20, 30];
for ele in a { // element, ele: i32
println!("Element is: {}", ele);
}
for ele in a.iter() { // iterator, ele: &i32
println!("Element is: {}", ele);
}
使用Range生成循环
for number in 1..4 {
println!("Number is: {}", number);
}
for number if (1..4).rev() { // revert the sequence
println!("Number is: {}", number);
}