CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Rust 语言入门/课时/基础语法
RawBlame
课时目录12 节

Rust 语言入门

  1. 01Rust 是什么
  2. 02基础语法
  3. 03所有权与借用
  4. 04生命周期
  5. 05结构体与枚举
  6. 06错误处理
  7. 07Trait 与泛型
  8. 08集合类型
  9. 09并发与 async
  10. 10宏与元编程
  11. 11Web 与生态
  12. 12综合练习

基础语法

variables、control flow、functions、基本类型。

35 分钟

基础语法

这一课你将能做到

  1. 用 let / let mut / shadowing 管理变量,理解「默认不可变」的设计
  2. 写函数、用 if / loop / while / for 组织控制流,把 if 当表达式用
  3. 完成一个统计判题耗时的 CLI 小工具

为什么要学

语法是所有后续内容的载体。Rust 的语法表面像 C,骨子里偏函数式:几乎一切都是表达式,类型推导无处不在,编译器报错友好到近乎手把手。这节课把地基打平,后面所有权、错误处理这些硬菜才咽得下去。

和 Java 对照着学最快:变量声明像 var,常量像 final,但「默认不可变」这条设计,把 Java 里靠自觉的 best practice 变成了强制。

先建立图像

Java:   int total = 0;             final int MAX = 100;
Rust:   let mut total = 0;         const MAX: u32 = 100;

Java:   var name = "judge";        // 类型推断
Rust:   let name = "judge";        // 类型推断 + 默认不可变

核心心智模型:let 绑定的是「名字到值」的关系,绑定默认不可变——想改值,显式声明 mut;想换绑新值,直接再 let(shadowing)。

跟着做

步骤 1:变量、可变性与 shadowing

cargo new basic-demo
cd basic-demo

把 src/main.rs 换成:

fn main() {
    let submit_count = 10;          // 不可变绑定
    // submit_count = 11;           // 编译错误:不可变变量不能二次赋值

    let mut total_ms = 0u64;        // mut 声明可变
    total_ms += 150;

    let submit_count = submit_count * 2;  // shadowing:换绑新值,连类型都可以变
    println!("提交数 {submit_count},总耗时 {total_ms} ms");
}

运行 cargo run。注意 shadowing 不是修改,是旧名字被新绑定遮蔽,编译器把它当两个变量。

步骤 2:基本类型与推导

fn main() {
    let a = 42;                 // 推导为 i32(整型默认)
    let b: u64 = 18_446_744_073_709_551_615;  // 显式标注,_ 是数字分隔符
    let pi = 3.14159;           // f64(浮点默认)
    let ok = true;              // bool
    let letter = '判';           // char 是 4 字节 Unicode 标量
    let pair = ("cpp", 1000);   // 元组:异构定长集合
    let arr = [3, 1, 4, 1, 5];  // 数组:定长,栈上

    let (lang, limit) = pair;   // 元组解构
    println!("{a} {b} {pi} {ok} {letter} {lang} {limit} {}", arr[2]);
}

整型家族:i8/i16/i32/i64/i128/isize 与对应 u 系列。判题内存计数、跨语言协议这类场景明确标 u64,其余交给推导。

步骤 3:函数与返回值

fn grade(score: u32) -> &'static str {
    if score >= 90 { "A" } else if score >= 60 { "P" } else { "F" }
}   // if 是表达式,不带分号的分支值直接作为返回值

fn add_time(total: &mut u32, delta: u32) {
    *total += delta;            // 不返回值时,隐式返回单元类型 ()
}

fn main() {
    let mut total = 0;
    add_time(&mut total, 120);
    add_time(&mut total, 80);
    println!("耗时 {total} ms,评级 {}", grade(95));
}

两个关键点:函数最后一行「不带分号的表达式」自动作为返回值,写了分号就变成 ();参数类型必须显式标注,且没有重载。

步骤 4:控制流全家桶

fn main() {
    // for + Range
    for case_id in 1..=3 {          // 1..=3 闭区间,1..4 半开区间
        println!("跑第 {case_id} 个样例");
    }

    // while
    let mut remaining = 8;
    while remaining > 0 { remaining /= 2; }

    // loop:能带返回值的无限循环
    let mut n = 1u64;
    let doubled = loop {
        n *= 2;
        if n > 1000 { break n; }    // break 带值,作为 loop 表达式的值
    };
    println!("{remaining} / {doubled}");
}

Rust 的 if 条件必须是严格 bool,if x = 5 这种 Java 里的手滑直接编译报错。

步骤 5:综合小工具——判题耗时统计

fn classify(avg: u64) -> &'static str {
    if avg < 100 { "S" } else if avg < 500 { "A" } else { "B" }
}

fn main() {
    let times_ms = [12, 8, 45, 3, 21];   // 各测试点耗时(毫秒)

    let mut total: u64 = 0;
    let mut max: u64 = 0;
    for &t in times_ms.iter() {
        total += t as u64;
        if t as u64 > max { max = t as u64; }
    }
    let avg = total / times_ms.len() as u64;

    println!("样本数 {}", times_ms.len());
    println!("总耗时 {total} ms,最慢单点 {max} ms,平均 {avg} ms");
    println!("性能评级:{}", classify(avg));
}

运行后改几个数字观察输出。数组的 .iter() 遍历和 as 转换先照抄,分别在第 8、9 课展开。

讲清楚要点

  1. 默认不可变:let 绑定不可变,mut 是显式 opt-in。「谁在什么时候改了数据」在代码里一眼可见,也是后续并发安全的基石。
  2. shadowing 不等于 mut:shadowing 生成新绑定,可以顺便改类型(let x = "42"; let x: u32 = x.parse().unwrap());mut 只是原地改值。
  3. 一切皆表达式:if、loop、代码块 {} 都有值。Rust 没有三元运算符,因为 let x = if c { a } else { b } 就是三元。
  4. 常量与不可变变量是两回事:const MAX: u32 = 100; 编译期内联、必须标类型;let 绑定有作用域、有所有权语义。
  5. 整型溢出:debug 构建溢出直接 panic,release 构建按位回绕。要明确语义用 checked_add / wrapping_add。

你可能会懵的地方

  • 为什么变量默认不可变? 可变状态是并发 bug 与推理负担的主要来源。默认不可变加显式 mut,让可变性成为 review 一眼能盯住的点。

  • shadowing 和 mut 怎么选? 类型不变、就地累加用 mut;需要转换类型或重新解释语义用 shadowing(先拿原始字符串再 parse 成数字,同名换绑最顺手)。

  • u32 和 u64 怎么选? 默认 i32 够用;内存字节数、时间戳、大计数用 u64。拿不准就 u64;Rust 收窄转换也必须写 as,不存在 Java 的静默截断惊喜。

  • 函数没有 return 会不会看漏返回值? Rust 惯例:最后表达式即返回值,只有提前返回才写 return。看到函数末行没分号,它就是返回值。

  • &'static str 是什么? 生命周期标注,表示这个字符串引用整个程序期间有效。所有字符串字面量都是 'static,第 4 课展开。

错法对照

错法问题正解
不可变变量二次赋值error[E0384]声明 let mut 或用 shadowing
函数末行多写分号返回 () 类型不匹配返回值表达式结尾不带分号
i32 + u64 直接混算error[E0308] 类型不匹配a as u64 + b 显式转换
if total { } 把数字当条件条件必须严格 boolif total > 0 { }
变量声明后不用warning: unused variable改名 _x 消音或删除

卡点急救

  1. 一堆 warning 但能跑:warning 不阻断运行,cargo fix 能自动修掉大部分(比如提示加 mut),保持零警告是好习惯。
  2. error[E0308] mismatched types:编译器会同时打印期望类型与实际类型,照着改即可;读懂这个错误码就学会了 Rust 排错的姿势。
  3. 数值算出来不对:检查整型除法截断(5/2 = 2)与溢出回绕;浮点比较用 (a - b).abs() < 1e-9,别用 ==。

面试常问

  • Rust 变量默认不可变带来什么好处? 可变状态被显式化,读写边界清晰,是编译期并发安全(Send/Sync 体系)的前置条件;Java 里 final 是可选的好习惯,Rust 把它升级为语言默认。

  • 「一切皆表达式」对代码风格意味着什么? 减少临时变量与命令式空转:if/match/loop 直接作为值赋给绑定,控制流与数据流合一,代码更接近「数据变换」的描述而非指令序列。

  • debug 和 release 下整型溢出行为有何不同? debug 溢出 panic(尽早暴露 bug),release 按二进制回绕(保性能)。依赖回绕语义必须显式用 wrapping_* 系列,把意图写进代码。

自测三问

  1. shadowing 和 mut 的区别是什么?各举一个适用场景。
  2. let x = if a > 0 { 1 } else { -1 }; 为什么合法?
  3. 1..4 和 1..=4 分别生成什么序列?

小结一句话

变量默认不可变、一切皆表达式、类型推导加显式边界——Rust 基础语法把 Java 的最佳实践升级成了语言强制。

延伸

  • The Rust Book ch03:通用编程概念
  • 下一课:所有权与借用——Rust 的灵魂,也是与 Java 差异最大的部分。
加载本节练习…
← 上一课Rust 是什么下一课 →所有权与借用