13、Rust程序设计语言——认识Cargo和Crates.io
目录1. 采用发布配置自定义构建2. 将crate发布到Crates.io2.1 编写有用的文档注释2.1.1 常用章节2.1.2 文档注释作为测试2.1.3 注释包含项的结构2.2 导出实用的公有API2.3 创建crates.io账号2.4 向新crate添加元数据2.5 发布到crates.io2.6 发布现有crate的新版本2.7 使用cargo yank从Crates.io撤回版本3. Cargo工作空间3.1 创建工作空间3.2 在工作空间中创建第二个包3.2.1 依赖外部包3.2.2 为工作空间增加测试4. 使用cargo install安装二进制文件5. Cargo自定义扩展命令参考1. 采用发布配置自定义构建在Rust中发布配置release profiles是预定义且可定制的配置文件集它们包含不同的配置允许程序员灵活地控制代码编译的多种选项。每一种配置都独立于其他配置。Cargo有两个主要的配置运行cargo build时采用的dev配置和运行cargo build --release的release配置。dev配置为开发定义了良好的默认配置release配置则为发布构建定义了良好的默认配置。当项目的Cargo.toml文件没有显式增加任何[profile.*]部分的时候Cargo会对每一个配置都采用默认设置。通过增加任何希望定制的配置对应的[profile.*]部分我们可以选择覆盖任意默认设置的子集。如下是dev和release配置的opt-level设置的默认值[profile.dev] opt-level 0 [profile.release] opt-level 3opt-level设置控制Rust会对代码进行何种程度的优化。这个配置的值从0到3.越高的优化级别需要更多的时间编译如果你在进行开发并经常编译可能会希望在牺牲一些代码性能的情况下减少优化以便编译得快一点。因此dev的opt-level默认为0.当你准备发布时花费更多时间在编译上则更好。只需要在发布模式编译一次而编译出来的程序则会运行很多次所以发布模式用更长的编译时间换取运行更快的代码因此release配置的默认opt-level为32. 将crate发布到Crates.io我们在项目中使用过crates.io上的包作为依赖也可以通过发布自己的包来向他人分享代码。crates.io上的crate注册表会分发你包的源代码因此它主要托管开源代码。2.1 编写有用的文档注释准确的包文档有助于其他用户理解如何以及何时使用它们所以花一些时间编写文档时值得。//可以注释代码但Rust也有特定的用于文档的注释类型通常被称为文档注释它们会生成HTML文档。这些HTML文档展示公有API文档注释的内容它们意在让对库感兴趣的程序员理解如何使用这个crate而不是它是如何被实现的。文档注释使用三条斜杠///而不是两条斜杠并且支持使用Markdown标记来格式化文本。将文档注释放在它所说明的项之前。/// Adds one to the number given.////// # Examples////// /// let arg 5;/// let answer my_crate::add_one(arg);////// assert_eq!(6, answer);/// pubfnadd_one(x:i32)-i32{x1}我描述了add_one函数的功能接着以Examples为标题开始了一小节并给出了展示如何使用add_one函数的代码。可以运行cargo doc来根据这些文档注释生成HTML文档。这个命令会运行Rust自带的rustdoc工具并将生成的HTML文档放到target/doc目录中。运行cargo doc --open会为当前crate的文档构建HTML并在浏览器中打开结果。定位到add_one函数时你会看到文档注释中的文本是如何被渲染的2.1.1 常用章节上面的文档展示了使用# Examples Markdown标题在HTML中创建了一个以Examples为标题的部分。其他一些crate作者经常在文档注释中使用的部分有Panics函数在什么情况下可能会panic!。不希望程序panic的调用者应确保不会在这些情况下调用该函数。Errors如果函数返回Result说明可能出现哪些错误以及什么条件会导致返回这些错误会有助于调用者编写代码以不同方式处理不同种类的错误。Safety如果调用该函数时unsafe的(后面会将不安全代码)这里应该解释为什么它是不安全的并说明函数要求调用者维持哪些不变式。大多数文档注释不需要包含所有这些章节但这是一份很好的检查清单可以提醒你关注用户会想了解的内容。2.1.2 文档注释作为测试在文档注释中添加示例代码块有助于展示如何使用你的库而且还有一个额外的好处运行cargo test时文档中的示例代码也会作为测试运行没有什么比带示例的文档更好了但也没有什么比示例失败的文档更糟糕了。运行cargo test如果我们修改函数或示例中的任意一方使示例里的assert_eq!触发panic然后再次运行cargo test就会看到文档测试捕获了示例与代码不同步的问题。2.1.3 注释包含项的结构//!这种文档注释风格为“包含这些注释的项”添加文档而不是为“位于这些注释之后的项”添加文档。我们通常在crate根文件src/lib.rs或模块内部使用这种文档注释为整个crate或整个模块编写说明。为了添加描述包含add_one函数的my crate crate用于的文档可以在src/lib.rs文件开头加入以//!开头的文档注释//! # My Crate//!//! my_crate is a collection of utilities to make performing certain//! calculations more convenient./// Adds one to the number given.// 省略最后一行以//!开头的注释后面没有代码。因为我们使用的是//!而不是///所以这里记录的是“包含这条注释的项”的文档而不是“紧随这条注释之后的项”的文档。在这里这个项就是src/lib.rs文件也就是crate根。这些注释描述的是真个crate。运行cargo doc --open项内部的文档注释特别适合用来描述crate和模块。使用它们来解释这个容器整体的目的可以帮助用户理解crate的组织方式。2.2 导出实用的公有API公有API的结构是你发布crate时主要需要考虑的。crate用户没有你那么熟悉其结构并且如果模块层级过大他们可能会难以找到所需的部分。第七章介绍了如何使用pub关键字使项公开以及如何使用use关键字将项引入作用域。不过在你开发crate时对你来说合理的结果对用户而言可能并不方便。你可能想把结构体组织成一个包含多层的层级结构但想使用你定义在深层级中的某个类型的人可能很难发现它的存在。他们也可能会厌烦不得不写use my_crate::some_module::another_module::UsefulType;而不是简单的use my_crate::UsefulType;。好消息是如果这种结构对外部用户来说并不方便你也不必重新安排内部组织。你可以使用pub use来重导出项从而建立一个与私有结构不同的公有结构。重导出会把某个位置的公有项在另一个位置再次公开就好像它原本就定义在哪里一样。假设我们创建了一个名为art的库用来建模艺术概念。在这个库里有两个模块kinds模块包含两个枚举PrimaryColor和SecondaryColorutils模块包含一个名为mix的函数//! # Art//!//! A library for modeling artistic concepts.pubmodkinds{/// The primary colors according to the RYB color model.pubenumPrimaryColor{Red,Yellow,Blue,}/// The secondary colors according to the RYB color model.pubenumSecondaryColor{Orange,Green,Purple,}}pubmodutils{usecrate::kinds::*;/// Combines two primary colors in equal amounts to create/// a secondary color.pubfnmix(c1:PrimaryColor,c2:PrimaryColor)-SecondaryColor{// 省略}}下面是这个crate生成的文档首页。注意PrimaryColor和SecondaryColor类型、以及mix函数都没有在首页中列出。我们必须点击kinds或utils才能看到它们。依赖这个库的另一个crate需要使用use语句把art中的项引入作用域同时必须指定当前定义的模块结构。如下所示useart::kinds::PrimaryColor;useart::utils::mix;fnmain(){letredPrimaryColor::Red;letyellowPrimaryColor::Yellow;mix(red,yellow);}写出上面代码的作者必须先弄清楚PrimaryColor在kinds模块中而mix在utils模块中。art crate的模块结构对开发art crate的人来说比对使用它的人更有意义。这种内部结构并没有给想理解如何使用art crate的人提供有价值的信息反而会带来困惑因为用户必须先搞清楚该去哪里找需要的内容还要在use语句中写出模块名。为了从公有API中去掉内部组织细节在art crate中加入pub use语句在顶层导出这些项。//! # Art//!//! A library for modeling artistic concepts.pubuseself::kinds::PrimaryColor;pubuseself::kinds::SecondaryColor;pubuseself::utils::mix;pubmodkinds{// 省略}pubmodutils{// 省略}运行cargo doc --open之后可以在首页看到重导出项这使PrimaryColor、SecondaryColor和mix更容易被找到。art crate的用户仍然可以像网页文档看的一样使用art中的内部结构useart::PrimaryColor;useart::mix;fnmain(){// 省略}在存在很多嵌套模块的情况下使用pub use将类型重导出到顶层会显著改善使用这个crate的体验。pub use的另一个用法是把当前crate的某个依赖中的定义重新导出让那个crate的定义称为你这个crate公有API的一部分。创建有用的公有API结构更像一门艺术而不是科学你可以不断迭代找到最适合用户的API。选择pub use能让你在crate内部结构的组织方式上保持灵活并将其与你呈现给用户的结构解耦。2.3 创建crates.io账号在发布任何crate之前你需要在crates.io上创建账号并获取一个API token。为此请访问crates.io首页并通过Github账号登陆。登录之后前往https://crates.io/me的账户设置页面获取API key。然后运行cargo login命令并在提示时粘贴你的API key。这个命令会把你的API token告诉cargo并将其保存在本地的~/.cargo/credentials文件中。所有token都是秘密不应该与任何人分享。如果泄露了立即前往crates.io撤销并重新生成一个token。2.4 向新crate添加元数据比如说你已经有一个希望发布的crate。在发布之前你需要在crate的Cargo.toml文件的[package]部分增加一些本crate的元数据。首先crate需要一个唯一的名称。虽然在本地开发crate时可以随意命名但crates.io上的crate名称遵循先到先得的原则。一但某个crate名称已经被占据就没有其他人能再用这个名称发布crate。请搜索你想使用的名称确认它是否已被占用。如果没有就把Cargo.toml中[package]里的name字段改成你想发布时使用的名称如下所示[package] name guessing_game即使你选择了一个唯一的名称如果此时尝试运行cargo publish发布该crate的话会得到一个警告缺少一些关键信息——关于该crate用途的描述以及用户可以在什么许可条款下使用它。在Cargo.toml中添加一两句简短描述即可因为它会在搜索结果中和你的crate一起显示。对于license字段你需要填写一个许可证标识符值。Linux基金会的Software Package Data Exchange(SPDX)列出了可用的标识符。如果使用MIT License如下所示[package] name guessing_name license MIT如果你想使用SPDX中不存在的许可证就需要把许可证文本放入一个文件中将该文件包含到项目里然后使用license-file指定该文件名而不是使用license字段。很多Rust社区成员选择与Rust本身相同的许可证也就是双许可证MIT OR Apache-2.0。可以使用OR分隔多个许可证标识符来为项目指定多个许可证。有了唯一的名称、版本号、由cargo new新建项目时增加的作者信息、描述和所选择的license已经准备好发布的项目的Cargo.toml文件看起来如下[package] name guessing_game version 0.1.0 edition 2024 description A fun game where you guess what number the computer has chosen. license MIT OR Apache-2.0 [dependencies]2.5 发布到crates.io在创建了账号、保存了API token、为crate准备好名字以及元数据之后可以发布了。发布crate会将该crate的某个特定版本上传到crates.io供他人使用。发布crate时务必小心发布是永久性的。对应版本无法被覆盖其代码也无法被删除。crates.io的一个主要目标是充当代码的永久归档服务器这样所有依赖crates.io上crate的项目可以一直正常工作。而如果允许删除版本就无法实现这一目标。不过可发布的版本号数量并没有限制。运行cargo publish发布。2.6 发布现有crate的新版本当你修改了crate并准备发布新版本时修改Cargo.toml中version的值。请使用语义化版本控制规则根据修改的类型决定下一个版本号。然后再次运行cargo publish来上传新版本。2.7 使用cargo yank从Crates.io撤回版本虽然你不能删除crate的历史版本但可以阻止未来的新项目把它加入依赖。这在某个版本因为某种原因损坏时会很用。为此Cargo支持对某个版本执行撤回yank。撤回某个版本会阻止新项目依赖这个版本不过所有已经依赖它的项目仍然可以下载并继续依赖它。从本质上说撤回意味着所有已有Cargo.lock的项目都不会因此损坏而任何新生成的Cargo.lock都不会再使用被撤回的版本。要撤回crate的某个版本请在之前发布该crate的目录中运行cargo yank并指定要撤回的版本。比如如果我们发布了名为guessing_game的crate的1.0.1版本并想撤回它就在guessing_game项目目录中运行cargoyank--vers1.0.1也可以撤销这次撤回让项目重新可以依赖该版本只需在命令中加上–undocargoyank--vers1.0.1--undo撤回不会删除任何代码。例如撤回功能并不能删除你不小心上传的秘密信息。如果发生了这种情况请立刻轮换这些秘密信息。3. Cargo工作空间Cargo提供了一项叫做工作空间workspace的功能可以帮助管理多个彼此相关、并行开发的包。3.1 创建工作空间工作空间是一组共享同一个Cargo.lock和输出目录的包。下面创建一个工作空间包含一个二进制crate和两个库。二进crate提供主要功能并依赖这两个库。一个库提供add_one函数另一个库提供add_two函数。三个crate属于同一个工作空间。创建工作空间的目录mkdiraddcdadd在add目录中新建Cargo.toml文件用来配置整个工作空间。这个文件不会有[package]部分而是会以[workspace]部分开头这样我们就能向工作空间添加成员。我们还会把resolver的值设为3以便在工作空间中使用Cargo最新的依赖解析算法。[workspace] resolver 3接下来在add目录运行cargo new新建adder二进制cratecargonew adder在工作空间中运行cargo new时新创建的包也会被自动加入工作空间Cargo.toml中[workspace]定义的members键[workspace] resolver 3 members [adder]可以运行cargo build来构建工作空间。工作空间在顶层只有一个target目录用来存放编译产物adder包不会有自己的target目录。即使我们在adder目录中运行cargo build编译产物也仍会放到add/target而不是add/adder/target。Cargo之所以这样组织工作空间中的target目录是因为工作空间中的crate本来就是要彼此依赖的。如果每个crate都有各自的target目录那么每个crate都不得不重新编译工作空间中的其他crate才能把产物放进自己的target目录。共享一个target目录可以避免不必要的重复构建。3.2 在工作空间中创建第二个包在工作空间中创建另一个成员包并将其命名为add_one。cargonew add_one--lib现在add目录包含如下目录和文件在add_one/src/lib.rs中增加一个add_one函数pubfnadd_one(x:i32)-i32{x1}现在可以让二进制包adder依赖包含库的add_one包。首先需要在adder/Cargo.toml中把add_one添加为一个路径依赖[dependencies] add_one { path ../add_one }Cargo并不会假定工作空间中的crate会彼此依赖因为我们需要显式声明这些依赖关系。在adder crate中使用add_one crate里的add_one函数。在adder/src/main.rs中调用add_onefnmain(){letnum10;println!(Hello, World! {num} plus one is {}!,add_one::add_one(num));}在顶层add目录中运行cargo build来构建工作空间。要从add目录运行这个二进制crate可以在cargo run时通过-p参数加上包名指定要运行工作空间中的哪个包cargorun-padder这会运行adder/src/main.rs中的代码其依赖add_one crate。3.2.1 依赖外部包注意工作空间只在顶层有一个Cargo.lock文件而不是让每个crate目录里都各自有一个Cargo.lock。这能确保所有crate使用的都是同一个版本的依赖。如果我们把rand包同时加到adder/Cargo.toml和add_one/Cargo.toml中Cargo会把它们都解析为同一个rand版本并把结果记录到唯一的Cargo.lock中。让工作空间中的所有crate使用相同依赖意味着这些crate会始终彼此兼容。将rand crate加到add_one/Cargo.toml的[dependencies]部分[dependencies] rand 0.10.2在add_one/src/lib.rs中加入use rand;然后在add目录中运行cargo build来构建整个工作空间这会引入并编译rand crate。顶层的Cargo.lock现在已经包含了add_one依赖rand的信息。不过即使rand在工作空间的某处被使用我们也不能直接在工作空间里的其他crate中使用它除非把rand加到它们各自的Cargo.toml中。要修复这个错误必须把rand也加到adder的Cargo.toml中。这样在构建adder包的时候才会将rand加到Cargo.lock中adder的依赖列表里。Cargo会确保工作空间中每个使用rand的crate都使用同一个版本只要它们声明的是彼此兼容的rand版本这样既节省空间也确保工作空间中的crate彼此兼容。如果工作空间中的crate为同一个依赖指定了彼此不兼容的版本Cargo仍然会分别解析它们但会尽量把版本数量控制得尽可能少。3.2.2 为工作空间增加测试为add_one::add_one函数增加一个测试pubfnadd_one(x:i32)-i32{x1}#[cfg(test)]modtests{usesuper::*;#[test]fnit_works(){assert_eq!(3,add_one(2));}}在根目录下运行cargo test会执行工作空间中所有crate的测试可以在根目录中通过-p参数并指定想要测试的crate名称如果你打算把工作空间中的crate发布到crates.io上那么工作空间中的每个crate都需要单独发布。和cargo test一样可以通过-p参数并指定要发布的crate名称来发布工作空间中的某个特定crate。4. 使用cargo install安装二进制文件cargo install命令允许你在本地安装和使用二进制crate。它并不是为了替代系统包管理器而是为Rust开发者提供一种方便的方式用来安装他人在crates.io上分享的工具。只有带有二进制目标的包才能被安装。二进制目标是指当crate包含src/main.rs文件或将其他文件指定为二进制目标时所生成的可运行文件这与库目标不同库目标本身不能单独运行但适合被其他程序接入。通常crate的README文件会说明它是库、带有二进制目标还是两者兼有。所有通过cargo install安装的二进制文件都会放在安装根目录下的bin文件夹中。请确保这个bin文件夹已经在系统环境变量PATH中。5. Cargo自定义扩展命令Cargo的设计允许你用新的子命令来扩展它而不必修改Cargo本身。如果你的PATH中有一个名为cargo-something的二进制文件那么你可以像Cargo子命令一样通过cargo something来运行它。这类自定义命令也会在你运行cargo --list时显示出来。Cargo这种设计带来了一个非常方便的好处你可以引用cargo install安装扩展然后像使用Cargo内建工具一样运行它们。参考1、进一步认识Cargo和Crates.io
