type
status
slug
date
summary
tags
category
password
icon
prerequisition:
download
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
以后需要更新rust只需要:
$ rustup update
而如果要卸载,只需要:
$ rustup update
You should know cargo
Cargo 是 Rust 的构建系统和包管理器。
Cargo 功能
Cargo 除了创建工程以外还具备构建(build)工程、运行(run)工程等一系列功能,构建和运行分别对应以下命令:
cargo的配置文件 Cargo.toml
The first line,
[package]
, is a section heading that indicates that the following statements are configuring a package. As we add more information to this file, we’ll add other sections
The last line, [dependencies]
, is the start of a section for you to list any of your project’s dependencies. In Rust, packages of code are referred to as crates. We won’t need any other crates for this project, but we will in the first project in Chapter 2, so we’ll use this dependencies section then.If you started a project that doesn’t use Cargo, as we did with the “Hello, world!” project, you can convert it to a project that does use Cargo. Move the project code into the src directory and create an appropriate Cargo.toml file.
Now we can build and run a cargo prj
Because the default build is a debug build, Cargo puts the binary in a directory named debug. You can run the executable with this command:
Let’s learn rust
关于Macro
在 Rust 中宏分为两大类:声明式宏( declarative macros )
macro_rules!
和三种过程宏( procedural macros ):#[derive]
,在之前多次见到的派生宏,可以为目标结构体或枚举派生指定的代码,例如Debug
特征
- 类属性宏(Attribute-like macro),用于为目标添加自定义的属性
- 类函数宏(Function-like macro),看上去就像是函数调用
而宏也是将一个值跟对应的模式进行匹配,且该模式会与特定的代码相关联。但是与
match
不同的是,宏里的值是一段 Rust 源代码(字面量),模式用于跟这段源代码的结构相比较,一旦匹配,传入宏的那段源代码将被模式关联的代码所替换,最终实现宏展开。值得注意的是,所有的这些都是在编译期发生,并没有运行期的性能损耗。Crates and Packages
A crate is the smallest amount of code that the Rust compiler considers at a time. Even if you run
rustc
rather than cargo
and pass a single source code file (as we did all the way back in the “Writing and Running a Rust Program” section of Chapter 1), the compiler considers that file to be a crate. Crates can contain modules, and the modules may be defined in other files that get compiled with the crate, as we’ll see in the coming sections.
A crate can come in one of two forms: a binary crate or a library crate. Binary crates are programs you can compile to an executable that you can run, such as a command-line program or a server. Each must have a function called main
that defines what happens when the executable runs. All the crates we’ve created so far have been binary crates.
Most of the time when Rustaceans say “crate”, they mean library crate, and they use “crate” interchangeably with the general programming concept of a “library".
The crate root is a source file that the Rust compiler starts from and makes up the root module of your crate
A package is a bundle of one or more crates that provides a set of functionality. A package contains a Cargo.toml file that describes how to build those crates. Cargo is actually a package that contains the binary crate for the command-line tool you’ve been using to build your code. The Cargo package also contains a library crate that the binary crate depends on. Other projects can depend on the Cargo library crate to use the same logic the Cargo command-line tool usesA package can contain as many binary crates as you like, but at most only one library crate. A package must contain at least one crate, whether that’s a library or binary crate.
Cargo follows a convention that src/main.rs is the crate root of a binary crate with the same name as the package.
Likewise, Cargo knows that if the package directory contains src/lib.rs, the package contains a library crate with the same name as the package, and src/lib.rs is its crate root.
Cargo passes the crate root files to
rustc
to build the library or binary.模块小抄
- 从 crate 根节点开始: 当编译一个 crate, 编译器首先在 crate 根文件(通常,对于一个库 crate 而言是src/lib.rs,对于一个二进制 crate 而言是src/main.rs)中寻找需要被编译的代码。
- 声明模块: 在 crate 根文件中,你可以声明一个新模块;比如,你用
mod garden;
声明了一个叫做garden
的模块。编译器会在下列路径中寻找模块代码: - 内联,在大括号中,当
mod garden
后方不是一个分号而是一个大括号 - 在文件 src/garden.rs
- 在文件 src/garden/mod.rs
- 声明子模块: 在除了 crate 根节点以外的其他文件中,你可以定义子模块。比如,你可能在src/garden.rs中定义了
mod vegetables;
。编译器会在以父模块命名的目录中寻找子模块代码: - 内联,在大括号中,当
mod vegetables
后方不是一个分号而是一个大括号 - 在文件 src/garden/vegetables.rs
- 在文件 src/garden/vegetables/mod.rs
- 模块中的代码路径: 一旦一个模块是你 crate 的一部分,你可以在隐私规则允许的前提下,从同一个 crate 内的任意地方,通过代码路径引用该模块的代码。举例而言,一个 garden vegetables 模块下的
Asparagus
类型可以在crate::garden::vegetables::Asparagus
被找到。
- 私有 vs 公用: 一个模块里的代码默认对其父模块私有。为了使一个模块公用,应当在声明时使用
pub mod
替代mod
。为了使一个公用模块内部的成员公用,应当在声明前使用pub
。
use
关键字: 在一个作用域内,use
关键字创建了一个成员的快捷方式,用来减少长路径的重复。在任何可以引用crate::garden::vegetables::Asparagus
的作用域,你可以通过use crate::garden::vegetables::Asparagus;
创建一个快捷方式,然后你就可以在作用域中只写Asparagus
来使用该类型。
目前为止我们介绍了 Rust 编译器所最常用的文件路径;不过一种更老的文件路径也仍然是支持的。
对于声明于 crate 根的
front_of_house
模块,编译器会在如下位置查找模块代码:- src/front_of_house.rs(我们所介绍的)
- src/front_of_house/mod.rs(老风格,不过仍然支持)
对于
front_of_house
的子模块 hosting
,编译器会在如下位置查找模块代码:- src/front_of_house/hosting.rs(我们所介绍的)
- src/front_of_house/hosting/mod.rs(老风格,不过仍然支持)
如果你对同一模块同时使用这两种路径风格,会得到一个编译错误。在同一项目中的不同模块混用不同的路径风格是允许的,不过这会使他人感到疑惑。
使用 mod.rs 这一文件名的风格的主要缺点是会导致项目中出现很多 mod.rs 文件,当你在编辑器中同时打开它们时会感到疑惑。
从不返回的 never type
Rust 有一个叫做
!
的特殊类型。在类型理论术语中,它被称为 empty type,因为它没有值。我们更倾向于称之为 never type。这个名字描述了它的作用:在函数从不返回的时候充当返回值。
不过一个不能创建值的类型有什么用呢?如果你回想一下示例 2-5 中的代码,曾经有一些看起来像这样的代码,如示例 19-26 所重现的:我们学习了
match
的分支必须返回相同的类型。如下代码不能工作:
这里的
guess
必须既是整型 也是 字符串,而 Rust 要求 guess
只能是一个类型。那么 continue
返回了什么呢?为什么示例 19-26 中会允许一个分支返回 u32
而另一个分支却以 continue
结束呢?正如你可能猜到的,
continue
的值是 !
。也就是说,当 Rust 要计算 guess
的类型时,它查看这两个分支。前者是 u32
值,而后者是 !
值。因为 !
并没有一个值,Rust 决定 guess
的类型是 u32
。
描述 !
的行为的正式方式是 never type 可以强转为任何其他类型。允许 match
的分支以 continue
结束是因为 continue
并不真正返回一个值;相反它把控制权交回上层循环,所以在 Err
的情况,事实上并未对 guess
赋值。
never type 的另一个用途是 panic!
。还记得 Option<T>
上的 unwrap
函数吗?它产生一个值或 panic。这里是它的定义:这里与示例 19-34 中的
match
发生了相同的情况:Rust 知道 val
是 T
类型,panic!
是 !
类型,所以整个 match
表达式的结果是 T
类型。这能工作是因为 panic!
并不产生一个值;它会终止程序。对于 None
的情况,unwrap
并不返回一个值,所以这些代码是有效的。Trait
trait 体中可以有多个方法:一行一个方法签名且都以分号结尾。
Rust 所有权
首先,让我们看一下所有权的规则。当我们通过举例说明时,请谨记这些规则:
Rust 中的每一个值都有一个 所有者(owner)。 值在任一时刻有且只有一个所有者。 当所有者(变量)离开作用域,这个值将被丢弃。
之前我们提到过当变量离开作用域后,Rust 自动调用
drop
函数并清理变量的堆内存。不过图 4-2 展示了两个数据指针指向了同一位置。这就有了一个问题:当 s2
和 s1
离开作用域,它们都会尝试释放相同的内存。这是一个叫做 二次释放(double free)的错误,也是之前提到过的内存安全性 bug 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。为了确保内存安全,在
let s2 = s1;
之后,Rust 认为 s1
不再有效,因此 Rust 不需要在 s1
离开作用域后清理任何东西。看看在 s2
被创建之后尝试使用 s1
会发生什么;这段代码不能运行:如果你在其他语言中听说过术语 浅拷贝(shallow copy)和 深拷贝(deep copy),那么拷贝指针、长度和容量而不拷贝数据可能听起来像浅拷贝。不过因为 Rust 同时使第一个变量无效了,这个操作被称为 移动(move),而不是叫做浅拷贝。上面的例子可以解读为
s1
被 移动 到了 s2
中。那么具体发生了什么,如图 4-4 所示。
这样就解决了我们的问题!因为只有
s2
是有效的,当其离开作用域,它就释放自己的内存,完毕。另外,这里还隐含了一个设计选择:Rust 永远也不会自动创建数据的 “深拷贝”。因此,任何 自动 的复制都可以被认为是对运行时性能影响较小的。
but we have clone
如果我们 确实 需要深度复制
String
中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone
的通用函数。第五章会讨论方法语法,不过因为方法在很多语言中是一个常见功能,所以之前你可能已经见过了。当出现
clone
调用时,你知道一些特定的代码被执行而且这些代码可能相当消耗资源。你很容易察觉到一些不寻常的事情正在发生。
原因是像整型这样的在编译时已知大小的类型被整个存储在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量 y
后使 x
无效。换句话说,这里没有深浅拷贝的区别,所以这里调用 clone
并不会与通常的浅拷贝有什么不同,我们可以不用管它。Rust 有一个叫做
Copy
trait 的特殊注解,可以用在类似整型这样的存储在栈上的类型上(第十章将会详细讲解 trait)。如果一个类型实现了 Copy
trait,那么一个旧的变量在将其赋值给其他变量后仍然可用。那么哪些类型实现了
Copy
trait 呢?你可以查看给定类型的文档来确认,不过作为一个通用的规则,任何一组简单标量值的组合都可以实现 Copy
,任何不需要分配内存或某种形式资源的类型都可以实现 Copy
。如下是一些 Copy
的类型:- 所有整数类型,比如
u32
。
- 布尔类型,
bool
,它的值是true
和false
。
- 所有浮点数类型,比如
f64
。
- 字符类型,
char
。
- 元组,当且仅当其包含的类型也都实现
Copy
的时候。比如,(i32, i32)
实现了Copy
,但(i32, String)
就没有。
对于 Rust 的
..
range 语法,如果想要从索引 0 开始,可以不写两个点号之前的值。换句话说,如下两个语句是相同的:const
First, you aren’t allowed to use
mut
with constants. Constants aren’t just immutable by default—they’re always immutable. You declare constants using the const
keyword instead of the let
keyword, and the type of the value must be annotated. String
很值得记忆
天坑++
Better ergonomics for pattern-matching on references.
Currently, matching on references requires a bit of a dance using
ref
and &
patterns:After this RFC, the above form still works, but now we also allow a simpler form:
哥们觉得原来的方式最好理解,淦!!!,这个新功能整死我了。
多亏stack overflow的帖子才找到答案:
self和Self
关于reference的部分
这里这一节的内容很重要:
何为智能指针?能不让你写出
****s
形式的解引用,我认为就是智能: ),智能指针的名称来源,主要就在于它实现了 Deref
和 Drop
特征,这两个特征可以智能地帮助我们节省使用上的负担:Deref
可以让智能指针像引用那样工作,这样你就可以写出同时支持智能指针和引用的代码,例如T
Drop
允许你指定智能指针超出作用域后自动执行的代码,例如做一些数据清除等收尾工作
先来看看
Deref
特征是如何工作的。智能指针解引用
上面所说的解引用方式和其它大多数语言并无区别,但是 Rust 中将解引用提升到了一个新高度。考虑一下智能指针,它是一个结构体类型,如果你直接对它进行
*myStruct
,显然编译器不知道该如何办,因此我们可以为智能指针结构体实现 Deref
特征。实现
Deref
后的智能指针结构体,就可以像普通引用一样,通过 *
进行解引用生命周期 lifetimes
函数或方法的参数的生命周期被称为 输入生命周期(input lifetimes),而返回值的生命周期被称为 输出生命周期(output lifetimes)。
编译器采用三条规则来判断引用何时不需要明确的注解。第一条规则适用于输入生命周期,后两条规则适用于输出生命周期。如果编译器检查完这三条规则后仍然存在没有计算出生命周期的引用,编译器将会停止并生成错误。这些规则适用于
fn
定义,以及 impl
块。第一条规则是编译器为每一个引用参数都分配一个生命周期参数。换句话说就是,函数有一个引用参数的就有一个生命周期参数:
fn foo<'a>(x: &'a i32)
,有两个引用参数的函数就有两个不同的生命周期参数,fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
,依此类推。第二条规则是如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数:
fn foo<'a>(x: &'a i32) -> &'a i32
。第三条规则是如果方法有多个输入生命周期参数并且其中一个参数是
&self
或 &mut self
,说明是个对象的方法 (method)(译者注:这里涉及 rust 的面向对象参见 17 章),那么所有输出生命周期参数被赋予 self
的生命周期。第三条规则使得方法更容易读写,因为只需更少的符号。- 作者:liamY
- 链接:https://liamy.clovy.top/article/OS_Tutorial/rust_learn
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。