Rust 系统编程语言完全教程 / 第04章:变量与数据类型基础
第04章:变量与数据类型基础
4.1 变量绑定(Variable Binding)
默认不可变
Rust 中使用 let 声明变量,默认不可变(immutable):
fn main() {
let x = 5;
println!("x = {}", x);
// x = 6; // ❌ 编译错误:cannot assign twice to immutable variable
}
注意: Rust 称之为"变量绑定"(binding)而非"变量赋值"(assignment),因为
let x = 5是将值5绑定到名字x上,而非简单的赋值操作。
为什么默认不可变
| 特性 | 默认不可变 | 默认可变(如 Python/JS) |
|---|---|---|
| 安全性 | 防止意外修改 | 可能被意外修改 |
| 并发 | 编译时保证无数据竞争 | 需要运行时保护 |
| 可读性 | 明确标注可变性 | 需要额外追踪 |
| 优化 | 编译器可做更多优化 | 优化空间有限 |
4.2 可变变量(mut)
使用 mut 关键字声明可变变量:
fn main() {
let mut x = 5;
println!("x = {}", x);
x = 6; // ✅ 使用 mut 声明的变量可以修改
println!("x = {}", x);
x += 1;
println!("x = {}", x); // x = 7
}
可变性的边界
fn main() {
let mut s = String::from("hello");
// ✅ 可以修改内容
s.push_str(", world");
println!("{}", s);
// ✅ 可以重新绑定(指向新的 String)
s = String::from("new string");
println!("{}", s);
// ❌ 不能改变类型(类型在第一次绑定时确定)
// s = 42; // 类型不匹配
}
实际应用:累加器
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let mut sum = 0; // 必须使用 mut,因为值会变化
for num in &numbers {
sum += num;
}
println!("总和: {}", sum); // 总和: 15
}
4.3 变量遮蔽(Shadowing)
基本遮蔽
使用 let 重新声明同名变量,遮蔽(shadowing) 之前的变量:
fn main() {
let x = 5;
println!("x = {}", x); // x = 5
let x = x + 1; // 遮蔽,创建新变量 x
println!("x = {}", x); // x = 6
let x = x * 2; // 再次遮蔽
println!("x = {}", x); // x = 12
}
类型变换遮蔽
与 mut 不同,遮蔽可以改变变量类型:
fn main() {
let spaces = " "; // &str 类型
let spaces = spaces.len(); // usize 类型
println!("空格数量: {}", spaces); // 空格数量: 3
// 如果用 mut:
// let mut spaces = " ";
// spaces = spaces.len(); // ❌ 编译错误:类型不匹配
}
遮蔽 vs mut
| 特性 | 遮蔽 (Shadowing) | mut |
|---|---|---|
| 关键字 | let | let mut |
| 可变内容 | 创建新变量 | 修改同一变量 |
| 可改类型 | ✅ 可以 | ❌ 不可以 |
| 作用域 | 遮蔽变量有独立作用域 | 变量始终在同一作用域 |
| 可借用 | 新变量的借用不冲突 | 同一变量的借用需遵守规则 |
fn main() {
// 遮蔽示例:类型转换
let input = "42";
let input: i32 = input.parse().expect("解析失败");
println!("数值: {}", input);
// 实际应用中,遮蔽常用于:
// 1. 类型转换
// 2. 不变性保护(let x = x + 1 使 x 继续不可变)
// 3. 解构后重用变量名
}
4.4 常量(const)
基本用法
// 常量必须标注类型,必须在编译时确定值
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159265358979;
const APP_NAME: &str = "MyApp";
fn main() {
println!("最大点数: {}", MAX_POINTS);
println!("圆周率: {}", PI);
println!("应用名: {}", APP_NAME);
}
常量命名规范
使用 SCREAMING_SNAKE_CASE(全大写下划线分隔):
const MAX_CONNECTIONS: usize = 100;
const DEFAULT_TIMEOUT_MS: u64 = 3000;
const BUFFER_SIZE: usize = 4096;
常量 vs 静态变量 vs let
| 特性 | const | static | let |
|---|---|---|---|
| 可变性 | 不可变 | 不可变(static mut 可变但 unsafe) | 可选 mut |
| 值确定时间 | 编译时 | 编译时 | 运行时 |
| 存储位置 | 内联到使用处 | 静态内存区 | 栈上 |
| 地址 | 无固定地址 | 有固定地址 | 有栈地址 |
| 类型要求 | 必须标注 | 必须标注 | 可推断 |
| 放置位置 | 任意作用域 | 仅全局作用域 | 任意作用域 |
| 使用场景 | 编译时常量 | 全局共享数据 | 局部变量 |
4.5 静态变量(static)
基本用法
static GREETING: &str = "Hello, World!";
static MAX_RETRIES: u32 = 3;
fn main() {
println!("{}", GREETING);
println!("最大重试次数: {}", MAX_RETRIES);
}
可变静态变量(unsafe)
static mut COUNTER: u32 = 0;
fn increment() {
// 访问可变静态变量必须在 unsafe 块中
unsafe {
COUNTER += 1;
println!("计数器: {}", COUNTER);
}
}
fn main() {
increment();
increment();
increment();
unsafe {
println!("最终计数: {}", COUNTER); // 3
}
}
注意:
static mut是不安全的,在多线程环境中可能导致数据竞争。推荐使用AtomicU32或Mutex<u32>替代。
线程安全的静态变量
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(0);
fn increment() {
COUNTER.fetch_add(1, Ordering::SeqCst);
}
fn main() {
let handles: Vec<_> = (0..10)
.map(|_| {
std::thread::spawn(|| {
for _ in 0..100 {
increment();
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
println!("计数器: {}", COUNTER.load(Ordering::SeqCst)); // 1000
}
4.6 类型推断
Rust 的类型推断
Rust 编译器可以根据上下文推断大部分变量类型:
fn main() {
// 编译器推断为 i32(整数默认类型)
let x = 42;
// 编译器推断为 f64(浮点数默认类型)
let y = 3.14;
// 编译器根据使用推断类型
let numbers: Vec<i32> = Vec::new();
// 等价于:
// let numbers = Vec::<i32>::new();
// 从函数返回值推断
let s = String::from("hello");
let len = s.len(); // len 被推断为 usize
println!("x={}, y={}, len={}", x, y, len);
}
需要显式标注类型的情况
fn main() {
// 1. parse() 需要知道目标类型
let n: i32 = "42".parse().unwrap();
// 或使用 turbofish 语法
let n = "42".parse::<i32>().unwrap();
// 2. 数字字面量需要指定类型(默认 i32/f64 之外的类型)
let a: u64 = 100;
let b: i8 = -128;
let c: f32 = 3.14;
// 3. 集合为空时需要类型信息
let mut v: Vec<i32> = Vec::new();
v.push(1);
// 4. 闭包返回类型需要明确
let closure = |x: i32| -> i32 { x + 1 };
println!("n={}, a={}, b={}, c={}", n, a, b, c);
println!("v={:?}, closure(5)={}", v, closure(5));
}
4.7 数字字面量
整数字面量
| 格式 | 示例 | 说明 |
|---|---|---|
| 十进制 | 98_222 | 下划线分隔提高可读性 |
| 十六进制 | 0xff | 0x 前缀 |
| 八进制 | 0o77 | 0o 前缀 |
| 二进制 | 0b1111_0000 | 0b 前缀 |
| 字节 | b'A' | 仅限 u8 |
fn main() {
let decimal = 98_222;
let hex = 0xff;
let octal = 0o77;
let binary = 0b1111_0000;
let byte = b'A';
println!("十进制: {}", decimal); // 98222
println!("十六进制: {}", hex); // 255
println!("八进制: {}", octal); // 63
println!("二进制: {}", binary); // 240
println!("字节: {}", byte); // 65
}
浮点数字面量
fn main() {
let x = 2.0; // f64(默认)
let y: f32 = 3.0; // f32(显式标注)
// 科学记数法
let z = 1e6; // 1000000.0 (f64)
let w = 2.5e-3; // 0.0025 (f64)
println!("x={}, y={}, z={}, w={}", x, y, z, w);
}
数字类型一览
| 类型 | 大小 | 范围 |
|---|---|---|
i8 | 8 位 | -128 到 127 |
i16 | 16 位 | -32,768 到 32,767 |
i32 | 32 位 | -2^31 到 2^31-1 |
i64 | 64 位 | -2^63 到 2^63-1 |
i128 | 128 位 | -2^127 到 2^127-1 |
isize | 平台相关 | 平台指针大小 |
u8 | 8 位 | 0 到 255 |
u16 | 16 位 | 0 到 65,535 |
u32 | 32 位 | 0 到 2^32-1 |
u64 | 64 位 | 0 到 2^64-1 |
u128 | 128 位 | 0 到 2^128-1 |
usize | 平台相关 | 平台指针大小 |
f32 | 32 位 | IEEE 754 单精度 |
f64 | 64 位 | IEEE 754 双精度 |
注意: 整数溢出在 debug 模式下会 panic,在 release 模式下会回绕(wrap around)。使用
checked_*、wrapping_*、saturating_*、overflowing_*方法明确处理溢出。
4.8 作用域与生命周期
作用域规则
fn main() { // main 作用域开始
let x = 5; // x 绑定
{ // 内部作用域开始
let y = 10; // y 绑定
println!("x + y = {}", x + y); // ✅ 可以访问外部的 x
} // 内部作用域结束,y 被释放
// println!("{}", y); // ❌ 编译错误:y 不存在
println!("x = {}", x); // ✅ x 仍然有效
} // main 作用域结束,x 被释放
遮蔽的作用域
fn main() {
let x = 5;
println!("x = {}", x); // 5
{
let x = x * 2; // 内部遮蔽,x = 10
println!("内部 x = {}", x); // 10
}
// 外部 x 恢复
println!("外部 x = {}", x); // 5
}
4.9 运算符
算术运算符
fn main() {
let a = 10;
let b = 3;
println!("加法: {} + {} = {}", a, b, a + b); // 13
println!("减法: {} - {} = {}", a, b, a - b); // 7
println!("乘法: {} * {} = {}", a, b, a * b); // 30
println!("除法: {} / {} = {}", a, b, a / b); // 3(整数除法)
println!("取余: {} % {} = {}", a, b, a % b); // 1
// 浮点数运算
let x = 10.0;
let y = 3.0;
println!("浮点除法: {} / {} = {}", x, y, x / y); // 3.3333...
// ⚠️ 整数除法会截断小数部分
println!("7 / 2 = {}", 7 / 2); // 3,不是 3.5
}
比较与逻辑运算符
fn main() {
// 比较运算符
println!("1 == 1: {}", 1 == 1); // true
println!("1 != 2: {}", 1 != 2); // true
println!("1 < 2: {}", 1 < 2); // true
println!("1 > 2: {}", 1 > 2); // false
println!("1 <= 1: {}", 1 <= 1); // true
println!("1 >= 2: {}", 1 >= 2); // false
// 逻辑运算符
let a = true;
let b = false;
println!("a && b: {}", a && b); // false
println!("a || b: {}", a || b); // true
println!("!a: {}", !a); // false
// 位运算符
let x: u8 = 0b1010;
let y: u8 = 0b1100;
println!("x & y: {:04b}", x & y); // 1000
println!("x | y: {:04b}", x | y); // 1110
println!("x ^ y: {:04b}", x ^ y); // 0110
println!("!x: {:04b}", !x); // 11110101 (u8)
println!("x << 1: {:04b}", x << 1); // 0100 (溢出位被丢弃)
println!("x >> 1: {:04b}", x >> 1); // 0101
}
类型转换
Rust 不支持隐式类型转换,必须使用 as 关键字:
fn main() {
let x: i32 = 42;
let y: f64 = x as f64; // i32 -> f64
let z: u8 = x as u8; // i32 -> u8(高位截断)
println!("i32: {}", x); // 42
println!("f64: {}", y); // 42.0
println!("u8: {}", z); // 42
// 浮点转整数会截断小数
let f = 3.99;
let i = f as i32;
println!("3.99 as i32 = {}", i); // 3(不是四舍五入)
// ⚠️ 超出目标类型范围时会回绕
let big: u32 = 300;
let small: u8 = big as u8;
println!("300 as u8 = {}", small); // 44 (300 % 256)
}
4.10 业务场景示例
配置管理
const DEFAULT_PORT: u16 = 8080;
const DEFAULT_HOST: &str = "127.0.0.1";
const MAX_CONNECTIONS: usize = 1000;
const REQUEST_TIMEOUT_SECS: u64 = 30;
struct ServerConfig {
host: String,
port: u16,
max_connections: usize,
timeout_secs: u64,
}
impl ServerConfig {
fn new() -> Self {
Self {
host: DEFAULT_HOST.to_string(),
port: DEFAULT_PORT,
max_connections: MAX_CONNECTIONS,
timeout_secs: REQUEST_TIMEOUT_SECS,
}
}
fn with_port(mut self, port: u16) -> Self {
self.port = port;
self
}
fn with_host(mut self, host: &str) -> Self {
self.host = host.to_string();
self
}
}
fn main() {
let config = ServerConfig::new()
.with_port(3000)
.with_host("0.0.0.0");
println!("服务器配置:");
println!(" 地址: {}:{}", config.host, config.port);
println!(" 最大连接数: {}", config.max_connections);
println!(" 超时时间: {} 秒", config.timeout_secs);
}
单位转换工具
/// 温度转换
fn celsius_to_fahrenheit(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}
fn fahrenheit_to_celsius(f: f64) -> f64 {
(f - 32.0) * 5.0 / 9.0
}
/// 字节单位转换
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
const TB: u64 = GB * 1024;
fn format_bytes(bytes: u64) -> String {
if bytes >= TB {
format!("{:.2} TB", bytes as f64 / TB as f64)
} else if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{} B", bytes)
}
}
fn main() {
// 温度转换
let c = 36.5;
let f = celsius_to_fahrenheit(c);
println!("{:.1}°C = {:.1}°F", c, f);
let f = 98.6;
let c = fahrenheit_to_celsius(f);
println!("{:.1}°F = {:.1}°C", f, c);
// 字节格式化
let sizes = vec![500, 1536, 1048576, 1073741824, 1099511627776];
for size in sizes {
println!("{} bytes = {}", size, format_bytes(size));
}
}
4.11 本章小结
| 要点 | 说明 |
|---|---|
| 默认不可变 | let 声明的变量默认不可变,需 mut 显式声明可变 |
| 遮蔽 | let 重新声明同名变量,可以改变类型和可变性 |
| const | 编译时常量,必须标注类型,使用 SCREAMING_SNAKE_CASE |
| static | 全局静态变量,有固定地址,static mut 需 unsafe |
| 类型推断 | 大部分情况编译器可推断,但 parse() 等需要额外信息 |
| 整数溢出 | debug 模式 panic,release 模式回绕 |
| 类型转换 | 使用 as 显式转换,无隐式转换 |
扩展阅读
- Rust 基本类型 — 官方教程
- Rust 语言参考 - 类型 — 类型系统参考
- num_traits crate — 数值类型 trait 集合
- Rust 整数溢出处理 — 溢出行为详解