本文围绕 Rust 基础语法展开,涵盖变量、数据类型、函数、控制流等核心概念,还介绍了所有权、结构体、枚举、模式匹配、错误处理、生命周期及重影等特色内容。Rust 是强类型且静态类型语言,变量默认不可变需用
mut声明,支持自动类型推断也可指定类型。其独特的所有权机制保障内存安全,通过借用和引用实现数据共享,生命周期确保引用有效。结构体可自定义类型,枚举描述多种可能类型,模式匹配高效处理枚举值,Result 和 Option 枚举助力错误处理,重影则允许变量名复用且能改变类型与属性,这些内容共同构成了 Rust 基础且关键的语法体系。

变量、基本类型、函数、注释和控制流------这些概念几乎是所有编程语言的"通用配置"。而在 Rust 里,这些基础概念会贯穿每一个程序的编写过程,早点把它们吃透,后续学 Rust 就能少走很多弯路,上手速度也会快不少。
一、变量
首先得说清楚,Rust 是一门强类型语言,但它有个方便的特点:能自动判断变量类型。这点很容易让人误以为它是弱类型语言,实际可不一样。
另外,Rust 里的变量默认是"不可变"的,要是想让变量能改值,必须用 mut 关键字声明。比如这样写:
plain
let a = 123; // 不可变变量,值定了就改不了
let mut b = 10; // 可变变量,后续能改它的值
你看,声明变量必须用 let 关键字。对于只学过 JavaScript 的开发者来说,可能觉得这写法挺熟悉;但要是只接触过 C 语言,估计会纳闷"怎么这么写"。
而且,一旦用 let a = 123; 声明了变量,下面这三行代码全都会报错,一个都跑不了:
plain
a = "abc";
a = 4.56;
a = 456;
第一行错在哪儿?因为 a 被声明为 123 后,类型就固定成整型了,你再把字符串赋值给它,类型不匹配,肯定不行。
第二行的问题是啥?Rust 不允许"会损失精度"的自动类型转换。4.56 是浮点数,把它赋给整型的 a,精度会受影响,所以语言本身就禁止这种操作。
第三行就更有意思了------a 不是变量吗?怎么连值都改不了?这就得说到 Rust 为了"高并发安全"做的设计了:它从语言层面尽量减少变量值被随意修改的可能,所以 a 虽然叫变量,但默认是不可变的。不过别误会,这并不意味着 a 不是"变量"(英文里的 variable),Rust 官方文档里,这种变量就叫"不可变变量"。
为啥要这么设计呢?你想啊,如果程序里一部分代码是"假设某个值永远不变"才写的,可另一部分代码悄悄改了这个值,那前一部分代码很可能就跑不出预期的结果。而且这种错误事后排查起来特别麻烦,Rust 这么做,就是为了从根源上减少这类问题。
当然,想让变量能改值也简单,加个 mut 关键字就行。比如这样写:
plain
let mut a = 123;
a = 456;
这样的代码就能正常编译运行,完全没问题。
常量与不可变变量的区别
既然不可变变量不能改值,那它不就和常量一样了吗?为啥还叫"变量"呢?其实两者差别不小。
在 Rust 里,下面这段代码是合法的,虽然编译器可能会警告"变量没被使用",但能通过编译:
plain
let a = 123;
let a = 456;
可要是把 a 定义成常量,再这么写就不行了:
plain
const a: i32 = 123;
let a = 456; // 报错,常量不能这么重新定义
简单说,变量支持"重新绑定"------同一个名字能重新代表另一个变量,但在重新绑定之前,它的值不能私自改。这样设计有个好处:编译器能在每一次"绑定"后的代码区域里,更精准地推理程序逻辑,减少潜在问题。
另外,虽然 Rust 能自动判断变量类型,但有些时候明确写清楚类型会更方便。比如:
plain
let a: u64 = 123;
这里就明确定义 a 是无符号 64 位整型变量。要是不写类型,Rust 会自动把 a 判断成有符号 32 位整型,这两种类型的取值范围差得可大了,后续用的时候很可能出问题。

二、数据类型
前面也提过,Rust 是静态类型语言------变量声明时可以明确指定类型,不过大多数时候不用麻烦,靠它的类型推断功能就行。
常用的基本类型有这些:
i32:32 位有符号整数u32:32 位无符号整数f64:64 位浮点数bool:布尔类型(只有 true 和 false 两个值)char:字符类型
举几个例子,一看就懂:
plain
let x: i32 = 42; // 32位有符号整数,值是42
let y: f64 = 3.14; // 64位浮点数,值是3.14
let is_true: bool = true; // 布尔类型,值是true
let letter: char = 'A'; // 字符类型,值是'A'

三、函数
在 Rust 里定义函数,得用 fn 关键字,函数的返回类型则通过箭头 -> 来指定。
比如写一个简单的加法函数:
plain
fn add(a: i32, b: i32) -> i32 {
a + b // 函数返回值,不用写return,最后一行表达式的结果就是返回值
}
要是函数不需要返回值,那返回类型就默认是 (),也就是"空元组",这种情况不用特意写返回类型。

四、控制流
控制流就是程序执行的"路线",Rust 里常用的有 if 表达式、loop 循环、while 循环和 for 循环,咱们一个个看。
if 表达式
if 表达式用来做"分支判断",比如判断一个数的大小:
plain
let number = 7;
if number < 5 {
println!("小于 5");
} else {
println!("大于等于 5");
}
这里 number 是 7,大于 5,所以会打印"大于等于 5"。

loop 循环
loop 就是 Rust 里的"无限循环",要是不主动退出,它会一直跑。想退出循环,用 break 关键字就行。比如这样写一个计数器:
plain
let mut counter = 0;
loop {
counter += 1; // 每次循环让计数器加1
if counter == 10 { // 当计数器等于10时
break; // 退出循环
}
}
这段代码跑下来,counter 会从 0 加到 10,然后循环就停了。

while 循环
while 循环适合"满足条件就继续跑"的场景,条件不满足了就退出。比如倒计时:
plain
let mut number = 3;
while number != 0 { // 只要number不等于0,就继续循环
println!("{}!", number);
number -= 1; // 每次循环让number减1
}
运行后会依次打印"3!""2!""1!",等 number 变成 0,循环就结束了。

for 循环
for 循环在遍历序列的时候特别好用,比如遍历一个范围:
plain
for number in 1..4 { // 遍历1到3(注意:1..4是左闭右开,不包含4)
println!("{}!", number);
}
这段代码会打印"1!""2!""3!",简洁又不容易出错,比手动控制计数器方便多了。

五、所有权(Ownership)
所有权是 Rust 独有的内存管理机制,也是它的核心特性之一。想搞懂所有权,得先明白三个关键概念:所有权(ownership)、借用(borrowing)和引用(reference)。
所有权规则
- Rust 里每个值都有一个"所有者"------就是哪个变量持有这个值。
- 每个值在同一时间只能有一个所有者,不能多个人"同时持有"。
- 当所有者超出它的"作用域"(比如函数执行完、代码块结束),这个值就会被自动删除,释放内存。
举个例子:
plain
let s1 = String::from("hello");
let s2 = s1; // 这里s1的所有权被"转移"给了s2,s1再也不能用了
// println!("{}", s1); // 这里编译会报错,因为s1已经没有这个值的所有权了
借用和引用
要是不想转移所有权,只是想临时用一下某个值,该怎么办?这时候就需要"借用"了。借用能让你引用数据,但不获取所有权,用 & 符号就能实现。
比如写一个计算字符串长度的函数:
plain
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // 这里用&s,就是"借用"s的值,不拿所有权
println!("The length of '{}' is {}.", s, len); // 用完还能正常用s,没问题
}
fn calculate_length(s: &String) -> usize {
s.len() // 函数里只能用s的值,不能改它,也拿不到所有权
}

六、结构体(Structs)
结构体的作用是"创建自定义类型",它里面的字段可以包含多种不同的数据类型,特别适合用来描述一个"有多个属性的事物"。
比如定义一个"用户"结构体:
plain
struct User {
username: String, // 用户名,字符串类型
email: String, // 邮箱,字符串类型
sign_in_count: u64, // 登录次数,无符号64位整型
active: bool, // 是否活跃,布尔类型
}
// 创建一个具体的用户实例
let user1 = User {
username: String::from("someusername"),
email: String::from("someone@example.com"),
sign_in_count: 1,
active: true,
};
这样一来,user1 就是一个 User 类型的变量,包含了用户的所有信息,用起来很清晰。

七、枚举(Enums)
枚举的作用是定义"一个值可能是几种类型中的一种",不用像结构体那样包含多个字段,更适合描述"不同的选项"。
比如定义 IP 地址的类型(IPv4 和 IPv6):
plain
enum IpAddrKind {
V4, // IPv4类型
V6, // IPv6类型
}
// 创建枚举实例
let four = IpAddrKind::V4; // four是IpAddrKind::V4类型
let six = IpAddrKind::V6; // six是IpAddrKind::V6类型
这样就能明确区分不同的 IP 类型,后续处理的时候也不容易混淆。
八、模式匹配(match)
match 是 Rust 里特别强大的控制流工具,有点像其他语言里的 switch 语句,但功能比 switch 强多了。它能帮你"匹配"枚举的不同类型,然后执行对应的代码。
比如定义一个"硬币"枚举,然后写个函数计算硬币的面值:
plain
enum Coin {
Penny, // 1美分硬币
Nickel, // 5美分硬币
Dime, // 10美分硬币
Quarter, // 25美分硬币
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1, // 匹配到Penny,返回1
Coin::Nickel => 5, // 匹配到Nickel,返回5
Coin::Dime => 10, // 匹配到Dime,返回10
Coin::Quarter => 25, // 匹配到Quarter,返回25
}
}
调用这个函数的时候,传不同的 Coin 实例,就能得到对应的面值,逻辑特别清楚。
九、错误处理
在 Rust 里处理错误,主要靠两种方式:Result<T, E> 和 Option<T>。它们能帮你明确处理"可能出错"的情况,避免程序崩溃。
Result
Result<T, E> 是个枚举,专门用来表示"操作成功"或"操作失败"。T 是成功时返回的值的类型,E 是失败时返回的错误类型。它的定义大概是这样的:
plain
enum Result<T, E> {
Ok(T), // 成功,里面存着成功的值
Err(E), // 失败,里面存着错误信息
}
比如写一个除法函数,要是除数为 0 就返回错误:
plain
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("Division by zero")) // 除数为0,返回错误信息
} else {
Ok(a / b) // 计算成功,返回结果
}
}
Option
Option<T> 也是个枚举,用来表示"有值"或"没值",避免出现空指针错误。它的用法很简单,比如写一个"从数组里取元素"的函数:
plain
fn get_element(index: usize, vec: &Vec<i32>) -> Option<i32> {
if index < vec.len() {
Some(vec[index]) // 索引合法,返回"有值",里面存着数组元素
} else {
None // 索引越界,返回"没值"
}
}
调用这个函数时,你能清楚知道"到底有没有取到元素",不用猜来猜去。
十、所有权与借用的生命周期
前面说了借用和引用,但还有个问题:怎么确保引用一直是"有效的"?不会出现"引用了一个已经被删除的值"的情况?这就需要"生命周期"了。
Rust 用生命周期标注来管理引用的有效性,标注的时候用 'a 这样的符号。不过大多数时候不用手动写,编译器会自动推断。
比如写一个"返回两个字符串中较长那个"的函数:
plain
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
这里的 'a 就是生命周期标注,它告诉编译器:x、y 这两个引用,还有函数的返回值,它们的生命周期是一样的。这样编译器就能确保,返回的引用不会在 x 或 y 被删除后还被使用。
十一、重影(Shadowing)
最后咱们聊聊"重影"。它和面向对象语言里的"重写"(Override)或者"重载"(Overload)完全不是一回事。其实前面说的"变量重新绑定",指的就是重影------简单说,就是同一个变量名可以被重新使用,代表另一个变量。
举个例子:
plain
fn main() {
let x = 5;
let x = x + 1; // 重新绑定x,现在x是6
let x = x * 2; // 再一次重新绑定x,现在x是12
println!("The value of x is: {}", x); // 打印结果是12
}
这段程序跑下来,输出就是"The value of x is: 12"。

这里要注意,重影和"可变变量赋值"不是一回事。重影是"用同一个名字重新代表另一个变量",新变量的类型、是否可变、值都能变;但可变变量赋值只能改"值",类型和可变属性是不能变的。
比如下面这段代码就会报错:
plain
let mut s = "123"; // s是可变字符串变量
s = s.len(); // 报错!不能给字符串变量赋整型值
s 本来是字符串类型,就算加了 mut 能改值,也不能把 s.len()(整型)赋值给它------类型不匹配,重影能做的事,可变变量可做不到。
十二、总结
到这里,咱们已经把 Rust 最核心的基础语法过了一遍。从变量的"可变与不可变"设计,到数据类型的精准区分;从函数的定义逻辑,到控制流的灵活运用;再到 Rust 独有的所有权、借用、生命周期,以及自定义类型的结构体与枚举------这些内容不只是孤立的知识点,更是构成 Rust 安全、高效特性的基石。
比如变量默认不可变与所有权机制,本质上是 Rust 从语言层面帮我们规避并发安全和内存泄漏问题;重影与可变变量的区别、模式匹配的灵活用法,则体现了它在"严谨性"和"易用性"之间的平衡。而错误处理的 Result 和 Option 枚举,更是让我们能写出更健壮、可维护的代码,避免了很多隐性 bug。
这些基础概念就像搭建房子的砖块,可能刚开始接触时会觉得"规矩有点多",比如所有权转移、生命周期标注这些设计,和其他语言的习惯不太一样。但只要多动手写代码、多琢磨每段代码背后的逻辑,慢慢就能体会到这些设计的巧妙之处。后续再学习 Rust 更复杂的特性(比如并发、Trait、泛型等)时,你会发现这些基础打得越牢,上手后续内容就越轻松。所以建议大家把这些基础语法多练几遍,用小例子验证每个知识点,真正吃透它们,再往下进阶就会顺理成章啦!