Rust 专题【左扬精讲】—— 变量、常量与标量数据类型
Rust 专题【左扬精讲】—— 变量、常量与标量数据类型
学习提醒
- 必须掌握:Rust 变量默认不可变、mut 关键字、const vs let 区别、整数溢出处理
- 理解记忆:变量遮蔽(Shadowing)与可变赋值的本质区别、类型推导机制
- 扩展了解:Rust 与其他语言在可变性、类型系统、安全性上的设计哲学差异
目录
一、注释(Comments)
注释是代码的"隐形文档",对编译器而言如同空气,但它是开发者之间最重要的沟通语言。Rust 的注释语法与大多数主流语言保持一致。
1.1 单行注释
使用 // 开头,行内或独占一行均可,注释内容在 // 之后直到行尾结束。
// Rust
fn main() {
// 这是一行注释,编译器会忽略它
let x = 42; // 行尾注释:解释 x 的含义
}
1.2 多行注释
使用 /* 开始、*/ 结束,可以跨越多行,常用于块级说明文档。
/*
这是一个多行注释
可以写很长很长的说明内容
*/
fn greet(name: &str) { /* 也可以这样:行内块注释 */ }
1.3 文档注释
Rust 提供了专用的文档注释,支持 Markdown 语法,并能通过 cargo doc 生成 HTML 文档。
/// 计算两个整数的和
///
/// # Examples
/// assert_eq!(add(2, 3), 5);
pub fn add(a: i32, b: i32) -> i32 { a + b }
/**
结构体的文档注释
使用 /* */ 包裹多行
*/
pub struct User { name: String }
提示:文档注释 ///(内部项)和 /** ... */ 用于为函数、结构体、模块等生成 API 文档,是 Rust 生态中分享库的重要工具。
设计精髓:Rust 注释的哲学
Rust 的注释设计与它的"清晰优于隐晦"哲学一致。文档注释直接支持 Markdown,这意味着标准库的文档美观且一致。而 // 和 /* */ 的选择更多是风格问题,但在 Rust 中,块注释还常被用于临时禁用代码(配合嵌套特性)。
二、常量(Constants)
常量是程序生命周期内始终保持不变的值。与变量不同,常量永远不可变,没有可变性开关,也不需要运行时检查——编译器在编译期就将常量的值内联到所有引用处,实现零成本抽象。
2.1 直接常量(字面量 / Literals)
直接写在代码中的字面量是最原生的常量形式,不绑定任何名称。
2.2 const 常量定义
Rust 中使用 const 关键字声明编译期常量,类型标注必须显式给出。
// Rust
const MAX_RETRIES: u32 = 3; // const 常量,类型必须标注
const PI: f64 = 3.14159; // 浮点常量
const GREETING: &str = "你好,世界"; // 字符串常量(生命周期 elided)
// const 还可以在函数内部声明(块作用域)
fn process() {
const TIMEOUT_MS: u64 = 5000;
println!("超时: {}ms", TIMEOUT_MS);
}
重要:Rust 的 const 是真正的编译期常量——它的值在编译阶段就完全确定,编译器会做常量折叠(Constant Folding),不会在运行时为常量分配内存。
2.3 多语言对比:常量声明
| 语言 | 语法 | 类型标注 | 可用位置 | 特殊规则 |
|---|---|---|---|---|
| Rust | const NAME: type = value; | 必须显式标注 | 任意作用域(块内/函数内/全局) | 仅编译期已知表达式 |
| C | #define NAME value 或 const type NAME = value; | 可选(宏无需标注) | 仅文件/全局作用域 | 宏无类型;const 在 C89 中行为模糊 |
| C++ | constexpr type NAME = value; 或 const type NAME = value; | 可选(C++11 后推荐 constexpr) | 任意作用域 | constexpr 保证编译期求值,const 可运行时求值 |
| Python | NAME = value(无专门语法,靠约定) | 无类型系统(可用 type hints) | 模块/类作用域 | 无真正常量,约定全大写变量名不变 |
| Go | const Name type = value | 可选(可省略自动推导) | 任意作用域 | 仅支持编译期可确定的常量表达式 |
| Java | static final type NAME = value; | 必须显式标注 | 类作用域(字段) | static + final 共同修饰,运行时常量 |
| JS | const NAME = value; | 无类型标注(可用 JSDoc/TS) | 块作用域 | 绑定不可重新赋值,但值本身可能可变 |
| TS | const NAME: type = value; | 可选(TypeScript 类型标注) | 块作用域 | 编译到 JS const,类型仅在编译期检查 |
注意:C 语言的 #define 宏:与 Rust 的 const 功能相近但有本质区别。宏是纯文本替换,没有类型、不占内存、不参与调试符号。而 Rust 的 const 有类型系统、参与类型检查、可在调试器中查看。
对比记忆
- Rust const = C++ constexpr(编译期求值,无运行时开销)
- Java static final = Rust static(运行时存储在 .rodata 段)
- Python/JS 没有真正的编译期常量,只能靠约定或运行时检查
- Go 的 const 与 Rust 设计最接近,但 Go 无 static 关键字
设计精髓:Rust 为什么需要 const + static 两种常量?
- const:编译期内联,零运行时成本,类似 C 宏但有类型安全
- static:拥有固定内存地址,生命周期为整个程序运行,可包含需要地址的引用(如 &'static str),但引用的数据必须是 'static 生命周期
三、变量(Variables)
3.1 Rust 关键字
Rust 有一套保留关键字(Reserved Keywords),它们在语言中有特殊含义,不能用作标识符(变量名、函数名等)。关键字分为两类:
- 纯关键字(Strict Keywords):在任何上下文中都不能作为标识符,如 as、break、const、if、let 等
- 严格关键字(Reserved Keywords):当前未使用,但为未来扩展保留,如 abstract、async、await、dyn、try 等
// Rust 关键字示例 —— 下列名称都不能用作变量名
// let, fn, if, else, for, while, loop, match, struct, enum,
// impl, trait, pub, mod, use, crate, self, super, async, await,
// return, move, ref, mut, dyn, where, type, as, in,
// break, continue, Self, static, unsafe, extern
// raw identifier: 用 r# 前缀可以在特殊场景下使用关键字作标识符
let r#if = 42; // 合法:raw identifier 语法 r#keyword
3.2 变量命名规则
Rust 的命名规范与大多数主流语言相似,但 Rust 社区有强烈的命名文化偏好:
| 规则 | 说明 |
|---|---|
| 字母或下划线开头 | 不能以数字开头,如 2name 非法 |
| 驼峰命名法(变量/函数) | Rust 约定用下划线分隔(snake_case):my_variable |
| 全大写+下划线(常量) | MAX_SIZE、PI_VALUE |
| 避免单字母大写(L 类型) | 由于 I、O 易与数字 1、0 混淆,Rust 不允许 |
| UTF-8 命名 | Rust 支持 Unicode 标识符,可用中文命名(如 let 名字 = "Rust";) |
3.3 变量定义与初始化
Rust 使用 let 关键字声明变量。与大多数语言不同,Rust 的变量默认是不可变的——这是语言设计中最核心的理念之一:安全性优先于便利性。
3.3.1 定义(声明)
// Rust:声明与初始化通常一起进行
let x: i32; // 声明(无初始化,类型标注可选)
x = 10; // 后续赋值:OK,未初始化的变量必须先赋值再使用
// 声明+初始化:最常见写法,类型可由右侧推导
let name: String = String::from("Alice");
let score = 100; // 类型由字面量推导为 i32
3.3.2 多语言对比:变量声明与初始化
| 语言 | 声明语法 | 类型推导 | 默认可变 | 未初始化使用 |
|---|---|---|---|---|
| Rust | let x: i32; | 支持(右侧推导) | 否,默认不可变 | 编译错误(安全) |
| C | int x; | 不支持 | 是 | 未定义行为(危险!) |
| C++ | int x; | C++11 auto | 是 | 未定义行为(危险!) |
| Python | x = 10(无需声明) | 强推导(动态类型) | 是(但变量可重新绑定) | NameError(运行时常量) |
| Go | x := 10 或 var x int | := 自动推导 | 是 | 编译错误(零值初始化) |
| Java | int x; | var x = 10(Java 10+) | 是 | 编译错误 |
| JS | let x = 10; | 支持(但推导结果常为 number) | 是(var/let),否(const) | undefined(隐式 undefined) |
| TS | let x: number = 10; | 支持(类型推断) | 是(let),否(const) | undefined |
重要:C/C++ 的未初始化变量陷阱:在 C 和 C++ 中,使用未初始化的变量是未定义行为(Undefined Behavior),程序可能输出随机垃圾值,甚至导致安全漏洞。Rust 在编译期就消除了这个隐患——如果编译器检测到使用未初始化变量,会直接报错。
3.4 可变性(Mutability)
Rust 用 mut 关键字显式声明变量的可变性。这是 Rust 与众不同的设计:默认不可变,需要时才开放可变权限。
// Rust:默认不可变,尝试修改会编译失败
let x = 5;
x = 6; // error[E0384]: cannot assign twice to immutable variable `x`
// 添加 mut:显式声明为可变变量
let mut y = 5;
y = 6; // OK: mut 允许重新赋值
println!("y = {}", y); // 输出: y = 6
设计思考:Rust 选择"默认不可变"而非"默认可变",是受函数式语言影响的决策。这使得多线程代码中大量潜在的数据竞争(Data Race)在编译期就被消灭,同时让代码的意图更加明确——阅读者无需猜测某个变量是否会被修改。
3.4.1 多语言对比:可变与不可变
| 语言 | 默认行为 | 声明不可变 | 声明可变 |
|---|---|---|---|
| Rust | 不可变(默认安全) | let x = 5; | let mut x = 5; |
| C | 可变(无保护) | 无原生语法(用 const 限制) | int x = 5; |
| C++ | 可变 | const int x = 5; | int x = 5;(或 int& x) |
| Python | 可变(一切皆对象) | 无(靠约定) | 直接赋值改变对象内容 |
| Go | 可变(默认) | 无原生语法 | x := 5(默认) |
| Java | 可变(字段/局部变量) | final int x = 5; | int x = 5; |
| JS | 可变(var/let) | const x = 5; | let x = 5; |
| TS | 可变(let) | const x = 5; | let x = 5; |
对比记忆口诀
- Rust = 默认安全:不可变是默认态,mut 是例外(类似 Kotlin 的 val vs var,但 Kotlin 是面向对象的)
- Java = 字段可变:final 修饰字段才是常量(与 Rust 相反)
- Python = 无约束:没有真正的不可变变量,任何变量都可以重新绑定
- JS/TS = const 是绑定不变:ES6 引入的 const 与 Rust 设计最接近
- C++ constexpr = Rust const,但 C++ 默认可变
3.5 变量遮蔽(Shadowing)
Rust 允许在同一个作用域内,用 let 重新声明同名变量,这称为变量遮蔽(Shadowing)。被遮蔽的旧变量在内存中仍然存在(若实现了 Drop 则被 drop),新变量占据新的栈帧位置。
// Rust:变量遮蔽 —— 用 let 重新声明同名变量
let x = 5;
println!("x = {}", x); // x = 5
let x = x + 1; // 遮蔽:x 从 i32 变为新值(类型相同,可不同)
println!("x = {}", x); // x = 6
let x = "hello"; // 遮蔽:类型从 i32 变为 &str(类型也可改变!)
println!("x = {}", x); // x = hello
注意:变量遮蔽 vs 可变赋值 —— 核心区别
- 遮蔽(Shadowing):用 let 创建全新变量,旧变量被遮蔽,可以改变类型
- 可变赋值(Mutation):用 mut 修改已有变量的值,类型保持不变,占用同一内存位置
3.5.1 多语言对比:变量遮蔽
| 语言 | 是否支持同名遮蔽 | 语法 | 类型可变化吗 |
|---|---|---|---|
| Rust | 支持(用 let) | let x = ...; let x = ...; | 是(可以改变类型) |
| C | 不支持(编译错误) | — | — |
| C++ | 支持(内层块可遮蔽外层) | 嵌套 { } 块内声明同名 | 否(类型固定) |
| Python | 支持(无块作用域限制) | 直接 x = ... 重新绑定 | 是(动态类型) |
| Go | 支持(短声明 := 在不同作用域) | 嵌套块内的 := | 否(类型固定) |
| Java | 不支持(同一作用域重复声明编译错误) | — | — |
| JS | let/const 不支持;var 部分支持(函数作用域提升) | var 在函数内可重复声明 | 否(var 无类型) |
| TS | let/const 不支持(同一块作用域) | 块级作用域内不允许重复声明 | 否(静态类型) |
Rust 的变量遮蔽不是为了方便改值(改值应该用 mut),而是为了:
- 值转换场景:对某个值做一系列不可变的变换操作,每次变换后绑定新变量名,逻辑更清晰
- 类型适配:可以用不同类型遮蔽同一名称(这在需要类型推导的场景中非常有用)
- 避免命名污染:不必为中间变量取不同的名字,如 let spaces = spaces.trim()
本质是 let 声明的是新变量,不是"对已有变量重新赋值",这是它与 mut 的根本区别。
3.6 字符串变量(String)
Rust 中字符串是理解难度较高的部分,因为它区分了两种类型:字符串切片 &str 和 拥有所有权的字符串 String。
// &str:字符串切片,borrowed view(借阅视图)
let s1: &str = "hello world"; // 字面量 &str,存储在二进制中,static 生命周期
// String:拥有所有权的堆字符串,可变
let s2 = String::from("hello");
let s3 = "hello".to_string(); // 同样创建 String
// 可变 String
let mut s4 = String::from("hello");
s4.push_str(", world!"); // OK: 可变 String,可以追加内容
println!("{}", s4); // hello, world!
3.6.1 多语言对比:字符串类型
| 语言 | 可变字符串 | 不可变字符串 | 字符串存储 |
|---|---|---|---|
| Rust | String(堆,可变) | &str(切片,借用) | UTF-8 编码 |
| C | char[] 数组(栈,需手动管理) | char* 指针(字符串字面量在 .rodata) | 无统一字符串类型,靠 char* + 长度约定 |
| C++ | std::string(可变) | const char* / std::string_view | ISO-8859-1 或 UTF-16(取决于编译器) |
| Python | 无(字符串不可变) | str(不可变) | Unicode(Python 3 全部是 Unicode) |
| Go | string(不可变,但 []byte/rune 可变) | string 本身不可变 | UTF-8 编码 |
| Java | StringBuffer / StringBuilder | String(不可变) | UTF-16(内部 char[]) |
| JS | 无(字符串不可变) | string(不可变 primitive) | UTF-16 编码 |
| TS | 无(字符串不可变) | string(不可变 primitive) | UTF-16 编码(继承 JS) |
关键记忆点
- Rust 的 &str ≈ Go 的 string(但 Go 的 string 不可变)
- Rust 的 String ≈ C++ 的 std::string(但 Rust 的所有权模型更严格)
- Python/JS 的字符串是不可变的——这是为了线程安全和字符串驻留(Interning)优化
- Java 的 String 同样不可变(安全性 + 哈希缓存),可变操作需要 StringBuilder
四、标量数据类型(Scalar Data Types)
Rust 的数据类型分为两大类:标量类型(Scalar) 和 复合类型(Compound)。标量类型是语言中最基础的数据表示,代表一个单一的值。
4.1 数据类型总览
| 分类 | Rust 类型 | 说明 |
|---|---|---|
| 整数 | 有符号:i8, i16, i32, i64, i128, isize | 可表示负数(-2^(n-1) ~ 2^(n-1)-1) |
| 无符号:u8, u16, u32, u64, u128, usize | 只能表示非负数(0 ~ 2^n-1) | |
| 浮点数 | f32(IEEE-754 单精度),f64(IEEE-754 双精度) | f64 是默认类型(等价 C double / JS number) |
| 布尔值 | bool | 值:true 或 false(占用 1 字节) |
| 字符 | char | Unicode 标量值,4 字节(UTF-32),可表示 emoji / 中文 |
| 单元类型 | ()(unit / 空元组) | 无返回值的函数的隐式返回类型 |
4.2 整型(Integer Types)
4.2.1 有符号 vs 无符号整数
// Rust 整数字面量可以加类型后缀,也可以由上下文推导
let decimal = 98_222; // 十进制,支持 _ 分隔符(可读性)
let hex = 0xff; // 十六进制
let octal = 0o77; // 八进制
let binary = 0b1111_0000; // 二进制
let byte = b'A'; // u8 字节字面量(ASCII)
// 类型后缀:明确指定字面量类型
let age: u8 = 30u8; // 30 as u8
let big: i128 = 1_000_000_000_000i128;
4.2.2 usize / isize
usize 和 isize 是与机器架构相关的整数类型:
// usize / isize:依赖运行环境的位数
// 64 位机器上 = u64/i64,32 位机器上 = u32/i32
let ptr_size: usize = std::mem::size_of_val("&x");
// 典型用途:数组索引、集合大小、指针运算
let arr = [1, 2, 3, 4, 5];
let len: usize = arr.len(); // len 的类型是 usize
4.2.3 整数溢出处理
这是 Rust 独有的编译模式设计,在 Debug 和 Release 模式下行为不同:
let a: u8 = 255;
// Debug 模式:panic!(触发溢出检测,程序 abort)
// Release 模式:二进制回绕(2's complement wrapping) 255 + 1 = 0
// 更安全的显式溢出处理方法:
let wrapped = a.wrapping_add(1); // 回绕(任何模式都安全)
let saturating = a.saturating_add(1); // 饱和:超过最大值则取最大值
let checked = a.checked_add(1); // 返回 None(溢出)或 Some(value)
let overflow = a.overflowing_add(1); // 返回 (result, bool溢出标志)
注意:整数溢出是 C/C++ 中常见的安全漏洞来源。攻击者常常利用缓冲区溢出中整数溢出的组合来绕过安全检查。Rust 的设计让开发者必须显式选择溢出处理策略,而不是依赖未定义行为。
4.2.4 多语言对比:整型
| 语言 | i32/u32 | 平台相关 | 默认整数类型 | 溢出行为 |
|---|---|---|---|---|
| Rust | 32 位,4 字节 | isize/usize(平台指针大小) | i32(字面量);f64(浮点默认) | Debug=panic;Release=wrapping(需显式选方法) |
| C | 通常 32 位 | int 等价平台字长(历史原因) | int(但大小不确定,平台相关) | 未定义行为(UB)!编译器可自由优化 |
| C++ | 继承 C | ptrdiff_t / size_t | int | 未定义行为(UB) |
| Python | 无固定大小(Arbitrary-precision int) | —(Python int 是大整数) | int(无大小限制,自动扩展) | 自动扩展精度,永不溢出 |
| Go | int32/uint32 | int/uint(平台字长) | int(平台相关,通常 64 位) | 回绕(wrapping) |
| Java | int(32 位)/ long(64 位) | —(Java 无平台相关类型) | int | 回绕(wrapping),不抛异常 |
| JS | —(JS 只有一种 number) | —(IEEE-754 双精度) | number(IEEE-754 double) | 精度丢失(大整数不安全,超过 2^53 会丢失精度) |
| TS | number(继承 JS) | — | number | 精度丢失(与 JS 相同) |
提示:Python 的大整数 vs Rust 的固定宽度
Python 的 int 可以自动扩展到任意精度(背后是 bignum 实现),但这带来了额外的运行时开销。Rust 的固定宽度整数更接近硬件原生,操作速度极快——代价是需要开发者手动管理溢出风险。
4.3 浮点型(Floating-Point Types)
Rust 有两种浮点类型:f32(IEEE-754 单精度)和 f64(IEEE-754 双精度)。默认使用 f64,因为现代 CPU 上 f64 的计算速度与 f32 几乎相同,且精度更高。
// Rust 浮点字面量
let x = 2.0; // f64(Rust 默认浮点类型)
let y: f32 = 3.0; // f32(需显式标注)
let scientific = 1e10; // 科学计数法:1 * 10^10 = f64
// NaN(Not a Number):非数字检测
let nan = (-1.0).sqrt(); // NaN
println!("{:?}", nan.is_nan()); // true
4.3.1 多语言对比:浮点型
| 语言 | 单精度 | 双精度 | 默认 | NaN 行为 |
|---|---|---|---|---|
| Rust | f32(IEEE-754) | f64(IEEE-754) | f64 | 可比较(is_nan()) |
| C | float | double | double(隐式提升) | math.h 中 isnan() 宏 |
| C++ | float | double | double | std::isnan() |
| Python | float = C double(IEEE-754) | float(单一类型) | math.nan, math.isnan() | |
| Go | float32 | float64 | float64 | math.IsNaN() |
| Java | float | double(默认) | double | Double.isNaN() / Float.isNaN() |
| JS | number = IEEE-754 double(唯一类型) | number | Number.isNaN()(注意与全局 isNaN() 区别) | |
| TS | number(IEEE-754 double) | number | Number.isNaN() | |
对比要点:JS/TS 只有一个 number 类型
JavaScript 和 TypeScript 的 number 类型等价于 IEEE-754 双精度浮点数,没有整数和单精度浮点的区分。这意味着 0.1 + 0.2 !== 0.3(浮点精度问题)在 JS/TS 中同样存在。
4.4 布尔型(Boolean Type)
let is_rust_fun: bool = true;
let is_hard = false; // 类型推导为 bool
// Rust 的 if 是表达式,可以返回值
let result = if true { 1 } else { 0 }; // result = 1
4.4.1 多语言对比:布尔型
| 语言 | 布尔字面量 | 布尔大小 | 真值判断 | 零值转换 |
|---|---|---|---|---|
| Rust | true / false | 1 字节 | 必须显式比较(无隐式转换) | 无隐式转换(if x 非法,需 if x != 0) |
| C | 无原生布尔(0=false, 非0=true) | 通常 int(4字节) | 隐式(整数直接作为条件) | 0=false,其他都是 true |
| C++ | true / false(C++11) | 1 字节 | 隐式转换 | 隐式转换到 int |
| Python | True / False | 单例对象 | 隐式(几乎所有对象都是 truthy) | None/0/""/[]/{} 都是 falsy |
| Go | true / false | 1 字节(但对齐后 1 字节) | 必须显式比较(无隐式转换) | 无隐式转换(if x 非法) |
| Java | true / false | 理论上 1 位,实际 1 字节 | 隐式转换(boolean 可直接作为条件) | 隐式(if (flag)) |
| JS | true / false | 单例对象 | 隐式(truthy/falsy 规则) | 0/null/undefined/""/NaN/false 都是 falsy |
| TS | true / false | 继承 JS | 隐式(truthy/falsy 规则) | truthy/falsy 规则 |
重要设计差异:隐式真值判断
- Rust/Go 的 if x(x 是整数)—— 编译错误,强制显式比较
- C/C++/Python/JS 的 if x(x 是整数)—— 合法,依赖隐式转换
- Rust 和 Go 选择了更严格的类型安全,代价是代码稍微冗长一些
4.5 字符型(Character Type)
Rust 的 char 是完整的 Unicode 标量值(Unicode Scalar Value),占 4 字节,可以表示任何 Unicode 字符,包括中文汉字、emoji、数学符号等。
let c1: char = 'A'; // ASCII 字符
let c2 = '中'; // Unicode 中文(char 可以存中文!)
let c3 = '😂'; // emoji(Unicode Scalar Value)
// char 不是 Rust 中最常用的字符串类型,字符串切片 &str 更常用
println!("'{}' is {} bytes", c3, std::mem::size_of_val(&c3)); // 4 bytes
4.5.1 多语言对比:字符型
| 语言 | 字符类型 | 大小 | 编码 | 中文支持 |
|---|---|---|---|---|
| Rust | char(Unicode 标量值) | 4 字节(UTF-32) | Unicode Scalar Value | 完整支持(emoji/汉字/数学符号) |
| C | char(1 字节) | 1 字节 | 平台编码(通常 ASCII/Latin-1) | 无原生支持(需 multibyte / wchar_t) |
| C++ | char / char16_t / char32_t / wchar_t | 平台相关 | char16_t=UTF-16; char32_t=UTF-32 | char16/32_t 支持,char 不支持 |
| Python | str 中的单个字符是 str(Unicode) | 可变(UTF-8 存储) | Unicode(Python 3) | 完整支持 |
| Go | rune(= int32,Unicode 码点) | 4 字节(rune) | UTF-8 存储 | 完整支持 |
| Java | char(2 字节) | 2 字节(UTF-16 码元) | UTF-16(基本多语言平面) | 支持(BMP 内的中文) |
| JS | 无 char 类型,字符串是 primitive | UTF-16 编码 | UTF-16 | 支持(但代理对问题) |
| TS | 无 char 类型(string) | UTF-16 | UTF-16 | 支持 |
Rust char vs Go rune vs Java char:三个都是 Unicode 标量值,但存储方式不同
Rust 的 char 占用 4 字节存储一个完整的 Unicode 标量值(最大到 U+10FFFF),是内存换便利的设计。Go 的 rune 本质上是 int32 的别名,行为相同。Java 的 char 是 UTF-16 码元,对超出 BMP 的字符(如 emoji)需要用代理对(surrogate pair)处理,稍显麻烦。
五、获取变量的字节数
Rust 使用 std::mem::size_of_val() 函数获取值的字节数,以及 std::mem::size_of<T>() 获取类型的字节数。
println!("bool: {} bytes", std::mem::size_of::()); // 1
println!("char: {} bytes", std::mem::size_of::()); // 4
println!("i32: {} bytes", std::mem::size_of::()); // 4
println!("i64: {} bytes", std::mem::size_of::()); // 8
println!("f64: {} bytes", std::mem::size_of::()); // 8
println!("&str: {} bytes (pointer only)", std::mem::size_of::<&str>()); // 16 (ptr+len on 64-bit)
// size_of_val:获取某个具体值的字节数
let text = "你好 Rust";
println!("'{}' is {} bytes", text, std::mem::size_of_val(text));
// 13 bytes (UTF-8: 你=3, 好=3, 空格=1, R=1, u=1, s=1, t=1)
5.1 多语言对比:获取类型大小
| 语言 | 语法 | 示例 |
|---|---|---|
| Rust | std::mem::size_of::<T>() std::mem::size_of_val(&value) |
size_of::() // 4 |
| C | sizeof(type) sizeof expression |
sizeof(int) // 通常 4 |
| C++ | sizeof(type) sizeof expression |
sizeof(double) // 通常 8 |
| Python | sys.getsizeof(obj) | sys.getsizeof(42) # 28 (含对象头) |
| Go | unsafe.Sizeof(x) | unsafe.Sizeof(int64(0)) // 8 |
| Java | Unsafe.ARRAY_XXX_INDEX_SCALE + Integer.BYTES |
Integer.BYTES // 4 |
| JS | 无直接方法(对象在堆中,大小不透明) | —(运行时不可知) |
| TS | 无直接方法(编译到 JS) | —(编译到 JS,无类型大小概念) |
Rust 的 size_of_val 揭示了 UTF-8 的高效性:
中文字符"你好"在 UTF-8 中每个占 3 字节,共 6 字节;而 Java 的 char[] 每个中文字符占 2 字节(UTF-16),共 4 字节。如果你的应用主要处理 ASCII 文本,UTF-8 非常节省空间(每个字符 1 字节)。
六、总结
本专题核心要点
- 注释:// 单行、/* */ 多行、/// 文档注释
- 常量:const = 编译期内联(零运行时成本);static = 运行时固定地址;无 mut 选项
- 变量:let 声明(默认不可变);let mut 显式可变;变量遮蔽(Shadowing)可以改变类型
- 标量类型:整型(i/u + 位数)、浮点(f32/f64,默认 f64)、布尔(bool)、字符(char,4字节 Unicode)
- 设计哲学:Rust 默认不可变、显式类型标注、安全的整数溢出处理、强制显式布尔比较
Rust vs 其他语言的本质区别
| 维度 | Rust 特色 | 其他语言对比 |
|---|---|---|
| 默认可变性 | 不可变(安全优先) | C/C++/Go/Java/JS 默认可变;Python 无约束 |
| 变量遮蔽 | 支持(let 重声明,可改变类型) | JS(Python) 可改变值/类型;C/Java 不支持同名遮蔽 |
| 整数溢出 | Debug=panic, Release=wrapping(需显式选方法) | C/C++=UB;Java=wrapping;Python=不溢出;JS=精度丢失 |
| 未初始化变量 | 编译错误(安全) | C/C++=UB(危险);Go=零值;Python=NameError;JS=undefined |
| 布尔比较 | 必须显式(无隐式转换) | C/C++/Python/JS 允许隐式(if x 合法) |
| const 语义 | 编译期求值,零成本内联(类似 C 宏但类型安全) | C++ constexpr 类似;Java static final 运行时;JS const 无编译期保证 |
- 复合类型:元组(Tuple)、数组(Array)、结构体(Struct)、枚举(Enum)
- 所有权系统:借用(Borrowing)、引用(References)、生命周期(Lifetime)—— Rust 最核心的概念
- 模式匹配:match 表达式、if let、解构(Destructuring)
- 泛型基础:泛型函数、Trait 与接口
- 错误处理:Result<T, E> vs Option<T> —— Rust 的显式错误处理哲学
作者:左扬 | 发表于 2026 年 | 水平有限,欢迎指正

浙公网安备 33010602011771号