Rustlings 原语类型实战:bool、char、数组、切片与元组的完整练习指南
Rust 有一组由编译器直接实现的基础数据类型(原语类型),它们是阅读和书写任何 Rust 代码的基石。本文围绕 Rustlings 练习集第 04 章「Primitive Types」展开,逐一剖析 bool、char、数组、数组切片(slice)和元组(tuple)五类核心原语的正确用法、常见陷阱与验证方式,并结合 info.toml 中的练习配置与 solutions 官方解答,说明每个练习是如何被编译和测试的。读完后,你将能够独立完成并理解 exercises/04_primitive_types 下全部六个练习,掌握这些类型在实际代码中的书写习惯。
本章练习总览与两种验证模式
exercises/04_primitive_types/README.md 开宗明义:Rust 有几个由编译器直接实现的基础类型,本章将逐一学习其中最重要的。对应到练习文件,六个练习覆盖的主题如下:
| 练习文件 | 主题 | 核心知识点 |
|---|---|---|
| primitive_types1.rs | bool 布尔类型 |
! 取反运算符 |
| primitive_types2.rs | char 字符类型 |
单引号字面量、Unicode 标量值 |
| primitive_types3.rs | 数组初始化 | [expr; N] 重复初始化简写、len() |
| primitive_types4.rs | 数组切片 | 半开区间 start..end 与闭区间 start..=end |
| primitive_types5.rs | 元组解构 | 用模式匹配绑定元组各字段 |
| primitive_types6.rs | 元组索引 | 以 0 起始的位置索引访问 |
这六个练习并非全部以同一种方式验收。从 rustlings-macros/info.toml 的登记信息看:
primitive_types1、primitive_types2、primitive_types5三个练习带有test = false标记,意味着 Rustlings 只编译并直接运行它们的main()函数来验收,不执行单元测试;- 其余练习(
primitive_types3、primitive_types4、primitive_types6)不设置该标记,会走测试流程——这正对应它们在源码中带有的#[cfg(test)] mod tests模块。
以 primitive_types4.rs 为例,其主体就是测试模块结构:main() 函数体留空供实验,真正的验收逻辑写在 slice_out_of_array 测试里。而 primitive_types1.rs 等练习则直接在 main() 中演示并等待你在 TODO 处补全代码。
本地运行方式:
rustlings run primitive_types1:只运行指定练习(按名称精确匹配);rustlings(watch 模式):交互式运行下一个待完成的练习;rustlings hint primitive_types4:查看官方提示,提示内容正是上面 info.toml 中hint字段对应的文本;rustlings reset primitive_types3:将练习重置回初始模板。
这些子命令定义在 src/cli.rs 中,其中 Run 支持省略练习名时「运行下一个待办练习」,Hint 同理可省略练习名。
bool:布尔类型与取反运算符
bool 只有 true 和 false 两个值,是最基础的原语类型。primitive_types1.rs 给出的骨架如下:
// Booleans (`bool`)
fn main() {
let is_morning = true;
if is_morning {
println!("Good morning!");
}
// TODO: Define a boolean variable with the name `is_evening` before the `if` statement below.
// The value of the variable should be the negation (opposite) of `is_morning`.
// let …
if is_evening {
println!("Good evening!");
}
}
练习要求定义一个 is_evening,其值为 is_morning 的「否定」。官方提示(见 info.toml)指出:Rust 用前缀运算符 ! 取反布尔值,例如 !true == false,对变量同样适用。官方解答 一行解决:
let is_evening = !is_morning;
这里还隐含了两个值得注意的 Rust 语法习惯:
- 条件语句中不加括号:
if is_morning { ... },而非 C 风格的if (is_morning); - 条件本身不带分号,直接接花括号代码块。
char:字符类型与单引号字面量
char 表示单个 Unicode 标量值,字面量必须使用单引号('C'),与表示字符串的双引号严格区分。primitive_types2.rs 的骨架:
// Characters (`char`)
fn main() {
// Note the _single_ quotes, these are different from the double quotes
// you've been seeing around.
let my_first_initial = 'C';
if my_first_initial.is_alphabetic() {
println!("Alphabetical!");
} else if my_first_initial.is_numeric() {
println!("Numerical!");
} else {
println!("Neither alphabetic nor numeric!");
}
// TODO: 声明 your_character,可尝试字母、数字、特殊字符、
// 其他语言的字符,甚至 emoji 😉
// let your_character = '';
if your_character.is_alphabetic() {
println!("Alphabetical!");
} else if your_character.is_numeric() {
println!("Numerical!");
} else {
println!("Neither alphabetic nor numeric!");
}
}
练习要求你为 your_character 挑选任意字符,并用 is_alphabetic() / is_numeric() 判断其类别。官方解答 选择了 emoji:
let your_character = '🦀';
这揭示了 char 与许多语言中「字符 = 单字节」的本质区别:
- 单个
char固定占 4 字节,因此'a'、'中'、'🦀'在内存中大小一致; - emoji 等需要 4 字节 UTF-8 编码的字符可以整体作为一个
char,这正是「Unicode 标量值」概念的体现; is_alphabetic()对 emoji 返回false,所以示例最终会打印Neither alphabetic nor numeric!。
数组:固定长度的栈上集合与 [expr; N] 简写
Rust 数组 [T; N] 是定长、栈上分配的集合,长度在编译期确定。primitive_types3.rs 要求创建一个至少含 100 个元素的数组 a:
fn main() {
// TODO: Create an array called `a` with at least 100 elements in it.
// let a = ???
if a.len() >= 100 {
println!("Wow, that's a big array!");
} else {
println!("Meh, I eat arrays like that for breakfast.");
panic!("Array not big enough, more elements needed");
}
}
逐个敲出 100 个元素既不现实也没必要。info.toml 中的官方提示给出了「重复初始化」简写语法 [value; N],并以 let array = ["Are we there yet?"; 100]; 为例,还附了一个加分思考:a.len() >= 100 还有哪些写法可以满足?官方解答 最简洁:
// An array with 100 elements of the value 42.
let a = [42; 100];
该语法要求元素是可复制(Copy)的确定值,编译器将其展开为 100 份相同元素。验收时 a.len() 返回 100(len 返回 usize),条件成立并打印 Wow, that's a big array!;若不满足,代码会走到 panic! 分支直接中断——这个练习本身就是对 if/else + panic! 控制流的示范。
切片:从数组中借用一段连续元素
切片(slice)是数组的一段连续视图,语法为 &a[start..end],结束索引是排他的(exclusive)。primitive_types4.rs 将其放在单元测试中考察:
#[cfg(test)]
mod tests {
#[test]
fn slice_out_of_array() {
let a = [1, 2, 3, 4, 5];
// TODO: Get a slice called `nice_slice` out of the array `a` so that the test passes.
// let nice_slice = ???
assert_eq!([2, 3, 4], nice_slice);
}
}
数组 a 的元素与索引对应关系为:1→0、2→1、3→2、4→3、5→4。要取出 [2, 3, 4],起始索引为 1、结束索引(排他)为 4。官方解答 同时演示了两种等价写法:
// Note that the upper index 4 is excluded.
let nice_slice = &a[1..4];
assert_eq!([2, 3, 4], nice_slice);
// The upper index can be included by using the syntax `..=` (with `=` sign)
let nice_slice = &a[1..=3];
assert_eq!([2, 3, 4], nice_slice);
即半开区间 1..4 与闭区间 1..=3 取到同一段元素。这里还有一个容易困惑的点:assert_eq! 的第一个参数是数组字面量(按值),第二个参数 nice_slice 却是引用,为什么能直接比较?info.toml 中的官方提示解释了这属于「类型强制转换(coercion)」机制——编译器在比较时将按值的 [i32; 3] 自动借为切片,从而与 &[i32; 3] 兼容。切片「借用」而非「复制」这一点也是后续所有权(move semantics)章节的重要铺垫,与本章 README 引用的官方书籍「The Slice Type」一节(见 README 的 Further information)主题一致。
元组:异构数据的聚合、解构与索引
元组把不同类型的值聚合为一个整体,例如 (String 字面量, f64)。本章用同一个 cat 元组连出两个练习:解构(primitive_types5.rs)与索引(primitive_types6.rs)。
元组解构:一条语句绑定多个变量
primitive_types5.rs 的完整骨架:
fn main() {
let cat = ("Furry McFurson", 3.5);
// TODO: Destructure the `cat` tuple in one statement so that the println works.
// let /* your pattern here */ = cat;
println!("{name} is {age} years old");
}
注意最后的 println! 使用了 Rust 的内嵌格式化(inline format arguments):{name}、{age} 会自动查找同名的局部变量,因此解构出的绑定名必须恰好是 name 和 age。官方解答 用一个解构模式完成:
let (name, age) = cat;
info.toml 的提示进一步说明:这是官方书籍「Data Types → The Tuple Type」一节的倒数第二个示例,关键在于「为元组构造一个模式(pattern)」,把 name、age 绑定到元组对应的部分。运行后输出 Furry McFurson is 3.5 years old。
元组索引:以 0 起始的位置访问
primitive_types6.rs 换一种访问方式——按下标取值:
#[cfg(test)]
mod tests {
#[test]
fn indexing_tuple() {
let numbers = (1, 2, 3);
// TODO: Use a tuple index to access the second element of `numbers`
// and assign it to a variable called `second`.
// let second = ???;
assert_eq!(second, 2, "This is not the 2nd number in the tuple!");
}
}
官方解答 只有一行:
let second = numbers.1;
元组索引以 0 起始:numbers.0 是 1,numbers.1 才是「第 2 个」元素 2,numbers.2 是 3。info.toml 的提示特意强调:虽然这里也能用解构 let,但建议改用索引语法——「你的工具箱里又多了一件工具」。
小结:本章覆盖的知识点与后续章节衔接
本章六个练习构成了一条清晰的学习路径:
- 标量原语:
bool(!取反、无括号条件)与char(单引号、4 字节、Unicode 标量值); - 集合原语:数组的
[expr; N]重复初始化与len(),切片的start..end/start..=end两种区间语法及其「排他结束」约定,以及比较时的隐式强制转换; - 聚合原语:元组的解构模式与 0 起始索引访问。
完成本章后,紧接着的 exercises/05_vecs 会引入长度可变、堆上分配的 Vec<T>,与本章的固定长度数组形成对照;而切片「借用数组一段」的语义,则是后续所有权、引用与借用章节的直接前置知识。所有练习的初始模板位于 exercises/04_primitive_types,对照解答位于 solutions/04_primitive_types,练习元数据(名称、目录、test 标记、提示文本)集中在 rustlings-macros/info.toml,可按需查阅。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00