rust的变量、数据类型、函数、控制流
- 变量
在 Rust 中,使用 let 关键字来声明变量。
核心特性:变量默认不可变
fn main() {
let x = 5; // 声明一个不可变变量 x,其值为 5
println!("The value of x is: {}", x);
x = 6; // 这行代码会编译错误!Cannot assign twice to immutable variable `x`
println!("The value of x is: {}", x);
}
这是 Rust 安全性和并发性设计的基石。它防止了值被意外改变。如果你希望一个变量是可变的,必须显式地使用 mut 关键字。
fn main() {
let mut x = 5; // 声明一个可变变量 x
println!("The value of x is: {}", x);
x = 6; // 这是允许的,因为 x 是可变的
println!("The value of x is: {}", x);
}
最佳实践:优先使用不可变变量,除非你明确需要改变其值。这能使代码更清晰、更安全。
变量隐藏(Shadowing)
你可以使用与之前变量相同的名字来声明一个新的变量。这被称为“隐藏”,意味着第二个变量“覆盖”了第一个。
fn main() {
let x = 5; // 第一个 x,不可变
let x = x + 1; // 第二个 x,隐藏了第一个 x。它仍然是不可变的,但值变成了 6。
{
let x = x * 2; // 第三个 x,在这个代码块内隐藏了第二个 x。值变成了 12。
println!("The value of x in the inner scope is: {}", x); // 输出 12
} // 代码块结束,第三个 x 离开作用域
println!("The value of x is: {}", x); // 输出 6,第二个 x 重新可见
}
隐藏 vs. mut:
· 隐藏:实际上是创建了一个新变量。你可以改变值的类型(例如从数字变成字符串),但新变量依然是不可变的,除非你用 mut。
· mut:是同一个变量,值可以改变,但类型不能改变。
let spaces = " "; // spaces 是字符串类型
let spaces = spaces.len(); // 隐藏后,spaces 是数字类型。如果用 `mut` 则无法这样操作。
- 数据类型
Rust 是静态类型语言,这意味着在编译时就必须知道所有变量的类型。编译器通常能根据值和使用方式推断出类型,但有时也需要显式注解。
数据类型分为两大类:标量和复合。
标量类型
代表单个值。
- 整型:
· 有符号:i8, i16, i32, i64, i128, isize(i 开头)
· 无符号:u8, u16, u32, u64, u128, usize(u 开头)
· 数字大小:8, 16, 32, 64, 128 表示位数。isize 和 usize 取决于系统架构(32位或64位)。
· 默认是 i32。
· 字面值:98_222(十进制),0xff(十六进制),0o77(八进制),0b1111_0000(二进制),b'A'(字节,仅限 u8)。 - 浮点型:
· f32(单精度)和 f64(双精度)
· 默认是 f64,因为在现代 CPU 上速度与 f32 差不多但精度更高。
· 字面值:3.14, -10.0, 2.,2.0f32 - 布尔型:
· true 和 false
· 类型注解是 bool - 字符类型:
· 使用单引号 '' 声明,占用 4 个字节。
· 代表一个 Unicode 标量值,这意味着它可以表示比 ASCII 多得多的字符:中文、日文、韩文、emoji 等。
· 示例:let c = 'z'; let z = 'ℤ'; let heart_eyed_cat = '😻';
复合类型
将多个值组合成一个类型。
数组:
· 数组中的每个元素必须具有相同的类型。
· 数组的长度是固定的(与 Vector 不同,Vector 是标准库提供的可变长度集合)。
· 使用方括号 [] 创建。
· 当你希望数据分配在栈上而不是堆上时,或者当你想要确保总是有固定数量的元素时,数组很有用。
fn main() {
// 声明一个数组,类型注解 [i32; 5] 表示元素类型为 i32,长度为 5
let a: [i32; 5] = [1, 2, 3, 4, 5];
// 另一种声明方式:创建一个每个元素值都为 3 的数组,长度为 5
let b = [3; 5]; // 等同于 let b = [3, 3, 3, 3, 3];
// 访问数组元素
let first = a[0]; // 1
let second = a[1]; // 2
// 如果访问 a[5],编译会通过,但运行时会 panic(程序因错误而退出)!
// 这是 Rust 内存安全原则的一个关键体现:数组访问会在运行时进行边界检查。
}
元组:
· 将多个不同类型的值组合在一起。长度固定,一旦声明,其长度和类型就不能改变。
· 使用圆括号 () 创建。
· 通过模式匹配解构(destructuring)或使用点号 . 后跟索引来访问元素。
fn main() {
// 声明一个元组,类型注解 (i32, f64, u8)
let tup: (i32, f64, u8) = (500, 6.4, 1);
// 解构
let (x, y, z) = tup;
println!("The value of y is: {}", y); // 输出 6.4
// 通过索引访问
let five_hundred = tup.0; // 500
let six_point_four = tup.1; // 6.4
let one = tup.2; // 1
}
- 函数
使用 fn 关键字来声明函数。Rust 使用 snake case 规范作为函数和变量名的命名风格:所有字母都是小写,并使用下划线分隔单词。
参数
函数参数必须显式声明类型。这是函数签名的一部分,编译器需要它来理解你的意图。
fn another_function(x: i32, y: f64) { // x 是 i32 类型,y 是 f64 类型
println!("The value of x is: {}", x);
println!("The value of y is: {}", y);
}
语句与表达式
· 语句:执行操作但不返回值的指令。let x = 6; 是一个语句。
· 表达式:会计算并产生一个值。5 + 6 是一个表达式,值为 11。函数调用、宏调用、一个单独的 {} 代码块都是表达式。
fn main() {
let y = { // 这个代码块是一个表达式...
let x = 3;
x + 1 // 注意:这里没有分号!加上分号就变成了语句,不再返回值。
}; // ...它的值是 4,并赋值给 y
println!("The value of y is: {}", y); // 输出 4
}
返回值
在 -> 后声明函数返回值的类型。在 Rust 中,函数的返回值等同于函数体最后一个表达式的值。
可以使用 return 关键字提前返回,但大多数函数都隐式地返回最后的表达式。
fn five() -> i32 { // 声明返回类型为 i32
5 // 没有分号,这是一个表达式,其值 5 被返回
}
fn plus_one(x: i32) -> i32 {
x + 1 // 返回值
// x + 1; // 如果加上分号,就变成了语句,返回 `()`(单元类型),与声明的 -> i32 冲突,会导致编译错误。
}
fn main() {
let x = five();
let y = plus_one(5);
println!("x = {}, y = {}", x, y); // x = 5, y = 6
}
- 控制流
if 表达式
if 条件必须是 bool 类型。Rust 不会自动将非布尔值转换为布尔值。
fn main() {
let number = 3;
if number < 5 { // 条件必须是 bool
println!("condition was true");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("condition was false");
}
}
因为 if 是一个表达式,所以可以在 let 语句的右侧使用它。
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 }; // 两个分支必须返回相同类型!
// let number = if condition { 5 } else { "six" }; // 错误!类型不匹配
println!("The value of number is: {}", number); // 5
}
循环
Rust 提供了三种循环:loop、while 和 for。
for:遍历集合。最常用、最安全、最简洁的循环方式。它利用了“迭代器”的概念,避免了 while 循环中可能出现的索引越界错误。
fn main() {
let a = [10, 20, 30, 40, 50];
// 使用 for 循环遍历数组元素
for element in a.iter() { // .iter() 返回一个迭代器
println!("the value is: {}", element);
}
// 更常见的用法:使用范围 (Range)
for number in (1..4).rev() { // (1..4) 生成 1,2,3。.rev() 将其反转
println!("{}!", number);
}
// 输出:
// 3!
// 2!
// 1!
}
while:条件循环,每次循环前都判断条件。
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!!!");
}
loop:无限循环,直到显式要求停止(break)。
fn main() {
let mut counter = 0;
let result = loop { // loop 也是一个表达式,可以从 break 返回值
counter += 1;
if counter == 10 {
break counter * 2; // 跳出循环并返回 counter * 2
}
};
println!("The result is {}", result); // 20
}
贵公网安备52052402000220号