Rust 是一门具有高性能、可靠性和生产力的程序设计语言,适用于各种领域的应用开发。作为一门快速、跨平台、低资源占用的语言,Rust 被各个公司应用于嵌入式、Web 应用、云组件等开发,近几年比较火,但也因为其学习成本较高被认为具备挑战性。
本人此前也未曾学习过 Rust,借写这篇文章的机会系统性学习下 Rust。阅读本文需要有其他语言基础,且内容可能相对简洁,如果期望了解更多细节可以阅读 Rust 官方推荐的《Rust 程序设计语言》。
Rust 安装#
第一步是安装 Rust。
Rust 官方提供了一个叫 rustup 的工具用于管理 Rust 编译工具链,我们需要安装 rustup。
如果是 Windows 可以前往 Rust 官网 下载安装包进行安装,如果是 macOS、Linux 或者 WSL,可以执行以下命令安装。
|
|
安装完成后执行命令查看 Rust 是否安装成功。
|
|
如果我们需要更新 Rust 也是通过 rustup 执行更新。
|
|
Hello world#
第一个 Rust 程序#
Rust 的源代码后缀为 rs,首先我们创建一个名为 main.rs 的文件,编写我们的第一个 Rust 程序。
|
|
然后执行编译器进行编译并执行。
|
|
与其他程序设计语言相似,在 Rust 中使用 fn 声明函数,而 main 是程序的主函数。在 Rust 中存在类似 C/C++ 的宏概念,但是 Rust 的宏更加强大。Rust 中的宏均带有 !
后缀,这里的 println! 就是一个宏,该宏的功能是输入信息到控制台并换行。
值得一提,Rust 的编译器报错能力相比其他语言要丰富得多,如果你把感叹号去掉,编译器会报错找不到 println 函数,且提示是否想使用 println! 宏。
第一个 Rust 项目#
和其他语言一样,Rust 也具备一个用于项目依赖管理的工具,名为 Cargo。Cargo 会在 rustup 安装的同时同步安装,我们可以通过 cargo –version 查看当前版本。
首先我们可以通过 Cargo 创建一个新项目。
|
|
该命令会创建一个项目文件夹,其中包含一个 rs 源文件和名为 Cargo.toml 的项目元信息文件。Cargo.toml 记录了项目的基本信息,以及该项目所依赖的第三方库和对应的版本。
|
|
除此之外,如果目录不在 Git 仓库内,cargo new 命令会自动将项目文件夹初始化为 Git 仓库,并写入对应的 .gitignore 文件。
接下来我们通过 Cargo 运行项目,该方法不需要手动编译再运行。
|
|
Cargo 本身还支持了构建、快速检测等功能,这里就不展开了。关于 Cargo 的依赖管理,我们在本文后面的章节将介绍。
基本概念#
变量和常量#
Rust 通过 let 定义变量,通过 const 定义常量。变量可以自动推导类型,而常量必须显式指定类型。并且,Rust 的常量必须由常量表达式赋值,不能是一个运行时计算的值。
|
|
但是变量默认是不可变的,而常量是总是不可变。通过 mut 关键词可将一个变量声明为可变的。
|
|
对于变量,可以通过 :<type>
显式指定其类型,常见的数据类型如下。
数据类型 | 关键词 |
---|---|
整型 | i8、u8、i16、u16、i32、u32、i64、u64、i128、u128、isize、usize |
浮点型 | f32、f64 |
布尔型 | bool |
字符类型 | char |
复合类型 | (i32, i64, bool) 元组、[i32] 数组 |
函数和表达式#
Rust 通过 fn 关键词定义函数,函数的参数和返回值定义采用了类似 TypeScript 的声明形式。
|
|
函数调用本身是一个表达式,最后的返回语句也可以简写成表达式形式,需要注意表达式是不需要带分号的。
|
|
在 Rust 中用大括号创建的新的块作用域其实也是一个表达式,最后也可以通过表达式形式返回块作用域的值。
|
|
控制流#
Rust 通过 if else 关键字实现不同分支的执行。
|
|
Rust 有三种循环:loop、while 和 for。
|
|
Rust 通过 match 关键词实现类似其他语言的 switch 和 case 语句。
|
|
所有权#
Rust 是一门具有所有权的语言,每个值都有一个所有者。当所有者离开作用域时,该值将被丢弃。
|
|
String 类型是 Rust 提供的被分配到堆上的可变字符串类型,通过 String::from 可以基于字符串字面值创建变量。上面的代码中,第一行我们创建了一个 String 字符串,它的所有者为 s1;而第二个我们通过赋值语句将 s1 赋值给 s2,此时 String 字符串的所有者将被转移到 s2,而转移之后如果再使用 s1 则编译失败。
为了便于在其他地方使用该变量,Rust 的所有权机制提供了引用和借用的概念。
引用类似 C++ 等语言的引用,通过 &
符号获得该变量的引用值,而不是所有权。而创建引用的过程,在 Rust 中被称为所有权借用。
|
|
如同变量默认是不可变的,引用默认也是不可变的,我们无法通过引用去修改该变量。如果需要修改,可以通过 mut 关键词将引用修饰为可变引用。但是创建了可变引用之后,就无法再对该变量创建新的引用了。
|
|
数据结构#
结构体#
结构体是一种类似元组的复合数据类型,其成员可以是不同类型。相比于元组,结构体的成员可以通过名字访问。
|
|
我们也可以定义没有成员名字的结构体,这种结构体被称为元组结构体。
|
|
Rust 还允许定义没有任何字段的结构体,这种结构体被称为类单元结构体,类似于 () 元组。这类结构体常常用于在某些类型中实现 trait 但不需要在类型中存储数据的时候。
|
|
结构体的方法与函数类似,也是使用 fn 关键词和名称声明,具备参数和返回值。但是方法是在结构体的上下文中通过 impl 关键词被定义的,且结构体的方法的第一个参数始终是 self,代表调用方法的结构体实例。
|
|
枚举#
Rust 通过 enum 关键词定义枚举值。
|
|
与其他语言不一样,Rust 的枚举类型的成员可以是不同类型,可以通过同一个枚举类型来接收不同的值。
|
|
因为这个特性,Rust 可以通过枚举来实现 Option 和 Result 等特殊枚举类型,解决空值、错误值等问题。Rust 没有 Null 值的概念,而是使用 Option 枚举来表示可能存在也可能不存在的值。Option 枚举是一个被定义在标准库的枚举类型,其定义如下。
|
|
当我们需要判断一个 Option 枚举值是否存在时,使用 match 语句来进行模式匹配即可。
泛型#
Rust 允许通过泛型来定义函数、结构体、枚举等类型,从而实现代码的复用。
|
|
与 Golang 不一样的,Rust 允许在方法上定义泛型。
|
|
trait 接口#
Rust 通过 trait 关键词定义接口,接口是一组方法的集合,用于定义一些类型共享的行为。
|
|
trait 可以定义默认的行为,并且作为函数的参数类型,用来约束参数的行为。
|
|
trait 同样可以结合泛型一起使用。
包管理#
包和模块#
前面我们使用了 Cargo 来创建 Rust 项目,Cargo 是 Rust 的包管理工具,提供了比较丰富的管理功能。现在我们首先简单了解下 Rust 中的一些包管理概念。
- package:项目,即一个 Cargo 工程,通过 Cargo.toml 进行管理;
- crate:包,独立的可编译单元,一个项目中包含一个或多个 crate;
- module:模块,对 crate 内的代码进行分组,形成层次结构;
对于项目而言,Cargo 工程下有一个与项目同名的 crate,其中 src/main.rs 是二进制项目的同名 crate 的入口文件,src/lib.rs 是库项目的同名 crate 的入口文件。
通过 mod 关键词可以在一个 crate 内创建模块,达到代码的组织的效果。
|
|
当我们想将上述代码中的模块实现在一个单独的源码文件中时,可以通过 mod 关键词声明引用,Rust 会在所在文件夹下找到对应的模块。
|
|
也可以创建一个文件夹实现更多源码文件,但是需要在文件夹下创建一个名为 mod.rs 的文件,或者在文件夹所在目录下创建一个同名的文件。
|
|
标准库和第三方库#
Rust 标准库是 Rust 自带的一组库,包含了很多常用的功能。Rust 通过 use 关键词引用标准库或者第三方库。
|
|
引用第三方库前需要先在 Cargo.toml 中添加依赖,可以通过 Cargo 提供的命令进行添加。
|
|
然后就可以通过 use 引用该库对应的内容了。
|
|
结语#
不得不说,Rust 的学习成本是真的高,虽然大致了解了 Rust 的一些语言基础的知识,但是感觉还是有很多东西需要深入学习的,比如 trait、生命周期、智能指针、所有权等,道阻且长。