认识 Cargo

但凡经历过 C/C++ 或 Go 语言 1.10 版本之前的用户都知道,一个好的包管理工具有多么的重要!!我那个时候是如此的渴望类似 nodejsnpm 包管理工具,但是却求而不得。

包管理工具最重要的意义就是任何用户拿到你的代码,都能运行起来,而不会因为各种包版本依赖焦头烂额。

Go 语言在 1.10 版本之前,所有的包都是在 github.com 下存放,导致了所有的项目都公用一套依赖代码,在本地项目复杂后,这简直是一种灾难。

说多了都是泪,笔者目前还有一个早期 Go 的项目(15 年写的),用到了 iris (一个坑爹 HTTP 服务),结果现在运行不起来了,因为找不到 iris 当时的那个版本了!!

作为一门现代化语言,Rust 吸收了多个语言的包管理优点,为大家提供超级大杀器: cargo,真的,再挑剔的开发者,都对它赞不绝口。👍

总而言之,cargo 提供了一系列的工具,从项目的建立、构建到测试、运行直至部署,为 Rust 项目的管理提供尽可能完整的手段。同时,与 Rust 语言及其编译器 rustc 紧密结合,可以说用了后就忘不掉,如同初恋般的感觉。

创建一个"你好,世界"项目

又见"你好,世界",肯定有读者在批评了:你就不能有点创意吗?"世界,你好"难道不配?你是读者,你说了算,那我们就来创建一个"世界,你好"。

上文提到,Rust 语言的包管理工具是 cargo。不过,我们无需再手动安装,之前安装 Rust 的时候,就已经一并安装了。

终于到了紧张刺激的 new new new 环节:

$ cargo new world_hello
$ cd world_hello

上面的命令使用 cargo new 创建一个项目,项目名是 world_hello (向读者势力低头的项目名称,泪奔),该项目的结构和配置文件都是由 cargo 生成,意味着我们的项目被 cargo 所管理

如果你在终端无法使用这个命令,考虑一下 环境变量 是否正确的设置:把 cargo 可执行文件所在的目录添加到环境变量中。

如果是在 Windows 的 WSL2 子系统下,出现以下错误:

error: command failed: 'rustc' error: caused by: Permission denied (os error 13)

可尝试先卸载,再使用sudo命令进行安装:$ sudo curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

早期的 cargo 在创建项目时,必须添加 --bin 的参数,如下所示:

$ cargo new world_hello --bin
$ cd world_hello

现在的版本,已经无需此参数,cargo 默认就创建 bin 类型的项目,顺便说一句,Rust 项目主要分为两个类型:binlib,前者是一个可运行的项目,后者是一个依赖库项目。

下面来看看创建的项目结构:

$ tree
.
├── .git
├── .gitignore
├── Cargo.toml
└── src
    └── main.rs

是的,连 git 都给你创建了,不禁令人感叹,不是女儿,胜似女儿,比小棉袄还体贴。

运行项目

有两种方式可以运行项目:

  1. cargo run

  2. 手动编译和运行项目

首先来看看第一种方式,一码胜似千言,在之前创建的 world_hello 目录下运行:

$ cargo run
   Compiling world_hello v0.1.0 (/Users/sunfei/development/rust/world_hello)
    Finished dev [unoptimized + debuginfo] target(s) in 0.43s
     Running `target/debug/world_hello`
Hello, world!

好了,你已经看到程序的输出:"Hello, world"

如果你安装的 Rust 的 host triplex86_64-pc-windows-msvc 并确认 Rust 已经正确安装,但在终端上运行上述命令时,出现类似如下的错误摘要 linking with `link.exe` failed: exit code: 1181,请使用 Visual Studio Installer 安装 Windows SDK

可能有读者不愿意了,说好了"世界,你好"呢?别急,在下一节,我们再对代码进行修改。(认真想来,"你好,世界“强调的是我对世界说你好,而"世界,你好“是世界对我说你好,明显是后者更有包容性和国际范儿,读者真·好眼光。)

上述代码,cargo run 首先对项目进行编译,然后再运行,因此它实际上等同于运行了两个指令,下面我们手动试一下编译和运行项目:

编译

$ cargo build
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s

运行

$ ./target/debug/world_hello
Hello, world!

行云流水,但谈不上一气呵成。 细心的读者可能已经发现,在调用的时候,路径 ./target/debug/world_hello 中有一个明晃晃的 debug 字段,没错我们运行的是 debug 模式,在这种模式下,代码的编译速度会非常快,可是福兮祸所伏,运行速度就慢了. 原因是,在 debug 模式下,Rust 编译器不会做任何的优化,只为了尽快的编译完成,让你的开发流程更加顺畅。

作为尊贵的读者,咱自然可以要求更多,比如你想要高性能的代码怎么办? 简单,添加 --release 来编译:

  • cargo run --release
  • cargo build --release

试着运行一下我们高性能的 release 程序:

$ ./target/release/world_hello
Hello, world!

cargo check

当项目大了后,cargo runcargo build 不可避免的会变慢,那么有没有更快的方式来验证代码的正确性呢?大杀器来了,接着!

cargo check 是我们在代码开发过程中最常用的命令,它的作用很简单:快速的检查一下代码能否编译通过。因此该命令速度会非常快,能节省大量的编译时间。

$ cargo check
    Checking world_hello v0.1.0 (/Users/sunfei/development/rust/world_hello)
    Finished dev [unoptimized + debuginfo] target(s) in 0.06s

Rust 虽然编译速度还行,但是还是不能与 Go 语言相提并论,因为 Rust 需要做很多复杂的编译优化和语言特性解析,甚至连如何优化编译速度都成了一门学问: 优化编译速度

Cargo.toml 和 Cargo.lock

Cargo.tomlCargo.lockcargo 的核心文件,它的所有活动均基于此二者。

  • Cargo.tomlcargo 特有的项目数据描述文件。它存储了项目的所有元配置信息,如果 Rust 开发者希望 Rust 项目能够按照期望的方式进行构建、测试和运行,那么,必须按照合理的方式构建 Cargo.toml

  • Cargo.lock 文件是 cargo 工具根据同一项目的 toml 文件生成的项目依赖详细清单,因此我们一般不用修改它,只需要对着 Cargo.toml 文件撸就行了。

什么情况下该把 Cargo.lock 上传到 git 仓库里?很简单,当你的项目是一个可运行的程序时,就上传 Cargo.lock,如果是一个依赖库项目,那么请把它添加到 .gitignore 中。

现在用 VSCode 打开上面创建的"世界,你好"项目,然后进入根目录的 Cargo.toml 文件,可以看到该文件包含不少信息:

package 配置段落

package 中记录了项目的描述信息,典型的如下:

[package]
name = "world_hello"
version = "0.1.0"
edition = "2021"

name 字段定义了项目名称,version 字段定义当前版本,新项目默认是 0.1.0edition 字段定义了我们使用的 Rust 大版本。因为本书很新(不仅仅是现在新,未来也将及时修订,跟得上 Rust 的小步伐),所以使用的是 Rust edition 2021 大版本,详情见 Rust 版本详解

定义项目依赖

使用 cargo 工具的最大优势就在于,能够对该项目的各种依赖项进行方便、统一和灵活的管理。

Cargo.toml 中,主要通过各种依赖段落来描述该项目的各种依赖项:

  • 基于 Rust 官方仓库 crates.io,通过版本说明来描述
  • 基于项目源代码的 git 仓库地址,通过 URL 来描述
  • 基于本地项目的绝对路径或者相对路径,通过类 Unix 模式的路径来描述

这三种形式具体写法如下:

[dependencies]
rand = "0.3"
hammer = { version = "0.5.0"}
color = { git = "https://github.com/bjz/color-rs" }
geometry = { path = "crates/geometry" }

相信聪明的读者已经能看懂该如何引入外部依赖库,这里就不再赘述。详细的说明参见此章:Cargo 依赖管理,但是不建议大家现在去看,只要按照目录浏览,拨云见日指日可待。

基于 cargo 的项目组织结构

前文有提到 cargo 默认生成的项目结构,真实的项目肯定会有所不同,但是在目前的学习阶段,还无需关注。感兴趣的同学可以移步:Cargo 项目结构

至此,大家对 Rust 项目的创建和管理已经有了初步的了解,那么来完善刚才的"世界,你好"项目吧。