首页
/ Rustlings 原语类型实战:bool、char、数组、切片与元组的完整练习指南

Rustlings 原语类型实战:bool、char、数组、切片与元组的完整练习指南

2026-09-03 17:22:37作者:冯爽妲Honey

Rust 有一组由编译器直接实现的基础数据类型(原语类型),它们是阅读和书写任何 Rust 代码的基石。本文围绕 Rustlings 练习集第 04 章「Primitive Types」展开,逐一剖析 boolchar、数组、数组切片(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_types1primitive_types2primitive_types5 三个练习带有 test = false 标记,意味着 Rustlings 只编译并直接运行它们的 main() 函数来验收,不执行单元测试;
  • 其余练习(primitive_types3primitive_types4primitive_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.tomlhint 字段对应的文本;
  • rustlings reset primitive_types3:将练习重置回初始模板。

这些子命令定义在 src/cli.rs 中,其中 Run 支持省略练习名时「运行下一个待办练习」,Hint 同理可省略练习名。

bool:布尔类型与取反运算符

bool 只有 truefalse 两个值,是最基础的原语类型。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 语法习惯:

  1. 条件语句中不加括号:if is_morning { ... },而非 C 风格的 if (is_morning)
  2. 条件本身不带分号,直接接花括号代码块。

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() 返回 100len 返回 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→02→13→24→35→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} 会自动查找同名的局部变量,因此解构出的绑定名必须恰好是 nameage官方解答 用一个解构模式完成:

let (name, age) = cat;

info.toml 的提示进一步说明:这是官方书籍「Data Types → The Tuple Type」一节的倒数第二个示例,关键在于「为元组构造一个模式(pattern)」,把 nameage 绑定到元组对应的部分。运行后输出 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.01numbers.1 才是「第 2 个」元素 2numbers.23info.toml 的提示特意强调:虽然这里也能用解构 let,但建议改用索引语法——「你的工具箱里又多了一件工具」。

小结:本章覆盖的知识点与后续章节衔接

本章六个练习构成了一条清晰的学习路径:

  1. 标量原语bool! 取反、无括号条件)与 char(单引号、4 字节、Unicode 标量值);
  2. 集合原语:数组的 [expr; N] 重复初始化与 len(),切片的 start..end / start..=end 两种区间语法及其「排他结束」约定,以及比较时的隐式强制转换;
  3. 聚合原语:元组的解构模式与 0 起始索引访问。

完成本章后,紧接着的 exercises/05_vecs 会引入长度可变、堆上分配的 Vec<T>,与本章的固定长度数组形成对照;而切片「借用数组一段」的语义,则是后续所有权、引用与借用章节的直接前置知识。所有练习的初始模板位于 exercises/04_primitive_types,对照解答位于 solutions/04_primitive_types,练习元数据(名称、目录、test 标记、提示文本)集中在 rustlings-macros/info.toml,可按需查阅。

登录后查看全文
热门项目推荐
相关项目推荐