rust学习记录
记录一下Rust的学习历程
第一章:安装和hello world
安装
1 |
|
这条命令会下载并执行一个脚本来安装rustup工具,进而安装最新的Rust稳定版本。该脚本可能会在执行过程中请求输入你的密码。一旦安装成功,你将能够看到如下所示的输出
1 |
|
上面的安装过程会自动将Rust工具链添加到环境变量PATH中,并在下一次登录终端时生效。假如你想要立即开始使用Rust而不用重新启动终端,那么你可以在终端中运行如下所示的命令来让配置立即生效:
1 |
|
或者,你也可以向~/.bash_profile 文件中添加下面的语句,手动将Rust添加到环境变量PATH中:
1 |
|
Hello , world
创建一个文件夹
先,我们需要创建一个文件夹来存储编写的Rust代码。通常而言,Rust不会限制我们存储代码的位置,但是针对本书中的各种练习和项目,我们建议你创建一个可以集合所有项目的根文件夹,然后将本书中所有的项目放在里面。
现在,你可以打开终端并输入相应命令,来创建我们的文件夹及第一个“Hello, world!”项目了。
对于Linux系统、macOS系统,以及Windows系统的PowerShell终端来说,输入的命令如下所示:
1 |
|
现在,你可以打开刚刚创建的main.rs 文件,并键入示例1-1中的代码。
main.rs
1 |
|
编译与运行是两个不同的步骤
你应该已经运行过刚刚编写的程序了,让我们来详细地讨论一下这个过程中的每一个步骤。
在运行一段Rust程序之前,你必须输入rustc命令及附带的源文件名参数来编译它:
1 |
|
编译完之后我们ls看一下编译出来了什么东西
1 |
|
假如你更加熟悉某种类似于Ruby、Python或JavaScript之类的动态语言,你可能还不太习惯在运行之前需要先进行编译。Rust是一种预编译语言,这意味着当你编译完Rust程序之后,便可以将可执行文件交付于其他人,并运行在没有安装Rust的环境中。而如果你交付给其他人的是一份.rb 、.py 或.js 文件,那么他们就必须要拥有对应的Ruby、Python或JavaScript实现来执行程序。当然,这些语言只需要用简单的一句命令就可以完成程序的编译和运行。这也算是语言设计上的权衡与取舍吧
hello cargo
是啥:
是Rust的包管理器,它可以处理众多诸如构建代码、下载编译依赖库等琐碎但重要的任务
1 |
|
使用Cargo创建一个项目
1 |
|
现在,让我们进入hello_cargo 文件夹,你可以看到Cargo刚刚生成的两个文件与一个目录:一个名为Cargo.toml 的文件,以及一个名为main.rs 的源代码文件,该源代码文件被放置在src 目录下。与此同时,Cargo还会初始化一个新的Git仓库并生成默认的.gitignore 文件。
Cargo.toml 中的内容如示例1-2所示,你可以使用文本编辑器打开它。
toml就是配置文件格式
Cargo.toml
1 |
|
同样按照惯例,Cargo会默认把所有的源代码文件保存到src 目录下,而项目根目录只被用来存放诸如README文档、许可声明、配置文件等与源代码无关的文件。使用Cargo可以帮助你合理并一致地组织自己的项目文件,从而使一切井井有条。
使用Cargo构建和运行项目
那么使用Cargo来构建和运行项目与手动使用rustc相比又有哪些异同呢?在当前的hello_cargo 项目目录下,Cargo可以通过下面的命令来完成构建任务:
1 |
|
与之前不同,这个命令会将可执行程序生成在路径target/debug/hello_ cargo (或者Windows系统下的target\debug\hello_cargo.exe )下。你可以通过如下所示的命令运行这个可执行程序试试看
1 |
|
一切正常的话,Hello, world! 应该能够被打印到终端上。首次使用命令cargo build构建的时候,它还会在项目根目录下创建一个名为Cargo.lock 的新文件,这个文件记录了当前项目所有依赖库的具体版本号。由于当前的项目不存在任何依赖,所以这个文件中还没有太多东西。你最好不要手动编辑其中的内容,Cargo可以帮助你自动维护它。
当然,也可以简单的使用cargo run 命令来一次完成编译和运行任务
1 |
|
这里没有看到编译hello_cargo的相关信息,因为cargo发现源码并没有被修改,所以就直接运行了所生成的二进制,我们现在修改一下源代码再试试run
1 |
|
还有一个命令,cargo check ,这个命令可以快速检查当前的代码是否可以通过编译,而不需要花费EAI的时间去真正生成可执行程序
1 |
|
总结:
cargo new project_name 创建一个Cargo项目目录
1 |
|
通过cargo build 和cargo check 来构建一个项目
通过cargo run 来构建并且运行这个项目,
构建所产生的结果会被Cargo 存储在target/debug目录下面,而非代码所处在的位置,
Cargo另一个优势是不区分操作系统(Linux\MacOS\Windows),在任何操作系统下都是一样的命令
以Release模式进行构建
当准备好发布自己的项目时,你可以使用命令cargo build –release在优化模式下构建并生成可执行程序。它生成的可执行文件会被放置在target/release 目录下,而不是之前的target/debug 目录下。这种模式会以更长的编译时间为代价来优化代码,从而使代码拥有更好的运行时性能。
第二章 编写一个猜数游戏
第三章 通用编程概念
src/main.rs
1 |
|
变量与可变性:
Rust中的变量默认是不可变的。Rust语言提供这一概念是为了能够让你安全且方便地写出复杂、甚至是并行的代码。
代码:
1 |
|
如果需要定义可变变量,就必须let mut x = 5;
除了避免出现bug,设计一个变量的可变性还需要考量许多因素。例如当你在使用某些重型数据结构时,适当地使用可变性去修改一个实例,可能比赋值和重新返回一个新分配的实例要更有效率;而当数据结构较为轻量的时候,采用更偏向函数式的风格,通过创建新变量来进行赋值,可能会使代码更加易于理解。在类似这样的情形下,为了可读性而损失少许的性能也许是值得的。
常量
与不可变变量类似,常量(constant)是绑定到一个常量名且不允许更改的值,但是常量和变量之间是有差异的
1 |
|
遮蔽
你可以声明和前面变量具有相同名称的新变量。这个是第一个变量被第二个变量遮蔽(shadow)
遮蔽和将变量标记为 mut
的方式不同,因为除非我们再次使用 let
关键字,否则若是我们不小心尝试重新赋值给这个变量,我们将得到一个编译错误。通过使用 let
,我们可以对一个值进行一些转换,但在这些转换完成后,变量将是不可变的。
mut
和遮蔽之间的另一个区别是,因为我们在再次使用 let
关键字时有效地创建了一个新的变量,所以我们可以改变值的类型,但重复使用相同的名称。例如,假设我们程序要求用户输入空格字符来显示他们想要的空格数目,但我们实际上想要将该输入存储为一个数字:
变量
Rust是一种静态类型(statically typed)的语言,意味着它必须在编译期间就要知道所有变量的类型,、
整型
表 3-1: Rust 中的整型
长度 | 有符号类型 | 无符号类型 |
---|---|---|
8 位 | i8 |
u8 |
16 位 | i16 |
u16 |
32 位 | i32 |
u32 |
64 位 | i64 |
u64 |
128 位 | i128 |
u128 |
arch | isize |
usize |
有符号和无符号表示数字能否取负数
每个有符号类型规定的数字范围是 -(2^(n - 1) ~ 2^(n - 1) - 1,其中 n
是该定义形式的位长度。所以 i8
可存储数字范围是 -(2^7) ~ 2^7 - 1,即 -128 ~ 127。
无符号类型可以存储的数字范围是 0 ~( 2^n )- 1,所以 u8
能够存储的数字为 0 ~ 2^(8 - 1),即 0 ~ 255。
此外,isize
和 usize
类型取决于程序运行的计算机体系结构,在表中表示为“arch”:若使用 64 位架构系统则为 64 位,若使用 32 位架构系统则为 32 位。
注意,可能属于多种数字类型的数字字面量允许使用类型后缀来指定类型,例如 57u8
。数字字面量还可以使用 _
作为可视分隔符以方便读数,如 1_000
,此值和 1000
相同。
表 3-2: Rust 的整型字面量
数字字面量 | 示例 |
---|---|
十进制 | 98_222 |
十六进制 | 0xff |
八进制 | 0o77 |
二进制 | 0b1111_0000 |
字节 (仅限于 u8 ) |
b'A' |
那么该使用哪种类型的整型呢?如果不确定,Rust 的默认形式通常是个不错的选择,整型默认是 i32
。isize
和 usize
的主要应用场景是用作某些集合的索引。
i32 就是 -2^31~2^31-1
整型溢出
比方说有一个u8
,存放区间是什么?是0(2^8)-1=0255
如果当你将其修改为范围之外的值,比如说256,则会发生整型溢出(integer overflow),这会导致两种行为的其中一种:
1、当在调试(debug)模式编译时,Rust 会检查整型溢出,若存在这些问题则使程序在编译时 panic。Rust 使用 panic 这个术语来表明程序因错误而退出。第 9 章 [“panic!
与不可恢复的错误”](file:///opt/Koodo Reader/resources/app.asar/build/ch09-01-unrecoverable-errors-with-panic.xhtml)会详细介绍 panic。
2、在当使用 --release
参数进行发布(release)模式构建时,Rust 不检测会导致 panic 的整型溢出。相反当检测到整型溢出时,Rust 会进行一种被称为二进制补码包裹(two’s complement wrapping)的操作。简而言之,大于该类型最大值的数值会被“包裹”成该类型能够支持的对应数字的最小值。比如在 u8
的情况下,256 变成 0,257 变成 1,依此类推。程序不会 panic,但是该变量的值可能不是你期望的值。依赖整型溢出包裹的行为不是一种正确的做法。
浮点型
浮点数是带有小数点的数字,Rust中有两种:
f32
类型是单精度浮点型
f64
为双精度浮点型。
1 |
|
数字运算
Rust 的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取模运算。整数除法会向下取整。
1 |
|
布尔类型
和大多数编程语言一样,Rust 中的布尔类型也有两个可能的值:true
和 false
。布尔值的大小为 1 个字节。Rust 中的布尔类型使用 bool
声明。
1 |
|
字符类型
Rust 的 char
(字符)类型是该语言最基本的字母类型,
1 |
|
注意,我们声明的 char
字面量采用单引号括起来,这与字符串字面量不同,字符串字面量是用双引号括起来。
Rust 的字符类型大小为 4 个字节,表示的是一个 Unicode 标量值,这意味着它可以表示的远远不止是 ASCII。
标音字母,中文/日文/韩文的文字,emoji,还有零宽空格(zero width space)在 Rust 中都是合法的字符类型。
Unicode 值的范围为 U+0000
~ U+D7FF
和 U+E000
~`U+10FFFF`。
不过“字符”并不是 Unicode 中的一个概念,所以人在直觉上对“字符”的理解和 Rust 的字符概念并不一致。
复合类型
复合类型(compound type)可以将多个值组合成一个类型。Rust 有两种基本的复合类型:元组(tuple)和数组(array)
元组类型
元组是将多种类型的多个值组合到一个复合类型中的一种基本方式。元组的长度是固定的:声明后,它们就无法增长或缩小。
我们通过在小括号内写入以逗号分隔的值列表来创建一个元组。
元组中的每个位置都有一个类型,并且元组中不同值的类型不要求是相同的。
1 |
|
因为元组被认作是单个复合元素。 想从元组中获取个别值,我们可以使用模式匹配来解构(destructure)元组的一个值,如下所示:
1 |
|
将这个元组分解成三个单独的变量x、y、z,这个过程称为解构
除了通过模式匹配进行结构外,还可以通过使用一个句点(.)连上要访问的值的索引来直接访问元组元素(类似Python下标索引取值),代码示例:
1 |
|
没有任何值的元组()
是一种特俗的类型,只有一个值,也可以写成()
。该类型被称为单元类型(unit type),该值也被称为单元值(unit value)。如果表达式不返回任何其他值,就隐式地返回单元值。
数组类型
数组:将多个值组合在一起的另一种方式就是数组(array)。与元组不同,数组内的每个元素必须是同样类型的元素,
长度:Rust语言的数组是固定长度的。
表达形式:方括号内逗号分隔
1 |
|
特点:
当你希望将数据分配到栈(stack)而不是堆(heap)时,或者希望确保始终具有固定数量的元素时,数组就特别有用。
但它不像vector(中文翻译为“向量”,Rust中的意义为“动态数据、可变数组”)类型那么灵活。
vector 类型类似于标准库中提供的集合类型,其大小允许增长或缩小。
如果不确定是使用数组还是 vector,那就应该使用一个 vector。
不过当你明确元素数量不需要改变时,数组会更有用。例如,如果你在程序中使用月份的名称,你很可能希望使用的是数组而不是 vector,因为你知道它始终包含 12 个元素:
1 |
|
使用方括号编写数组的类型,其中包含每个元素的类型、分号,然后是数组中的元素数,如下所示:
1 |
|
以这种方式编写数组的类型看起来类似于初始化数组的另一种语法:如果要为每个元素创建包含相同值的数组,可以指定初始值,后跟分号,然后在方括号中指定数组的长度,
1 |
|
访问数组内元素
数组是可以在栈上分配的已知固定大小的单个内存块。可以使用索引访问数组的元素,
1 |
|