小卡第一天到「Rust 餐厅」后厨上班,师傅丢给他一句话:「你只管炒菜,杂事都交给 Cargo。」Cargo 是这家餐厅的后厨总管:他有一本万能菜单,记着每道菜需要哪些半成品料理包;他每天清晨骑着三轮车去中央食材市场(crates.io)进货,哪个批次要用哪个版本,他门儿清;炒完菜他让试菜员尝一口(cargo test),合格才端出去;餐厅开了七家分店,他让所有分店共用一间中央厨房(workspace),原料口味完全一致;小卡研究出的秘制酱料,经他包装后还能上架到美食平台,供全世界的餐厅下载使用。整个故事里,小卡只需要记住一件事:专注写代码。接下来,我们跟着小卡,把这套后厨管理术一步步学明白。

岗位关联

学会 Cargo,等于解锁了 Rust 开发的「自动驾驶」:从建项目、管依赖、跑测试到发布上线,一套命令全搞定。这是每个 Rust 工程师的日常基本功,也是进入现代 AI 基础设施开发(如推理引擎、高性能服务)的必备技能。

目录
  1. 可视化演示
  2. 认识 Cargo:后厨总管的第一课
  3. 项目配置:Cargo.toml 是菜单兼采购单
  4. 依赖管理:精确到批次的采购哲学
  5. crates.io:中央食材市场
  6. 构建与测试:后厨流水线
  7. 工作空间:连锁餐厅的中央厨房
  8. 发布流程:菜谱上架到全球
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

演示Cargo依赖解析与版本锁定、构建流水线编译过程、workspace多crate共享管理,交互切换步骤查看核心机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

依赖解析与版本锁定 Cargo.toml声明依赖版本范围 解析器解决传递依赖冲突 semver: ^1.2 → ≥1.2.0,<2.0.0 Cargo.lock锁定精确版本保证可复现构建

认识 Cargo:后厨总管的第一课

小卡第一天上班,Cargo 递给他一张员工卡,刷卡动作是 cargo new my_dish。刷完,一间小厨房拔地而起:墙上挂着菜单(Cargo.toml),灶台设在 src/main.rs,仓库间是 target/ 目录。小卡还没动一根手指,一个整洁的项目骨架就自动站在眼前——这就是「后厨总管」给的见面礼。

Cargo 是 Rust 官方的构建系统与包管理器,名字取「货物」之意,寓意把代码当货物来打包、运输。在 Rust 世界里,几乎所有日常操作都围绕 Cargo 展开:它是统筹全局的后厨总管,把建目录、下依赖、编代码、跑测试、发布上架整合成一条流水线,开发者只需要记住几条命令就能运转整个厨房。

先看项目结构:Cargo.toml 是菜单加采购单,声明了菜品信息和所需食材;src/main.rs 是主灶台(可执行程序的入口),如果想写库则在 src/lib.rs;target/ 是成品仓库——所有编译产物都堆在这里,删掉也无妨,Cargo 可以重新做一桌。创建项目用 cargo new(全新开一间厨房)或 cargo init(在现有空房间里布置厨房)。日常最常用的四道命令是:cargo build 做菜、cargo run 上菜、cargo check 快速验味、cargo test 全量试菜。

小卡看着自动生成的整洁厨房,第一次体会到「总管家」的含金量:以前在别的语言里手忙脚乱搭环境的日子,一去不复返了。

cargo new→生成{Cargo.toml, src/main.rs, .gitignore}\displaystyle \text{cargo new} \xrightarrow{\text{生成}} \{ \text{Cargo.toml},\ \text{src/main.rs},\ \text{.gitignore} \}
这个公式描述了创建项目的产物集合:执行一次 cargo new 后,Cargo 自动生成一张菜单、一个灶台文件和一个版本管理忽略清单。
  1. 输入命令cargo new 是开新厨房的指令,后面通常跟项目名。
    cargo new my_dish\text{cargo new my\_dish}
  2. 生成骨架文件命令自动产出三个核心文件,不需要手动创建。
    Cargo.toml+src/main.rs+.gitignore\text{Cargo.toml} + \text{src/main.rs} + \text{.gitignore}
  3. 花括号集合含义数学中的花括号表示「一组对象」,这里表示这些文件共同构成项目的最小骨架。
    {Cargo.toml, src/main.rs, .gitignore}\{ \text{Cargo.toml},\ \text{src/main.rs},\ \text{.gitignore} \}

项目配置:Cargo.toml 是菜单兼采购单

每个后厨墙上都挂着一本「菜单 + 采购单」的合订本,这就是 Cargo.toml。客人点什么菜(依赖什么库)、采购员进什么货(依赖版本范围)、能不能加辣(可选特性),全都明明白白写在上面。小卡第一次翻开它,发现整间厨房的规矩都浓缩在这一个文件里。

Cargo.toml 使用 TOML 格式书写——一种专门给人类读的配置语言,结构像字典条目一样清晰,用方括号表示区块。它不像代码那样需要编译,改完立即生效,Cargo 总管会实时照单执行。

文件内最常打交道的有三块:[package] 区块登记菜品的元信息——菜名(name)、菜谱版本(version)、以及基于哪一年的 Rust 口味(edition);[dependencies] 区块是采购清单,列出所有第三方料理包及版本要求;[features] 区块定义可选配项,比如「加辣」「加蛋」,别家在引用这个库时可以按需开启。还有一个 [profile] 区块用来调整做菜档位:dev 档优先出锅速度,release 档优先菜品质量(运行性能)。

小卡第一次改菜单就发现,改完 Cargo.toml 后,总管自动生成了一张叫 Cargo.lock 的采购订单——版本被精确锁死,从此每桌菜的配方都一模一样。

Cargo.toml={package⏟元信息, dependencies⏟依赖, features⏟特性}\displaystyle \text{Cargo.toml} = \{ \underbrace{\text{package}}_{\text{元信息}},\ \underbrace{\text{dependencies}}_{\text{依赖}},\ \underbrace{\text{features}}_{\text{特性}} \}
公式把 Cargo.toml 拆成三块核心区域,每一块各司其职,像菜单、采购单和可选配项合订成一本书。
  1. package 区块登记菜名、版本号、Rust 版本年份等元信息,相当于菜谱的封面。
    package:{name, version, edition}\text{package}: \{ \text{name},\ \text{version},\ \text{edition} \}
  2. dependencies 区块列出所有第三方料理包的名称与版本约束,相当于采购单。
    dependencies:{serde="1.0", tokio="1.20"}\text{dependencies}: \{ \text{serde} = \text{"1.0"},\ \text{tokio} = \text{"1.20"} \}
  3. features 区块定义可选项,让下游使用者按需开启,相当于「加辣/不加辣」的勾选框。
    features:{f1:[dep_a], f2:[dep_b]}\text{features}: \{ f_1: [\text{dep\_a}],\ f_2: [\text{dep\_b}] \}

依赖管理:精确到批次的采购哲学

进货最怕供应商偷偷改配方:今天买的酱油和昨天的味道不一样,整道菜就毁了。Cargo 的采购逻辑是,下单前先问清楚三个数字——主版本、次版本、补丁版本,再决定买哪批货。

这套编号规则叫语义化版本,格式是 主版本.次版本.补丁版本,例如 1.2.3。主版本号(1)变了说明有破坏性改动,老顾客可能吃不了;次版本号(2)变了说明加了新菜但不影响老味道;补丁版本号(3)变了只是修了小 bug,安全得很。

在 Cargo.toml 里写依赖约束时,最常见的写法是 ^1.2.3(读作 caret 约束):它允许解析到 1.x.x 系列中不低于 1.2.3 的任意版本,但绝不跨过 2.0.0。这种「开放但守底线」的策略,既保证了灵活升级,又防止破坏性版本悄悄混入。代码跑起来后,Cargo 会生成 Cargo.lock 文件,把最终解析出的精确版本号全部记录在案——这就是那张「精确到批次」的采购订单。同时,依赖还有传递性:你直接依赖 A,A 又依赖 B,Cargo 会把 B 也自动拉进来解析,像总管查供应商的供应商。

小卡盯着 Cargo.lock 里那串精确到第三位的版本号,恍然大悟:所谓「可复现构建」,就是无论什么时候重进后厨,总管都拿出同一批货,做出同一桌菜。

Accepted(v)  ⟺  majorv=majorr ∧ minorv≥minorr\displaystyle \text{Accepted}(v) \iff \text{major}_v = \text{major}_r \ \land \ \text{minor}_v \ge \text{minor}_r
公式表达 caret 约束的判断条件:候选版本的「主版本」必须与约束要求相同,且「次版本」不能低于约束下限。
  1. 主版本号必须相同^1.2.3 只接受 1.x.x,绝不允许 2.0 悄悄混入,避免破坏性变更。
    majorv=majorr\text{major}_v = \text{major}_r
  2. 次版本号不低于下限只要新版本的功能比要求的多,就可以接受。
    minorv≥minorr\text{minor}_v \ge \text{minor}_r
  3. 补丁版本自由浮动小修小补的版本最安全,Cargo 会自然选择最新的补丁号。
    patchv≥0\text{patch}_v \ge 0

crates.io:中央食材市场

后厨再大,也不可能自己种所有菜、酿所有酱。Rust 社区的解法,是开一家官方中央食材市场——crates.io。货架上摆着几十万种半成品料理包,小卡需要什么,推着小车去进货就行。

这里的「料理包」在 Rust 里叫 crate,是包(package)和库(library)的统称。一个 crate 就是一份打包好的、带说明书(文档)的代码,有的负责序列化数据(如 serde),有的负责异步运行时(如 tokio)。crates.io 是 Rust 官方的包注册中心,相当于市场的管理方,所有 crate 在这里登记、存档、分发。

进货动作极其简单:cargo add serde 一条命令,Cargo 总管就替小卡写好 Cargo.toml 里的依赖声明,并立刻下载到本地;cargo search 关键词 可以在市场里搜货。下载下来的 crate 源码会进入本地的缓存仓库,编译时再引用。国内开发者如果觉得市场太远(网络慢),还可以配置镜像源,相当于在市场旁边开一个本地分仓库,把热门货提前囤好。

小卡用 cargo add serde 三秒进完货,忍不住感叹:以前在其他语言里手动找包、下载、配置路径的日子,跟现在一比简直是原始社会。

Publishable  ⟺  version unique ∧ license set ∧ package verify passed\displaystyle \text{Publishable} \iff \text{version unique} \ \land \ \text{license set} \ \land \ \text{package verify passed}
公式描述一个 crate 能否成功上架 crates.io 的三道门槛:版本号不重复、许可证已声明、打包验证通过。
  1. 版本号唯一市场上不允许出现两个同名同版本的 crate,否则下游会分不清该买哪个。
    version unique\text{version unique}
  2. 许可证声明每个 crate 必须声明开源许可证,相当于食品包装上的配料公示。
    license set\text{license set}
  3. 打包验证通过cargo package 会在本地模拟打包,确保上架后别人能正常下载使用。
    package verify passed\text{package verify passed}

构建与测试:后厨流水线

食材备齐,后厨开火。Cargo 的构建流水线像极了做饭:洗菜切配(编译)、装盘(链接)、出锅(生成可执行文件)。小卡最开心的,是这套流水线全自动——他只需喊一嗓子「上菜」,总管就指挥全员动起来。

cargo build 是标准做菜流程,编译当前项目和所有依赖,产出可执行文件;加 --release 参数则切换到「宴席档」,编译器会花更多时间深度优化,做出的菜运行更快,适合端给客人(交付上线)。日常开发则用 dev 档,出锅快、调试信息全。

开发中最常用的其实是 cargo check——它只检查类型和借用关系是否正确,不生成机器码,速度比完整编译快数倍,好比拿筷子蘸一下尝尝咸淡,不必等整桌菜做完。而真正的质量把关靠 cargo test:代码中用 #[test] 标记的函数就是试菜项目,测试分三层——单元测试(灶台上试小样)、集成测试(整桌菜试吃)、文档测试(说明书里的示例也要能跑通)。

小卡从此养成习惯:改完代码先 cargo check 快速验味,确认无误再 cargo test 正式试菜,最后才敢 cargo run 端上桌——极少翻车。

Binary=Link(⋃i=1nCompile(cratei))\displaystyle \text{Binary} = \text{Link}\left( \bigcup_{i=1}^{n} \text{Compile}(\text{crate}_i) \right)
公式描述最终可执行文件的诞生过程:先把每个 crate 单独编译成目标文件,再把它们全部收集起来链接成最终程序。
  1. 编译每个 crate对第 i 个 crate 执行编译,产出中间目标文件,相当于把每样食材切好备好。
    Compile(cratei)\text{Compile}(\text{crate}_i)
  2. 并集合并把所有编译产物放进同一个集合,任何依赖缺失都会在这一步暴露。
    ⋃i=1nCompile(cratei)\bigcup_{i=1}^{n} \text{Compile}(\text{crate}_i)
  3. 链接成二进制Link 操作把所有目标文件拼装成最终可执行文件,像是把切好的菜摆盘上桌。
    Link(⋅)\text{Link}(\cdot)

工作空间:连锁餐厅的中央厨房

餐厅生意越做越大,小卡开了七家分店,每家都有独立的厨房(crate)。如果每家都单独买菜、单独建库,浪费不说,口味还容易跑偏。Cargo 给出的解法是 workspace——工作空间,相当于一间中央厨房,所有分店共用一套采购订单和一口大锅。

workspace 是 Cargo 管理多个相关 crate 的方式。在根目录的 Cargo.toml 里声明 [workspace] 区块并列出成员,这些成员便纳入统一管理。最大的好处是共享一份 Cargo.lock:所有成员依赖的第三方包版本全局一致,不会出现「A 店用 serde 1.0、B 店用 serde 2.0」的混乱。成员之间用 path 依赖直接互相引用,改公共代码立刻联动。

实际工程里,workspace 常用于把大型项目拆成多个小 crate:一个 common 放公共工具,一个 api 提供服务接口,一个 cli 放命令行入口。每个 crate 保持独立可测试,合在一起又是一个整体。小卡把秘制酱料配方抽成 common 包后,三家分店共用一处配方,改一次全店生效——这正是 workspace 的核心价值。

小卡站在中央厨房里看着七家分店同时出菜,感慨万千:一个人管七个厨房听起来可怕,但在 Cargo 的 workspace 面前,不过是点一下 cargo build 的事。

Workspace=⋃i=1nMemberi,Lock shared by all members\displaystyle \text{Workspace} = \bigcup_{i=1}^{n} \text{Member}_i, \qquad \text{Lock shared by all members}
公式说明工作空间的本质:它是多个成员 crate 的并集,并且所有成员共享同一个依赖锁文件。
  1. 成员 crate每个 Member 是一个独立的后厨灶台,有自己的 Cargo.toml 和源码目录。
    Memberi\text{Member}_i
  2. 并集成组所有成员拼在一起构成完整的大项目,互不冲突。
    ⋃i=1nMemberi\bigcup_{i=1}^{n} \text{Member}_i
  3. 共享锁文件整个 workspace 只有一份 Cargo.lock,确保所有分店菜谱完全一致。
    Lock shared\text{Lock shared}

发布流程:菜谱上架到全球

小卡自创的「内存安全酱」在自家餐厅大受欢迎,师傅说:上架到美食平台吧,让全世界餐厅都能用。这就是 Rust 世界的 cargo publish——把 crate 发布到 crates.io,供全球开发者下载。

发布前要过三道关:版本号必须符合语义化规则且未被占用;必须声明许可证;必须通过 cargo package 本地模拟打包,确保包内容完整、能正常使用。这些都通过了,才执行 cargo publish 正式上架。如果发现某个版本有严重 bug,可以用 cargo yank 把该版本「下架」,阻止新项目依赖它,但已锁定的项目不受影响——好比召回一批有问题的酱油,但已经上桌的菜不收回。

版本迭代同样有规矩:修 bug 升级补丁号(1.2.3 → 1.2.4),加新功能、保持兼容升级次版本号(1.2.3 → 1.3.0),破坏性改动才升级主版本号(1.2.3 → 2.0.0)。这套规则是对下游的承诺:看到主版本没变,大家就敢安心升级。

上架一周后,小卡的酱料被下载了上百万次,他成了后厨圈的明星。回望这趟旅程,从建厨房、买菜、炒菜、试菜到开分店、上架全球——所有杂事,都是那位叫 Cargo 的总管在背后默默操持。

Version′=Version+Δ,Δ∈{major, minor, patch}\displaystyle \text{Version}' = \text{Version} + \Delta, \qquad \Delta \in \{ \text{major},\ \text{minor},\ \text{patch} \}
公式表示版本号的三种递增方式:发布新版本时,要么升主版本、要么升次版本、要么升补丁版本,三选一。
  1. patch 递增只修 bug,第三位加 1,对下游完全安全。
    1.2.3→1.2.41.2.3 \rightarrow 1.2.4
  2. minor 递增新增功能且保持兼容,第二位加 1,下游可放心升级。
    1.2.3→1.3.01.2.3 \rightarrow 1.3.0
  3. major 递增出现破坏性改动,第一位加 1,下游需要仔细阅读升级说明。
    1.2.3→2.0.01.2.3 \rightarrow 2.0.0

AI 应用场景

场景一:AI 推理引擎的依赖治理

阶段:模型部署与工程化

解决的问题:AI 推理服务往往同时依赖高性能计算库、分词器、HTTP 框架等十几个底层 crate,版本错配会导致「编译十分钟后报错」的噩梦。Cargo 用 Cargo.lock 精确锁定每个依赖版本,配合 CI 流水线实现一键复现构建,从源头消灭「在我机器上是好的」这类问题。

具体案例:某 NLP 团队用 Rust 开发高性能打分服务,依赖 candle(LLM 推理库)、tokenizers(分词器)、axum(HTTP 服务)。团队在 CI 中依次执行 cargo fmt、cargo clippy、cargo test、cargo build --release,任何依赖升级必须先过全量测试再合入主干。

场景二:Monorepo 大型 AI 平台的 workspace 拆分

阶段:软件架构与团队协作

解决的问题:AI 平台常包含数据管道、模型服务、特征存储等模块,若全部塞进一个 crate,编译缓慢、耦合严重。工作空间 workspace 让各模块独立成 crate,共享一份依赖锁文件,公共逻辑抽成 common 成员,一处修改全链路生效。

具体案例:某推荐系统团队维护一个含 12 个 crate 的 workspace:common(公共工具)、feature_server、rank_service、offline_pipeline 等。每次 cargo build 一次性构建全部成员,CI 中任一 crate 测试失败都会阻断发布。

场景三:Rust 版 AI 开源库的分发

阶段:开源社区与生态建设

解决的问题:研究者写好一个 Rust 版模型推理库,若手动分发源码包,用户安装成本极高。通过 cargo publish 发布到 crates.io,全球开发者一条 cargo add 命令即可拉取,传递依赖自动解析,文档示例还能通过文档测试自动验证。

具体案例:HuggingFace 的 candle、rust-bert 等 Rust AI 库都通过 crates.io 分发,严格遵循语义化版本:新模型支持升 minor,破坏性 API 变更升 major,让下游企业敢大胆升级。

Python 演示

python70 行
from collections import deque  # 用字典模拟 Cargo.toml:包名 -> 版本号 + 依赖清单(约束)cargo_toml = {    "my_app": {"version": "0.1.0", "deps": {"serde": "^1.0", "tokio": "^1.20"}},    "serde": {"version": "1.0.200", "deps": {"serde_derive": "^1.0"}},    "serde_derive": {"version": "1.0.200", "deps": {}},    "tokio": {"version": "1.35.0", "deps": {"bytes": "^1.0"}},    "bytes": {"version": "1.5.0", "deps": {}},}  def parse_version(v):    return tuple(int(x) for x in v.split("."))  def match_constraint(constraint, version):    # 实现 caret(^) 约束: 主版本相同且不低于下限    if constraint.startswith("^"):        lo = parse_version(constraint[1:])        v = parse_version(version)        return v[0] == lo[0] and v >= lo    return version == constraint  def resolve(root):    # BFS 收集所有传递依赖, 得到精确锁定计划    plan = {}    queue = deque([root])    while queue:        name = queue.popleft()        if name in plan:            continue        meta = cargo_toml[name]        for dep, req in meta.get("deps", {}).items():            v = cargo_toml[dep]["version"]            if match_constraint(req, v):                plan[dep] = v                queue.append(dep)    plan[root] = cargo_toml[root]["version"]    return plan  def build_order(plan):    # 拓扑排序: 依赖别人的先编译, 被依赖的后编译    indeg = {name: 0 for name in plan}    for name in plan:        for dep in cargo_toml[name].get("deps", {}):            if dep in plan:                indeg[dep] += 1    ready = deque([name for name in plan if indeg[name] == 0])    order = []    while ready:        name = ready.popleft()        order.append(name)        for dep in cargo_toml[name].get("deps", {}):            if dep in plan:                indeg[dep] -= 1                if indeg[dep] == 0:                    ready.append(dep)    return order  if __name__ == "__main__":    lock = resolve("my_app")    print("Cargo.lock resolved:")    for name, version in sorted(lock.items()):        print("  {} = {}".format(name, version))    print("Build order:", " -> ".join(build_order(lock)))
目的:用 Python 模拟 Cargo 的核心流程——根据 Cargo.toml 的版本约束解析出精确锁定方案(Cargo.lock),再按依赖关系输出自底向上的构建顺序。
  1. cargo_toml = {...}用字典模拟 Cargo.toml,记录每个包自己声明的版本和对下游的约束。
  2. def match_constraint(...)实现 caret(^)版本约束:主版本相同且版本号不低于约束下限。
  3. queue = deque([root])BFS 广度优先遍历,像 Cargo 一样逐层收集所有传递依赖。
  4. for dep, req in meta.get("deps", {}).items():遍历每个包的依赖清单,逐条核对能否满足约束。
  5. plan[dep] = v把通过约束检查的精确版本写入 plan 字典,相当于生成 Cargo.lock。
  6. if indeg[dep] == 0:当某个包的所有依赖都已就绪,它才进入构建队列。
  7. print("Build order:", ...)输出与 Cargo 一致的从依赖底层到顶层的构建顺序。

常见误区

误区Cargo 不就是从网上下载库的工具吗?下载完就没它什么事了,编译是编译器 rustc 的活。

正解Cargo 是「全流程后厨总管」:它负责建项目、下依赖、编译调度、跑测试、发布上架,下载只是它日常工作的一小环。cargo build 背后是 Cargo 自动调用 rustc、管理编译顺序、缓存增量产物。它更像 npm + make + pytest 的一体化综合体,而不是单纯的下载器。

为何常见:新手最先接触 cargo add 或 cargo build 时总看到它在联网拉包,于是把「拉包」当成了全部职责。

误区Cargo.lock 是自动生成的临时文件,应该加进 .gitignore 里,不用管它。

正解这得看项目类型。二进制应用(能跑的程序)必须提交 Cargo.lock,保证每次发布构建的依赖完全一致,这是可复现部署的基石;但库项目(给别人用的 crate)通常不提交,因为库需要在下游更宽的版本范围内解析,提交反而会限制兼容性。一句话:应用锁配方,库留灵活。

为何常见:Cargo.lock 确实由工具自动生成,容易被误认为「垃圾产物」,而实际上它是保证「同一批货做同一桌菜」的定海神针。

误区两个都是编译命令,结果应该一样,随便用哪个都行。

正解两者差异巨大。cargo check 只做类型检查和借用检查,不生成机器码,速度比完整编译快数倍,适合开发中高频验证;cargo build 才真正产出可执行文件。类比来说,check 是拿筷子蘸一下尝咸淡,build 是整套上菜流程。把 check 当 build 用会拿不到程序,把 build 当 check 用会浪费大量时间。

为何常见:两条命令语法几乎一样、输出也都很快,新手容易误以为 check 只是 build 的加速模式,而忽略它「只验味不出菜」的本质。

误区版本号嘛,从 0.0.1 开始,改一个 bug 就加一位,全凭心情。

正解Rust 生态默认语义化版本:主版本(major)代表破坏性变更、次版本(minor)代表新增兼容功能、补丁版本(patch)代表修 bug。随意乱升会导致依赖方在 ^1.0.0 约束下解析到不兼容的新版本,运行时直接崩溃。这就好比悄悄把菜谱里的主料换了,却跟客人说「配方没变」——信任就是这样崩掉的。

为何常见:没经历过下游项目被 breaking change 破坏的痛苦,就难以理解版本号本质上是「对兼容性的承诺」。

面试题

Q1. Cargo 中用来在空目录里初始化一个新项目的命令是?

单选题 · easy

  • A.cargo new
  • B.cargo init← 正确答案
  • C.cargo build
  • D.cargo run

答案:B。cargo init 在已有空目录中初始化项目;cargo new 是新建一个全新目录并在其中初始化。build 和 run 分别是编译和运行,不是初始化命令。类比:init 是在现有厨房里布置灶台,new 是连厨房一起盖好。

Q2. Cargo.toml 中 [dependencies] 区块的作用是?

单选题 · easy

  • A.声明项目名和版本号
  • B.列出项目依赖的第三方 crate 及版本约束← 正确答案
  • C.配置编译优化等级
  • D.定义单元测试函数

答案:B。[dependencies] 是采购清单,列出所有需要的料理包及版本要求。项目名和版本号在 [package] 区块,编译优化在 [profile] 区块,测试函数写在代码里而非 Cargo.toml 中。这道题考的正是 Cargo.toml 各区块的职责分工。

Q3. 关于语义化版本 1.2.3,其中「3」代表什么?

单选题 · easy

  • A.主版本号,表示破坏性变更
  • B.次版本号,表示新增功能
  • C.补丁版本号,表示 bug 修复← 正确答案
  • D.构建流水线编号

答案:C。语义化版本格式为「主。次。补丁」:主版本破坏性变更,次版本新增兼容功能,补丁版本只修 bug。「3」是第三位,即补丁号。把它理解为「换菜谱主料 / 加新菜 / 修调味」三种改动程度的等级即可。

Q4. 哪个文件记录了最终锁定后的精确依赖版本,保证每次构建结果一致?

单选题 · easy

  • A.Cargo.toml
  • B.Cargo.lock← 正确答案
  • C..gitignore
  • D.target/config.json

答案:B。Cargo.lock 是「精确到批次的采购订单」,记录解析出的精确版本号。Cargo.toml 只写版本范围(如 ^1.0),不锁定具体版本;.gitignore 管理版本控制忽略规则;target 目录是编译产物,其中没有 config.json 这类文件。

Q5. 依赖约束 ^1.2.3 允许解析到下列哪个版本?

单选题 · mid

  • A.0.9.0
  • B.2.0.0
  • C.1.9.0← 正确答案
  • D.1.2.2

答案:C。caret(^)表示「主版本相同且不低于下限」:^1.2.3 接受 >=1.2.3 且 <2.0.0 的版本。1.9.0 满足条件;0.9.0 主版本不对,2.0.0 跨了主版本,1.2.2 低于下限。这就像「同一个菜系系列,但不能换主料」,Cargo 会选范围内最新的合规版本。

Q6. 关于 Cargo.lock 的提交策略,下列说法正确的是?

单选题 · mid

  • A.二进制项目与库项目都应提交 Cargo.lock
  • B.二进制项目与库项目都不应提交 Cargo.lock
  • C.二进制项目应忽略 Cargo.lock,库项目应提交
  • D.二进制项目应提交 Cargo.lock,库项目通常忽略← 正确答案

答案:D。二进制应用提交 lock,确保部署时可复现同一套依赖;库项目通常忽略 lock,以便在下游更宽的版本范围内解析,提升兼容性。类比:自营餐厅要锁定进货批次保证口味一致,半成品供应商则要适应不同买家的厨房条件。

Q7. 下列哪些属于 Cargo 的常用命令?

多选题 · mid

  • A.cargo build← 正确答案
  • B.cargo test← 正确答案
  • C.cargo pip
  • D.cargo check← 正确答案

答案:ABD。build(编译)、test(测试)、check(快速检查)都是 Cargo 内置命令。cargo pip 并不存在——pip 是 Python 的工具,Cargo 生态中下载依赖用 cargo add。这道题提醒我们别把其他语言的包管理命令混淆进 Cargo。

Q8. 关于 workspace(工作空间)的描述,正确的有哪些?

多选题 · mid

  • A.多个成员 crate 共享一份 Cargo.lock← 正确答案
  • B.每个成员 crate 必须有独立的 Cargo.lock
  • C.成员之间可以用 path 依赖互相引用← 正确答案
  • D.workspace 中的所有成员必须包含相同代码

答案:AC。workspace 的价值正在于共享一份 Cargo.lock,让所有成员依赖版本全局一致,因此 B 错误;成员之间通过 path 依赖互引是常规操作,C 正确;成员是独立的 crate,各自代码不同、职责不同,D 错误。类比中央厨房:各分店菜不同,但共用同一套进货订单。

Q9. cargo publish 发布 crate 到 crates.io 之前,以下哪项不是必要检查?

单选题 · hard

  • A.版本号在 crates.io 上未被占用
  • B.已声明许可证(license)
  • C.通过 cargo package 本地打包验证
  • D.已上传编译好的二进制文件到 crates.io← 正确答案

答案:D。crates.io 托管的是源码包,不直接接收编译好的二进制文件,下载后由使用方的 Cargo 负责编译。发布前必须保证版本唯一、license 已声明、打包验证通过。这好比美食平台上架的是「菜谱和半成品包」,而不是已经炒好的菜。

Q10. 在 Rust 大型项目中利用 Cargo 进行模块拆分,下列哪些做法合理?

多选题 · hard

  • A.将公共逻辑抽成 workspace 中的 common 成员 crate← 正确答案
  • B.不同业务模块通过 path 依赖引用 workspace 内的公共 crate← 正确答案
  • C.为了避免依赖问题,把所有代码堆在一个超大 main.rs 中
  • D.对每个成员 crate 分别执行 cargo test 验证其正确性← 正确答案

答案:ABD。A、B、D 都是 workspace 拆分的常规操作:公共逻辑下沉为 common,业务模块用 path 依赖它,各成员独立测试。C 是反面教材——巨无霸单文件会拖慢编译、增加耦合,违背 Cargo 的模块化设计初衷。