Rust 入门实战系列(1)- Hello World

3,253 阅读8分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第22天,点击查看活动详情

专栏开篇

Rust 这门语言有很多优势,但最令我好奇的地方在于,这还是第一次见到一个语言,从前端展示,到后端业务,到系统编程都能做。这一点太了不起了。

我们能想象用 JS 来写 Linux Kernel,或者用 C++ 写前端么?简直天方夜谭,但是这两个领域,Rust 可以。

第二个吸引我的点在于内存管理。Java,Python,Go 这类语言提供了 GC,工程师只管分配内存,不用管删除,提高了开发效率。而 Rust 则没有 GC,且不用手工管理内存,不会有野指针,并发安全,它通过【共享不可变,可变不共享】的理念,让运行时风险降低,现代高级语言能做到这一点很厉害。

所以,今天开一个新坑,这个系列希望以代码实战的方式来学习 Rust,可能解读分析不会占比非常大,笔者本人也是初学,希望和大家一起成长。如果有理解的不对的,欢迎一起交流。

作为这个系列第一篇,希望首先收集所有官方,权威的专栏,课程,资料。并通过 demo 来简单上手。

Rust 是什么

Rust is a multi-paradigm, general-purpose programming language. Rust emphasizes performance, type safety, and concurrency. Rust enforces memory safety—that is, that all references point to valid memory—without requiring the use of a garbage collector or reference counting present in other memory-safe languages.

简单说就是五个点:通用语言,高性能,类型安全,并发,无GC。

毕竟笔者主语言还是 Go,直观来看跟 Rust 相比有两个劣势:

  • 通常来说,Golang 的性能一般来说是足够的,无论是gc开销还是goroutine调度。但是如果你想追求极致性能,还是建议考虑 Rust,毕竟没有GC而且可以自定义并发运行时与 allocator。

  • 从代码风格上来看,golangci-lint 只会告诉你你不应该怎么样,而 cargo clippy 不仅可以告诉你不应该如何,还会告诉你你完整的上下文(一个lint可以同时标记多处代码)。

当然,Rust 陡峭的学习曲线跟 Go 还是有差距的,毕竟 Golang 整体非常简洁,上手成本很低,认真学的话一周以内就可以完全上手,但 Rust 还是需要理解很多概念,尤其是【所有权】。

但既然这么牛逼,这个成本还是非常值得的。

学习资料(持续更新)

专栏开篇还是希望把市面上已知的 reference,教程汇总一下,以后有需要了随时过来找。如果有遗漏的欢迎大家指出。

工具链

Rust 作为现代化语言不仅有良好的语言设计,还提供了简单方便的工具链。

rustup

a command line tool for managing Rust versions and associated tools.

rustup 主要负责【工具链安装管理】,它是 Rust 提供的一个用于管理 Rust 版本以及相关工具的 cli 工具,有了它,我们的安装过程会非常容易。安装 rustup 很简单,运行下面的命令就ok:

# Run the following in your terminal, then follow the onscreen instructions.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

使用以上命令安装后,便拥有了 Rust 语言开发环境,包含了 rustc, cargo 等必备的工具,看到以下输出就代表成功了:

Rust is installed now. Great!

而如果此后 Rust 发布了新版本,我们希望升级的话,同样可以基于 rustup 进行:

$ rustup update

如果你希望卸载 Rust 以及 rustup,运行下面的命令:

$ rustup self uninstall

cargo

Cargo is the Rust package manager. Cargo downloads your Rust package's dependencies, compiles your packages, makes distributable packages, and uploads them to crates.io, the Rust community’s package registry.

Cargo 负责 Rust 代码编译程序构建,你可以把它理解为 Rust 的包管理机制的承接工具。你的程序存在依赖,或需要发布,都需要依赖 cargo 的能力。

后面我们在用到的时候会详细介绍,目前只需要知道 cargo 提供了一些命令让我们可以方便的创建工程,安装依赖,编译代码等。

  • 创建工程: cargo new rust-project
  • 安装:cargo install xxxx
  • 加减包: cargo-edit
  • Watch: cargo-watch

Hello World

好了,利用 rustup 安装好环境以后,我们就可以来试验一下,各个语言入门的 Hello World 在 rust 怎么写。

为了承接这个专栏下的学习记录,我创建了一个新的 github 仓库 rust-learn,本次以及随后所有专栏里涉及的代码都会放到这个 repo 里面,感兴趣的同学可以直接看源码。

代码实战

这里我们新建一个 hello-world 文件夹,创建一个 main.rs 文件,输入以下内容

fn main() {
    println!("Hello, world!");
}

保存文件后用 rustc 来编译(类似 gcc/clang 的角色)

rustc main.rs

这一步会执行编译,稍等一下你会发现目录下多了个 main 的二进制可执行文件。这也是跟 Ruby,Javascript, Python 等动态语言最大的区别。有了一个可执行文件,你可以直接拿去执行,而不用要求环境必须包含某种语言的解释器。

执行 ./main 命令后会输出:Hello, world! 。达到预期。

这里也可以看到 rust 还是非常简洁的,不需要类似 golang 中的 import 就能执行。

拆解程序

这一节我们来看看基于上面的 Hello World 示例,我们能得到 Rust 项目的哪些特征。

fn main() {

}
  • 我们可以用 fn 来声明一个函数,跟 Golang 中的 function 一样;
  • main 函数和其他语言类似,都代表了在一个可执行项目中首先被执行的代码逻辑;
  • 缩进格式默认是 4 个空格,而不是 1 个 tab;
  • println! 这个很特别,加了 ! 代表调用了 Rust 的宏,如果是普通的函数,可以把 ! 去掉;
  • Rust 的每个语句都需要以分号 ; 结尾,依次来表示语句结束;
  • 【编译】和【运行】是独立的两步,Rust 程序需要先编译,生成二进制可执行文件,再运行。

cargo 改造

前面我们提到了,Rust 使用 cargo 来做包管理。事实上在正式的工程里,我们很少会用 rustc 来做每一次编译。cargo 封装了强大的能力来拉取依赖,构建编译产物。这一节我们来学习一下。

对于我们的 Hello World 示例代码,鉴于只是一个打印,不包含任何外部依赖,不用 cargo 似乎也还好,这里我们展示一下如何用 cargo 来做到同样的事情。

通常来说,用前面提到的 rustup 安装时已经能够自动安装 cargo,不确定的话可以用这个命令确认一下:

$ cargo --version

创建项目

$ cargo new hello_world_cargo

====================================
     Created binary (application) `hello_world_cargo` package

这里我们用 cargo new 命令来创建一个 hello_world_cargo 工程,此时项目根路径下会多了 hello_world_cargo 的目录,我们 cd 进去看看。

image.png

可以看到,cargo 为我们生成了一个 src 目录,下面是一个 main.rs 的源码。外层还有个 Cargo.toml。生成的文件内容如下:

  • Cargo.toml

这里的格式是 TOML (Tom’s Obvious, Minimal Language) , 这也是 cargo 统一的配置格式。

  • [package] 描述对包级别的配置,包的名称,版本,和使用的 Rust 版本;
  • [dependencies] 是对依赖的管理。在 Rust 中,代码包被称为 crates,在目前的示例中我们没有依赖,这里暂时略过,后面文章会提到。
[package]
name = "hello_world_cargo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
  • main.rs

是不是很惊喜?这个代码跟前面我们自己写的 Hello World 不是一个,这是 Cargo 自动给我们生成的。区别在于此前的 main.rs 我们直接放到了工程根目录下。而用 Cargo 创建的 main.rs 是在 src 目录下。

这里也是体现 Rust 工程的要求,希望把所有代码收敛到 src 一个目录下面。外层只放 license,配置文件,README 即可。

fn main() {
    println!("Hello, world!");
}

编译运行

在我们的 hello_world_cargo 目录下执行 cargo build 就会触发编译。

$ cargo build

Finished dev [unoptimized + debuginfo] target(s) in 1.51s

此时我们的目录变成了这样:

image.png

编译的产物被放到了 target/debug 目录下。这里 target/debug/hello_world_cargo 就是我们的二进制可执行文件。运行之后依然符合预期输出结果:

$ ./target/debug/hello_world_cargo 

Hello, world!

由于我们是首次运行 cargo build,所以你会发现目录下多了一个 Cargo.lock 文件用来追踪各个依赖的准确版本:

# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "hello_world_cargo"
version = "0.1.0"

注意,开发者不要编辑这个文件,把 Cargo.lock 的维护全面交给 Cargo 就 ok。

在开发编译阶段,其实我们也可以直接 cargo run 来一次性完成【编译】+【运行】两个步骤,类似 Golang 中的 go run main.go,这样会更方便。

$ cargo run

    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/hello_world_cargo`
Hello, world!

如果我们不需要编译产物,只是想校验一下当前的代码能否编译,有一个比 cargo build 快的多的平替 cargo check,这样就可以只校验,不产出编译产物。保持定期运行 cargo check 检查代码是一个好习惯。

发布模式

这一点感觉跟 js 类似,Rust 的编译其实也分 dev 和 release 两种模式。我们上面的编译其实就是 dev 模式下的。而一旦代码完全ready,达到release状态,我们就可以用 cargo build --release 来触发正式编译,这种模式下会增加更多的优化。

$ cargo build --release

   Compiling hello_world_cargo v0.1.0 (/demo/src/github.com/ag9920/rust-learn/hello_world_cargo)
    Finished release [optimized] target(s) in 0.69s

运行这个命令耗时会比 dev 模式的编译要长一点,并且会在 target/release 目录下生成编译产物。

image.png

总结

今天是我们 Rust 入门实战系列的第一篇,我们了解了如何安装 Rust,怎样基于 Rust 语法写 Hello World,以及初步了解了 Cargo 包管理的用法。算是初步入了门,后面我们会逐步了解 Rust 的类型系统,所有权,各种实战用法。

对于 Rust,笔者也是初学,希望和大家一起进步,如果有不对的地方欢迎评论交流。

感谢阅读!