Rust学习教程2:基本语法

🦀 Rust学习教程2:基础语法

面向 C++ 工程师的 Rust 基础语法精讲:变量与类型、函数、控制流,辅以与 C++ 的对比、FAQ、练习与总结。读完即可动手写简单程序。


📑 目录

  • [零、🚀 从 Hello World 开始](#零、🚀 从 Hello World 开始)
  • [一、📦 变量与类型](#一、📦 变量与类型)
  • [二、⚙️ 函数](#二、⚙️ 函数)
  • [三、🔄 控制流](#三、🔄 控制流)
  • [四、💬 表达式与语句](#四、💬 表达式与语句)
  • [五、🆚 与 C++ 的区别](#五、🆚 与 C++ 的区别)
  • [六、❓ FAQ 常见问题](#六、❓ FAQ 常见问题)
  • [七、✏️ 练习](#七、✏️ 练习)
  • [八、✅ 总结](#八、✅ 总结)

零、🚀 从 Hello World 开始

确保已安装 rustup 和 Cargo,在终端执行:

bash 复制代码
cargo new rust_basics
cd rust_basics

src/main.rs 中已有默认入口。本教程涉及的语法都可在该项目的 main.rs 或新文件中练习。

rust 复制代码
fn main() {
    println!("Hello, world!");
}

运行:cargo run。下面所有示例均可放在 main 中或单独函数里调用。

本教程知识结构概览
🦀 Rust 基础语法
变量与类型
let / let mut
标量 整型 浮点 bool char
元组与数组
常量与遮蔽
函数
参数与返回类型
表达式返回值
发散函数
控制流
if / else 表达式
loop while for
表达式与语句
块与分号
注释与打印
📁 cargo new
✏️ 写代码
▶️ cargo run


一、📦 变量与类型

Rust 是静态类型 语言,类型在编译期确定;同时支持类型推断 ,多数情况下不必手写类型。变量默认不可变 ,需要可变时显式写 mut

1.1 🔒 变量声明:letlet mut

rust 复制代码
fn main() {
    let x = 42;           // 不可变,类型推断为 i32
    let mut y = 10;       // 可变
    y += 1;               // OK

    // x += 1;            // 编译错误:cannot assign twice to immutable variable
}

记忆要点 :默认不可变,要改就加 mut
变量
let x = 42
🔒 不可变
let mut y = 10
🔓 可变
可变何时用
需要原地修改时
let mut 显式声明
不可变的好处
编译期保证不改
便于推理与并发

1.2 📊 标量类型(Scalar)

类型 说明 示例 与 C++ 对照
整型 有符号 / 无符号,固定位宽 i32, u64, i8 int32_t, uint64_t
浮点 f32, f64 3.14_f32, 2.0 float, double
布尔 bool true, false bool
字符 char,Unicode 标量 '中', 'a' char(Rust 为 4 字节)

整型字面量:可加类型后缀与分隔符:

rust 复制代码
let a = 42_i32;           // 有符号 32 位
let b = 0xff_u8;          // 十六进制,无符号 8 位
let c = 1_000_000;        // 可读性分隔

默认推断 :未指定时,整数推断为 i32,浮点数推断为 f64
其他标量
f32 f64
bool
char
整型
i8 i16 i32 i64 i128
u8 u16 u32 u64 u128

1.3 🧩 复合类型:元组与数组

元组(Tuple):固定长度、可异构。

rust 复制代码
let t: (i32, f64, bool) = (42, 3.14, true);
let first = t.0;          // 42,通过 .0 .1 .2 访问
let (x, y, z) = t;        // 解构

数组(Array):固定长度、同类型,长度是类型的一部分。

rust 复制代码
let arr: [i32; 5] = [1, 2, 3, 4, 5];   // 类型为 [i32; 5]
let zeros = [0; 10];                    // [0, 0, 0, ...],10 个 0
let first = arr[0];                     // 下标访问,越界会 panic

与 C++ 对照:Rust 的数组更像 std::array<T, N>,长度固定;动态数组用 Vec<T>(后续阶段会学)。
复合
(T, U, ...) 元组
T; N 数组
标量
i8..i128, u8..u128
f32, f64
bool, char
类型

1.4 🌑 常量与变量遮蔽(含作用域)

常量(const):编译期确定,必须带类型,命名习惯全大写+下划线。

rust 复制代码
const MAX_POINTS: u32 = 100_000;

变量遮蔽(shadowing) :用 let 再次声明同名变量,新变量可换类型、可覆盖旧值,与 mut 不同(mut 是原地改,不换类型)。

官方书中的要点:第二个变量会「遮蔽」第一个,直到它自己再被遮蔽或所在作用域结束;离开内层作用域后,外层绑定重新可见。

下面用 println! 演示:同一作用域内遮蔽、以及内层作用域遮蔽后离开作用域的情况。

rust 复制代码
fn main() {
    let x = 5;
    println!("1. 外层 x = {}", x);   // 1. 外层 x = 5

    let x = x + 1;                    // 同一作用域内遮蔽,x 变为 6
    println!("2. 遮蔽后 x = {}", x);  // 2. 遮蔽后 x = 6

    {
        let x = x * 2;                // 内层作用域遮蔽:新 x = 12
        println!("3. 内层作用域 x = {}", x);  // 3. 内层作用域 x = 12
    }   // 内层 x 离开作用域,被丢弃

    println!("4. 离开内层后 x = {}", x);  // 4. 离开内层后 x = 6
}

运行输出

text 复制代码
1. 外层 x = 5
2. 遮蔽后 x = 6
3. 内层作用域 x = 12
4. 离开内层后 x = 6

作用域与遮蔽关系(谁可见、何时恢复):
内层作用域 x 外层作用域 x 内层作用域 x 外层作用域 x let x = 5 let x = x + 1 let x = x * 2 (遮蔽) 块结束,内层 x 丢弃 println → 5 println → 6 println → 12 println → 6 (外层 x 重新可见)
内层块 {}
外层
x = 5
let x = x+1 → x = 6
离开内层后 x 仍是 6
let x = x*2 → x = 12

与 C++ 对比:C++ 没有「同一作用域内同名遮蔽」的惯用写法;Rust 的 shadowing 常用于转换类型或中间变量,且内层遮蔽不会改变外层的绑定,离开内层后外层变量恢复可见。

换类型的典型用法(仍用官方书例子):

rust 复制代码
fn main() {
    let spaces = "   ";
    let spaces = spaces.len();   // 遮蔽并换类型:&str → usize
    println!("spaces 数量: {}", spaces);  // 3
}

1.5 ✍️ 类型注解何时需要写?

编译器能推断时可以不写;以下情况建议或必须写:

  • 函数参数与返回值(见下一节)
  • 无法从上下文推断时(如 let v: Vec<i32> = vec![];
  • 提高可读性时
rust 复制代码
let x = 42;                    // 推断为 i32
let f = 3.14;                  // 推断为 f64
let explicit: u64 = 100;       // 显式指定 u64



有足够上下文?
可省略类型
必须写类型
函数参数/返回值/空容器等


二、⚙️ 函数

Rust 用 fn 定义函数,参数和返回值类型必须写;最后一行的表达式 若无分号则作为返回值(无需写 return)。

2.1 📌 基本形态

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    a + b    // 表达式,无分号,即返回值
}

fn with_return(x: i32) -> i32 {
    if x > 0 {
        return x * 2;   // 提前返回时用 return
    }
    0
}

要点

  • 参数:名字: 类型,多个用逗号分隔。
  • 返回值:-> 类型;无返回值可省略或写 -> ()
  • 最后一行为表达式(无分号)时,该表达式的值即为函数返回值。

fn name
参数: 类型
-> 返回类型
函数体
最后表达式 = 返回值
无分号 表达式
有 return
分号/语句
函数调用
求值参数
执行函数体
最后一行
该值即返回值
返回

2.2 💡 表达式与语句(为何无分号就能返回?)

Rust 中:

  • 语句:执行操作,不产生值,以分号结尾。
  • 表达式 :产生值;若在函数体末尾且没有分号,该值就是函数返回值。
rust 复制代码
fn demo() -> i32 {
    let x = 1;           // 语句
    let y = {            // 块也是表达式
        let z = 2;
        z + 1             // 块的值是 3,无分号
    };
    y + x                 // 函数返回值 4,无分号
}

所以「无 return 也能返回」是因为最后一行是表达式。

2.3 ♾️ 发散函数(永不返回)

若函数永不返回(如无限循环或 panic!),返回类型写 !(never type):

rust 复制代码
fn forever() -> ! {
    loop {}
}

2.4 📋 小结速查

写法 含义
fn f(x: i32) -> i32 { x } 单参数,返回最后一行的值
fn f() -> () { }fn f() { } 无返回值
return x; 提前返回,可写可不写(末尾表达式更常见)

三、🔄 控制流

Rust 提供 if/elseloopwhilefor,且 if 和块都是表达式,可赋值、可返回。

3.1 🔀 if / else

条件必须是 bool,不能像 C++ 那样用整数或指针。

rust 复制代码
let n = 5;
if n < 0 {
    println!("negative");
} else if n == 0 {
    println!("zero");
} else {
    println!("positive");
}

if 表达式:分支可产生值,类型须一致。

rust 复制代码
let result = if n > 0 { "positive" } else { "non-positive" };
// result 为 &str

true
false


条件 bool
分支 1
下一条件?
else if
else
结果

3.2 🔁 loop:无限循环

loop 相当于 while true,必须用 breakreturn 退出;break 可带返回值(常用于重试直到成功)。

rust 复制代码
let mut counter = 0;
let result = loop {
    counter += 1;
    if counter == 10 {
        break counter * 2;   // 跳出并带出值 20
    }
};



进入 loop
执行循环体
break?
break 可带值
loop 表达式得到该值

3.3 ⏳ while

与 C++ 类似,条件为 bool

rust 复制代码
let mut n = 3;
while n > 0 {
    println!("{}", n);
    n -= 1;
}

3.4 🏃 for:迭代器驱动

Rust 的 for 用于遍历迭代器,最常见的是范围与集合:

rust 复制代码
// 范围:1..5 表示 1,2,3,4(不包含 5)
for i in 1..5 {
    println!("{}", i);
}

// 包含上界:1..=5 表示 1 到 5
for i in 1..=5 {
    println!("{}", i);
}

// 遍历数组
let arr = [10, 20, 30];
for x in arr {
    println!("{}", x);
}

// 需要下标时
for (i, x) in arr.iter().enumerate() {
    println!("index {}: {}", i, x);
}

与 C++ 对比:更像「范围 for」for (auto x : range),而不是 C 风格 for (int i=0; i<n; i++)
循环
loop
break/return
while 条件
循环体
for x in 迭代器
循环体
1..5 或 1..=5
产生迭代项
for 绑定变量
执行循环体

3.5 📊 控制流小结

构造 用途
if / else if / else 条件分支,可作表达式
loop 无限循环,break 可带值
while 条件 条件循环
for x in 迭代器 遍历,推荐优先使用

四、💬 表达式与语句

理解「表达式 vs 语句」能减少「为什么这里不能加分号」的困惑。
语句
不产生值
let x=1;, 带分号的表达式
表达式
产生值
1+2, if/else 块, 块最后一行无分号
代码

概念 是否产生值 典型例子
语句 let x = 1;println!(...);; 结尾的表达式
表达式 1 + 2if x { 1 } else { 0 }、块的最后一行(无分号)

块(block){ ... } 也是表达式,块的值 = 最后一条表达式的值(最后一行无分号)。

rust 复制代码
let y = {
    let x = 1;
    x + 1    // 块的值是 2
};

无分号
有分号
块 { ... }
最后一行
块的值 = 该表达式
块的值 = ()

4.1 📝 注释与打印

注释 :与 C++ 类似,// 行注释,/* ... */ 块注释;文档注释用 ///(项上方)或 //!(模块/ crate 级别)。

rust 复制代码
// 行注释
/* 块注释 */
/// 文档注释:说明下面函数的作用
fn f() {}

打印 :常用宏 println!print!eprintln!(标准错误);{} 为 Display,{:?} 为 Debug(调试输出),{:#?} 为带缩进的 Debug。

rust 复制代码
let x = 42;
println!("x = {}", x);
println!("debug: {:?}", (1, true));

五、🆚 与 C++ 的区别

下表按「变量与类型、函数、控制流」做对照,便于从 C++ 迁移。

主题 C++ Rust
变量默认 可改 默认不可变,需 let mut 才可改
类型 可隐式转换、可省略(auto) 强类型,推断但少隐式转换
数组 T[] / std::array<T,N> [T; N] 固定长度,越界 panic
函数返回 必须 return 或构造返回值 最后表达式无分号即返回值
条件 可用整数、指针等 条件必须是 bool
循环 for(;;) / while / 范围 for loop / while / for x in 迭代器
无 return void 函数或单表达式函数 任意函数都可用末尾表达式返回值

思维差异

  • C++:默认可变、多种所有权( raw 指针/引用/智能指针)。
  • Rust:默认不可变、单一所有权,后续会学到借用与生命周期。


Rust 习惯
默认不可变
强类型 少隐式
表达式即返回
C++ 习惯
默认可变
隐式转换
return 显式


六、❓ FAQ 常见问题

Q1:为什么 let x = 1; 后面不能写 x = 2

因为变量默认不可变。需要可改就写 let mut x = 1;

Q2:函数最后一行加了分号,为什么报错「expected type, found ()」?

加分号后那一行变成语句,不产生值,相当于返回 ()。若函数声明返回 i32 等类型,类型不匹配就会报错。解决:去掉最后一行的分号,或显式 return 值;

Q3:1..51..=5 有什么区别?
1..5 是左闭右开,即 1,2,3,4;1..=5 包含右端,即 1,2,3,4,5。

Q4:能否像 C++ 一样写 for (int i = 0; i < n; i++)

可以但不推荐。需要索引时用 for i in 0..n { ... },或 iter().enumerate()。Rust 更推荐迭代器写法。

Q5:loopwhile true 有区别吗?

语义上类似。loop 是专门语法,且 break 可带值(如 break 42),写法更简洁。

Q6:数组越界会怎样?

运行时 panic,程序终止。编译期仅在部分常量下标时能做检查。

Q7:如何指定整数类型避免推断成 i32?

字面量加后缀,如 0_u8100_i64,或 let x: u64 = 100;

Q8:变量遮蔽后,离开内层作用域会怎样?

内层遮蔽的变量随块结束被丢弃,外层的同名绑定重新可见(见 [1.4 变量遮蔽](#1.4 变量遮蔽) 的 println! 示例)。


七、✏️ 练习

建议在 Rust Playground 或本地 cargo new 项目中完成。

7.1 📦 变量与类型

  1. 声明不可变变量 pi: f64 = 3.14159,再声明可变变量 count: i32,在循环中自增到 5 并打印。
  2. 定义一个长度为 4 的数组,用 for 遍历并打印每个元素。
  3. 定义一个元组 (i32, bool, &str),分别用 .0.1.2 和解构两种方式取出值并打印。
  4. 遮蔽与作用域 :在外层定义 let x = 10,在内层块中 let x = x * 2 并打印,离开内层后再打印 x,验证外层 x 仍为 10。

7.2 ⚙️ 函数

  1. 写函数 fn area_rect(w: f64, h: f64) -> f64,返回矩形面积。
  2. 写函数 fn is_even(n: i32) -> bool,不用 return,用最后一行表达式返回。
  3. 写一个返回 () 的函数,内部用 loopbreak 提前退出。

7.3 🔄 控制流

  1. if 表达式实现:若 n > 0 返回 "positive",否则返回 "non-positive",结果存到变量并打印。
  2. loop + break 实现:从 1 累加,直到和大于 100,break 时带出当前和并打印。
  3. for i in 1..=10 打印 1 到 10;再用 for (i, v) in [10,20,30].iter().enumerate() 打印下标与值。

7.4 🎯 综合

  1. 温度转换 :实现 fn celsius_to_fahrenheit(c: f64) -> f64,并在 main 里对 0、100 调用并打印结果。

完成后可配合 Rustlingsvariablesfunctionsifprimitive_types 巩固。


八、✅ 总结

  • 变量与类型let / let mut,标量(整型/浮点/布尔/字符)+ 元组与固定长度数组;默认不可变、强类型;遮蔽在内层作用域结束后恢复外层绑定。
  • 函数fn 名(参数: 类型) -> 返回类型,最后一行的表达式(无分号)即返回值。
  • 控制流if(可作表达式)、loop(可 break 带值)、whilefor x in 迭代器;条件必须是 bool
  • 与 C++:默认不可变、无隐式类型当条件、数组固定长度、用表达式返回值。
  • 下一步 :掌握这些后,进入所有权、借用与生命周期(阶段 2),再配合 Rustlings 与小项目巩固。

基础语法是 Rust 的入口,习惯「默认不可变 + 表达式即值」后,后面所有权与错误处理会顺很多。建议每天写几段小代码,把本教程里的示例和练习都敲一遍。
📦 变量类型
⚙️ 函数
🔄 控制流
📖 所有权
🦀 进阶


上一篇:《Rust学习教程1:搭建Rust环境》

下一篇:正在编写...

相关推荐
Xin_ye100863 分钟前
C# 零基础到精通教程 - 第十八章:部署与发布——让应用上线
开发语言·c#
思麟呀30 分钟前
C++11并发编程:call_once一次性执行+atomic原子类型+CAS无锁编程+自旋锁
linux·开发语言·jvm·c++·windows
humors2211 小时前
学习方法的系统梳理与实践应用
学习·学习方法
码不停蹄的玄黓1 小时前
Java 生产者-消费者模型详解
java·开发语言·python
爱讲故事的1 小时前
操作系统第一讲复习:为什么学习操作系统,以及操作系统到底在做什么?
linux·开发语言·windows·学习·ubuntu·c#
笨蛋不要掉眼泪1 小时前
Java并发编程:Executors框架类深度解析
java·开发语言·并发
_童年的回忆_2 小时前
【php】在linux下PHP安装amqp扩展
linux·开发语言·php
AIMath~2 小时前
python中的uv命令揭秘
开发语言·python·uv
弹简特2 小时前
【零基础学Python】06-Python模块和包、异常处理、文件常用操作
开发语言·python
x***r1512 小时前
Postman-win64-7.2.2-Setup安装步骤详解(附API接口测试与参数配置教程)
开发语言·lua