cargo管理项目
cargo new <项目名> 会创建一个新示范项目文件夹
cargo init 在当前文件夹作为项目根目录,初始化一个项目
cargo run 编译并运行
cargo build 只编译
cargo build --release 编译优化版本
cargo check 快速检查(不生成二进制,比 build 快)
cargo test 运行测试
cargo clippy 代码风格和常见错误检查器
cargo fmt 自动格式化代码
添加依赖:cargo add <包名>
https://crates.io/
vs code左下角会有一个"RUST DEPENDENCES“的菜单可以看到依赖
单独编译
单独编译某个文件 rustc test.rs
动态类型&静态类型
rs
// 声明一个静态,所有的静态都会被编译器写死并占用内存
let mut s = "hello";
// s.push_str(" world");
let mut s = String::from("hello"); // 抄到堆里,变成笔记本
s.push_str(" world"); // ✅ 成功!现在 s 变成了 "hello world"
变量
- const 声明常量
- 常量在任何作用域都能声明,包括函数外的全局作用域。
- 命名约定:全大写 + 下划线分隔。必须显式标注类型。
- 常量的值必须是【编译期】就能确定的常量表达式。
- 必须标注类型。
rs
const MAX_POINTS: u32 = 100_000; // 数字里的下划线只是为了方便阅读,不影响值
- let声明变量
- 只能出现在函数体、代码块、闭包等运行时作用域内。
- 用 let 声明的变量默认是【不可变的】。
- 如果确实需要修改变量,在 let 后面加 mut 关键字。
- 必须先初始化才能使用,可以延迟赋值,但是使用前必须赋值。
rs
fn main() {
let x = 5; // 不可变变量
println!("x 的值是: {}", x);
let mut y = 10; //可变变量
println!("y 的初始值: {}", y);
y = 20; // OK,因为 y 是可变的
println!("y 修改后的值: {}", y);
// 必须先初始化才能使用
let late_init;
late_init = 42; // 延迟初始化是允许的
println!("延迟初始化的值: {}", late_init);
}
- 变量遮蔽(shadowing)
- 你可以用同一个名字声明一个新变量,新变量会"遮蔽"旧变量。
- 变量遮蔽是使用let新创建一个变量,不是重新赋值。
- 蔽可以发生在内层作用域,内层结束后外层变量恢复。
rs
fn main() {
let z = 5;
let z = z + 1; // 新的 z,值为 6,遮蔽了旧的 z
let z = z * 2; // 又一个新的 z,值为 12
println!("经过两次遮蔽后 z = {}", z);
// 遮蔽的强大之处:可以【改变类型】,而 mut 做不到。
let spaces = " "; // spaces 是 &str(字符串)
let spaces = spaces.len(); // 新的 spaces 是 usize(数字)
println!("空格数量: {}", spaces);
// 遮蔽可以发生在内层作用域,内层结束后外层变量恢复。
let value = 100;
{
let value = value * 10; // 内层作用域的新 value = 1000
println!("内层作用域 value = {}", value);
}
println!("外层作用域 value = {}", value); // 仍然是 100
}
数据类型
Rust 是【静态类型】语言:编译期必须知道每个值的类型。
多数情况下编译器能自动推断,但有时需要你显式标注。
- 标量类型(Scalar):整数、浮点数、布尔、字符
rs
fn main() {
// ------------------------------------------------------------------
// 1. 整数(Integers)
// ------------------------------------------------------------------
// 有符号:i8 i16 i32 i64 i128 isize
// 无符号:u8 u16 u32 u64 u128 usize
// 其中 isize/usize 的位数取决于运行平台(64 位机器上是 64 位)。
// 默认整数类型是 i32。
let a: i32 = -42;
let b: u8 = 255; // u8 范围是 0..=255
let big: i64 = 9_000_000_000;
println!("整数: a={}, b={}, big={}", a, b, big);
// 整数字面量的多种写法:
let decimal = 98_222; // 十进制
let hex = 0xff; // 十六进制
let octal = 0o77; // 八进制
let binary = 0b1111_0000; // 二进制
let byte = b'A'; // 字节(仅限 u8),值为 65
println!(
"字面量: 十进制={}, 十六进制={}, 八进制={}, 二进制={}, 字节={}",
decimal, hex, octal, binary, byte
);
// 整数溢出:
// 在 debug 模式下溢出会 panic;release 模式下会"环绕"(wrapping)。
// 想明确处理溢出,用这些方法:
let x: u8 = 250;
println!("checked_add(10): {:?}", x.checked_add(10)); // 返回 Option,溢出时为 None
println!("wrapping_add(10): {}", x.wrapping_add(10)); // 环绕:250+10=260 -> 4
println!("saturating_add(10): {}", x.saturating_add(10)); // 饱和:停在最大值 255
// ------------------------------------------------------------------
// 2. 浮点数(Floating-Point)
// ------------------------------------------------------------------
// 两种:f32(单精度)和 f64(双精度)。默认是 f64。
let f1 = 3.14; // 推断为 f64
let f2: f32 = 2.71828;
println!("浮点数: f1={}, f2={}", f1, f2);
// ------------------------------------------------------------------
// 3. 数值运算
// ------------------------------------------------------------------
let sum = 5 + 10;
let difference = 95.5 - 4.3;
let product = 4 * 30;
let quotient = 56.7 / 32.2;
let remainder = 43 % 5; // 取余
// 注意:整数除法会截断小数部分
let int_division = 7 / 2; // 结果是 3,不是 3.5
println!(
"运算: 和={}, 差={:.1}, 积={}, 商={:.4}, 余={}, 整数除法={}",
sum, difference, product, quotient, remainder, int_division
);
// ------------------------------------------------------------------
// 4. 布尔(Boolean)
// ------------------------------------------------------------------
let t = true;
let f: bool = false;
println!("布尔: t={}, f={}", t, f);
// ------------------------------------------------------------------
// 5. 字符(char)
// ------------------------------------------------------------------
// Rust 的 char 是 4 字节,表示一个 Unicode 标量值,
// 所以它不仅能存 ASCII,还能存中文、emoji 等。
// 注意:char 用【单引号】,字符串用双引号。
let c = 'z';
let chinese = '中';
let emoji = '😻';
println!("字符: c={}, 中文={}, emoji={}", c, chinese, emoji);
// ==================================================================
// 类型转换(Type Casting)
// ==================================================================
// Rust 【不会】隐式转换数值类型,必须用 as 显式转换。
let integer = 65;
let float_val = integer as f64; // i32 -> f64
let character = integer as u8 as char; // i32 -> u8 -> char,得到 'A'
println!("转换: 整数={}, 浮点={}, 字符={}", integer, float_val, character);
// 缩窄转换可能截断,要小心:
let large = 300;
let small = large as u8; // 300 超出 u8 范围,结果是 300 % 256 = 44
println!("缩窄转换: 300 as u8 = {}", small);
// ==================================================================
// 四、类型推断与显式标注
// ==================================================================
// 大多数时候编译器能推断类型:
let inferred = 2.0; // 推断为 f64
// 但有些场景必须标注,否则编译器不知道用哪个类型,例如 parse:
let guess: u32 = "42".parse().expect("不是数字");
// 或者用"涡轮鱼"(turbofish)语法指定:
let guess2 = "42".parse::<u32>().expect("不是数字");
println!("推断: {}, parse 标注: {}, turbofish: {}", inferred, guess, guess2);
}
- 复合类型(Compound):元组、数组
rs
fn main() {
// ------------------------------------------------------------------
// 6. 元组(Tuple)
// ------------------------------------------------------------------
// 把多个不同类型的值组合成一个复合值。长度固定。
let tup: (i32, f64, char) = (500, 6.4, 'A');
// 解构(destructuring):一次性把元组拆成多个变量
let (one, two, three) = tup;
println!("元组解构: {}, {}, {}", one, two, three);
// 用 . 加索引访问元组元素(从 0 开始)
println!("元组索引访问: {}, {}, {}", tup.0, tup.1, tup.2);
// 空元组 () 叫做"单元类型"(unit type),表示"没有值",
// 是没有返回值的函数的隐式返回类型。
let unit: () = ();
println!("单元类型: {:?}", unit);
// ------------------------------------------------------------------
// 7. 数组(Array)
// ------------------------------------------------------------------
// 数组中所有元素【类型相同】,长度固定(编译期已知)。
// 数组数据存放在【栈】上。
let arr = [1, 2, 3, 4, 5];
// 显式标注:[类型; 长度]
let typed_arr: [i32; 5] = [10, 20, 30, 40, 50];
// 快速初始化:[初始值; 长度] -> 5 个 3
let repeated = [3; 5];
println!("数组: {:?}", arr);
println!("带类型数组: {:?}", typed_arr);
println!("重复初始化: {:?}", repeated);
// 用索引访问数组元素
println!("第一个元素: {}, 第二个元素: {}", arr[0], arr[1]);
println!("数组长度: {}", arr.len());
// 越界访问:编译期已知的越界会编译错误;
// 运行期(比如用变量索引)越界会 panic,而不是读到非法内存。
// 例如 arr[10] 会触发 "index out of bounds" panic。
}
函数
- 函数定义的位置不影响调用:Rust 不要求"先定义后使用",
- 只要在同一作用域里能找到即可。下面调用的函数都定义在 main 之后。
- 函数中最后的那个值则为返回值,如果要提前返回则使用return
rs
fn main() {
// ------------------------------------------------------------------
// 1. 无参数、无返回值的函数
// ------------------------------------------------------------------
say_hello();
// ------------------------------------------------------------------
// 2. 带参数的函数
// ------------------------------------------------------------------
// 每个参数都【必须】标注类型,这是函数签名的硬性要求。
print_labeled_measurement(5, 'h');
// ------------------------------------------------------------------
// 3. 带返回值的函数
// ------------------------------------------------------------------
let five = five();
println!("five() 返回: {}", five);
let sum = add(3, 4);
println!("add(3, 4) = {}", sum);
// ------------------------------------------------------------------
// 4. 语句 vs 表达式
// ------------------------------------------------------------------
// 这是 Rust 区别于很多语言的关键点:
// - 【语句】(statement):执行某个动作,不返回值。如 `let x = 5;`
// - 【表达式】(expression):计算并产生一个值。如 `5 + 6`、函数调用、{ } 块
//
// Rust 是"基于表达式"的语言,很多东西都是表达式。
// 一个【块】 { } 也是表达式,它的值是最后一个表达式的值。
// 注意:作为返回值的那一行【结尾没有分号】!加了分号就变成语句了。
let y = {
let inner = 3;
inner + 1 // 没有分号 -> 这是块的返回值,所以 y = 4
};
println!("块表达式的值 y = {}", y);
// 对比:if 也是表达式,可以直接赋值
let condition = true;
let number = if condition { 5 } else { 6 };
println!("if 表达式赋值 number = {}", number);
// ------------------------------------------------------------------
// 5. 多返回值:用元组
// ------------------------------------------------------------------
let (q, r) = divmod(17, 5);
println!("17 除以 5: 商={}, 余={}", q, r);
// ------------------------------------------------------------------
// 6. 提前返回:return 关键字
// ------------------------------------------------------------------
println!("abs_value(-8) = {}", abs_value(-8));
println!("abs_value(8) = {}", abs_value(8));
// ------------------------------------------------------------------
// 7. 函数作为值(函数指针)
// ------------------------------------------------------------------
// 函数本身也有类型,可以赋值给变量、当作参数传递。
let f: fn(i32, i32) -> i32 = add;
println!("通过函数指针调用: {}", f(10, 20));
// 把函数传给另一个函数
let result = apply(add, 6, 7);
println!("apply(add, 6, 7) = {}", result);
// ------------------------------------------------------------------
// 8. 闭包(Closure)简介
// ------------------------------------------------------------------
// 闭包是可以捕获其所在环境的匿名函数,用 |参数| 表达式 的形式写。
let multiplier = 3;
let multiply = |n: i32| n * multiplier; // 捕获了外部变量 multiplier
println!("闭包 multiply(5) = {}", multiply(5));
// 闭包常用于把行为传给函数(后面集合那章会大量用到)
let nums = [1, 2, 3, 4];
let doubled: Vec<i32> = nums.iter().map(|x| x * 2).collect();
println!("用闭包把每个元素翻倍: {:?}", doubled);
}
// ----------------------------------------------------------------------------
// 下面是各个被调用的函数定义
// ----------------------------------------------------------------------------
// 无参数、无返回值。无返回值其实是返回单元类型 ()。
fn say_hello() {
println!("你好,Rust!");
}
// 带两个参数。注意每个参数都标注了类型。
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("测量值是: {}{}", value, unit_label);
}
// 有返回值的函数:用 -> 标注返回类型。
// 函数体最后一个表达式(无分号)就是返回值。
fn five() -> i32 {
5 // 没有分号,作为返回值
}
// 两数相加。最后一行是表达式,作为返回值。
fn add(a: i32, b: i32) -> i32 {
a + b
}
// 返回多个值:打包成元组返回。
fn divmod(a: i32, b: i32) -> (i32, i32) {
(a / b, a % b)
}
// 使用 return 提前返回(常配合条件判断)。
fn abs_value(n: i32) -> i32 {
if n < 0 {
return -n; // 提前返回,用 return 时通常带分号
}
n // 正常情况下的返回值
}
// 高阶函数:接收一个函数作为参数。
// 参数 op 的类型是 "接收两个 i32、返回 i32 的函数"。
fn apply(op: fn(i32, i32) -> i32, a: i32, b: i32) -> i32 {
op(a, b)
}
流程控制
- if / else if / else
- if 作为表达式
- loop 无限循环 + break 返回值
- while 条件循环
- for 遍历(配合 Range、集合)
- 循环标签(loop labels)
rs
fn main() {
// ==================================================================
// 一、if / else
// ==================================================================
let number = 7;
// if 的条件【必须】是 bool 类型。
// Rust 不会把非布尔值自动当真假,例如 `if number` 是编译错误。
if number < 5 {
println!("条件为真:number 小于 5");
} else {
println!("条件为假:number 不小于 5");
}
// 多重条件:else if
let n = 6;
if n % 4 == 0 {
println!("{} 能被 4 整除", n);
} else if n % 3 == 0 {
println!("{} 能被 3 整除", n);
} else if n % 2 == 0 {
println!("{} 能被 2 整除", n);
} else {
println!("{} 不能被 4、3、2 整除", n);
}
// ------------------------------------------------------------------
// if 是表达式,可以直接给变量赋值
// ------------------------------------------------------------------
let condition = true;
// 注意:if 和 else 两个分支返回的类型必须相同。
let value = if condition { 5 } else { 6 };
println!("if 表达式赋值: value = {}", value);
// ==================================================================
// 二、loop:无限循环
// ==================================================================
// loop 会一直循环,直到遇到 break。
let mut counter = 0;
// loop 也是表达式:break 后面可以跟一个值,作为整个 loop 的返回值。
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // 跳出循环并返回 20
}
};
println!("loop 的返回值: {}", result);
// continue:跳过本次循环剩余部分,直接进入下一次
let mut i = 0;
let mut sum_of_odds = 0;
loop {
i += 1;
if i > 10 {
break;
}
if i % 2 == 0 {
continue; // 偶数跳过
}
sum_of_odds += i;
}
println!("1~10 中奇数之和: {}", sum_of_odds);
// ==================================================================
// 三、while:条件循环
// ==================================================================
// 条件为真时继续循环,条件为假时结束。
let mut count = 3;
while count != 0 {
println!("倒计时: {}", count);
count -= 1;
}
println!("发射!🚀");
// ==================================================================
// 四、for:遍历
// ==================================================================
// for 是 Rust 中最常用、最安全的循环方式。
// 4.1 遍历数组/集合元素
let arr = [10, 20, 30, 40, 50];
for element in arr {
println!("数组元素: {}", element);
}
// 4.2 配合 Range 遍历数字区间
// 1..4 表示 1, 2, 3(不含右端 4)
// 1..=4 表示 1, 2, 3, 4(含右端,注意 = 号)
for x in 1..4 {
println!("1..4 -> {}", x);
}
for x in 1..=3 {
println!("1..=3 -> {}", x);
}
// 4.3 反向遍历:.rev()
for x in (1..4).rev() {
println!("反向 -> {}", x);
}
// 4.4 带索引遍历:.enumerate() 返回 (索引, 值)
let letters = ['a', 'b', 'c'];
for (index, letter) in letters.iter().enumerate() {
println!("索引 {} 对应字母 {}", index, letter);
}
// 4.5 用 step_by 控制步长(每隔几个取一个)
for x in (0..10).step_by(2) {
print!("{} ", x); // 0 2 4 6 8
}
println!();
// ==================================================================
// 五、循环标签(Loop Labels)
// ==================================================================
// 当循环嵌套时,break / continue 默认只作用于【最内层】循环。
// 想跳出外层循环,给循环加一个标签(以 ' 开头),然后 break '标签。
let mut total = 0;
'outer: for i in 1..=5 {
for j in 1..=5 {
if i * j > 6 {
println!("i={}, j={} 时 i*j>6,跳出外层循环", i, j);
break 'outer; // 直接跳出外层 'outer 循环
}
total += i * j;
}
}
println!("累加结果: {}", total);
}
所有权
- Rust 中每一个【值】都有一个【所有者】(owner)。
- 同一时刻,值【只能有一个】所有者。
- 当所有者离开作用域时,这个值就会被【自动释放】(drop)。
rs
fn main() {
// ==================================================================
// 一、作用域和自动释放
// ==================================================================
// 这里之所以为放在堆,是因为大小可能改变的数据,数据本体一定在堆上,实际是分配了额一个固定大小的栈身份卡指向堆。
{
let s = String::from("hello"); // s 进入作用域,在堆上分配内存
println!("作用域内: {}", s);
} // s 离开作用域,Rust 自动调用 drop 释放内存,无需手动 free
}
启动入口
Rust 可执行程序最终需要一个入口函数(通常是 main),但它不一定在 src/main.rs 中;src/main.rs 只是 Cargo 的默认入口位置。