diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/00_preface/01_introduction/index.html b/00_preface/01_introduction/index.html new file mode 100644 index 0000000..ddf17ee --- /dev/null +++ b/00_preface/01_introduction/index.html @@ -0,0 +1,3080 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.1   Rust简介 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

Rust简介

+

1 为什么要使用Rust?

+

Rust是一种令人兴奋的新兴编程语言,它可以让每个人编写可靠且高效的软件。Rust可以用来替换C/C++,Rust和他们具有相同的性能,但是很多常见的bug在编译时就可以被消灭。Rust是一种通用的编程语言,但是它更善于以下场景:

+
    +
  • 需要运行时的速度
  • +
  • 需要内存安全
  • +
  • 更好地利用多核处理器
  • +
+

2 Rust与其他语言的比较

+

C/C++性能非常好,但类型系统和内存都不太安全。Java/C#拥有GC(内存回收器),能保证内存安全,也有很多优秀特性,但是性能相对C/C++不足。Rust则具有这两类语言的共同优点:

+
    +
  • 安全
  • +
  • 无需GC
  • +
  • 易于维护、调试,代码安全高效
  • +
+

3 Rust擅长的领域

+
    +
  • 高性能的 Web Service
  • +
  • WebAssembly
  • +
  • 命令行工具
  • +
  • 嵌入式设备
  • +
  • 系统编程
  • +
+

4 Rust与Firefox

+

Rust最初是 Mozilla 公司一个研究性项目。Firefox 是 Rust 产品应用的一个重要的例子。Mozzilla一直以来都在使用 Rust 创建一个名为Servo 的实验性浏览器引擎,其中所有内容都是并行执行的。目前 Servo 的部分功能已经被集成到 Firefox 里面了。Firefox原来的量子版就包含了 Servo 的CSS渲染引擎,Rust使得 Firefox 在这方面得到了巨大的性能改进。

+

5 Rust的用户案例

+
    +
  • +

    Google: 新操作系统 Fuschia ,其中 Rust 代码量大约占30%

    +
  • +
  • +

    Amazon: 基于Linux开发了可以在直接裸机、虚拟机上运行容器的操作系统

    +
  • +
  • +

    System76: 使用纯Rust开发了下一代安全操作系统 Redox

    +
  • +
  • +

    蚂蚁金服: 库操作系统 Occlum

    +
  • +
  • +

    斯坦福和密歇根大学: 使用 Rust 开发嵌入式实时操作系统,应用于 Google 的加密产品

    +
  • +
  • +

    微软: 正在使用 Rust 重写 Windows 中的一些低级的系统组件

    +
  • +
  • +

    微软: WinRT/Rust 项目

    +
  • +
  • +

    Drobox、Yelp、Coursera、LINE、Cloudflare、Alassian、npm、Ceph、百度、华为、Sentry、Deno...

    +
  • +
+

6 Rust的优点

+
    +
  • +

    性能高

    +
  • +
  • +

    安全性高

    +
  • +
  • +

    无所畏惧的并发

    +
  • +
+

8 总结

+

Rust是一门面向未来的编程语言,我相信学习Rust可以让自己未来发展之路越走越远。但也要注意的是,Rust 有很多独有的概念,它们和现在大多数主流语言都不同,所以学习 Rust 的时候必须从基础概念一步一步地学。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/01_primer/01_installation/index.html b/01_primer/01_installation/index.html new file mode 100644 index 0000000..cba2591 --- /dev/null +++ b/01_primer/01_installation/index.html @@ -0,0 +1,3044 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.1   安装 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

安装

+

Rust 的官方网址是: https://www.rust-lang.org/

+

1. 安装 Rust

+

1.1. 脚本安装方式

+

使用电脑打开到 Rust 的官方网站之后,Rust 会判断我们当前的操作系统并引导我们进行安装。

+

Windows 的 Linux子系统、Linux、MacOS 只需执行以下命令,即可根据提示进行安装

+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+
+
+

推荐使用脚本的方式进行安装,安装脚本会将 Rust 相关组件安装自己的用户目录上,并且设置好相关的环境变量

+
+

1.1. 二进制安装包安装

+

Rust 也支持使用二进制文件包的方式的安装方式。

+

打开其他安装方式的页面:https://forge.rust-lang.org/infra/other-installation-methods.html,我们找到二进制安装包下载链接的页面位置,如下图所示

+

01-01.png

+
+

在编写此文档的时候,Rust的最新稳定版本是1.60.0,你在打开的时候,页面可能有所变化。

+
+

找到与自己操作系统匹配的安装包,点击下载进行安装即可。

+

1.2. 包管理工具安装

+

包管理工具可以快速安装Rust,如mac电脑使用homebrew安装,如下命令

+
brew install rust
+
+

1.3. 安装验证

+

安装完成后,使用以下命令查看版本号,如果正常显示rust版本信息代表安装成功,否则重启终端或者检查其他原因

+
rustc --version
+
+

使用脚本方式安装的 Rust 会包含一个 rustup 工具,我们还可以使用 rustupdoc 指令通过浏览器打开本地文档,如下

+
rustup doc
+
+

2. IDE开发

+

如果使用 VsCode 或者 Intellij Idea 进行 Rust 代码开发,还可以在插件市场搜索 Rust,安装上 Rust 的官方插件,可以更好地在 IDE 中使用 Rust 的相关功能。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/01_primer/02_hello_world/index.html b/01_primer/02_hello_world/index.html new file mode 100644 index 0000000..0d9d41f --- /dev/null +++ b/01_primer/02_hello_world/index.html @@ -0,0 +1,3050 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.2   运行Hello World - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

运行Hello World

+

1. 文件命名规范

+

我们通常使用一个 英文名称的 空目录作为 Rust 项目的目录,并遵循以下规范:

+
    +
  • 程序文件名后缀为: rs
  • +
  • 文件命名应当使用小写,如果名称包含多个单词,每个单词之间使用下划线隔开,如: hello_world.rs
  • +
+

2. 编写第一个Rust程序

+

2.1. 创建代码

+

创建一个名为hello_world的目录,在目录下创建main.rs文件,写入以下内容

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

以上内容就是 Rust 程序最简单的代码:

+
    +
  • +

    定义函数: fn main(){} 代表一个没有参数也没有返回值的名为main的函数

    +
  • +
  • +

    main函数: 在 Rust 中, mian 函数是 Rust 可执行程序的入口

    +
  • +
  • +

    打印文本: println!("Hello, world!"),Rust代码的缩进是4个空格,而不是tab,println!是 Rust macro(宏),如果是函数,就没有!,Rust代码以;结尾

    +
  • +
+

以上代码的功能是: 在mian 函数中调用 println!宏 在控制台打印了一条字符串:"Hello, world!"

+

2.2. 编译代码

+

使用以下命令进行编译

+
rustc main.rs
+
+

如果是在windows编译,生成的可执行文件名称为main.exe,使用以下命令执行

+
.\main.exe
+
+

如果是在mac/Linux中编译,生成的可执行文件名称为main,使用以下命令执行

+
./main
+
+

3. 总结

+

Rust 代码先编译成二进制可执行程序才能运行,编译命令为 rustc 源代码文件名 ,如

+
rustc main.rs
+
+
    +
  • +

    编译成功后,会生成一个二进制可执行文件,在 Windows 上还会多出一个后缀为 .pdb 的文件,里面包含调试信息

    +
  • +
  • +

    Rust 是 ahead-of-time 编译的语言,可以先编译程序,然后把可执行文件交给别人运行,无需安装 Rust

    +
  • +
  • +

    rustc 只适合简单的 Rust 程序,如果源代码比较多,使用 rustc 并不合适,在后续将会介绍 Cargo,用于解决这个问题

    +
  • +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/01_primer/03_cargo/index.html b/01_primer/03_cargo/index.html new file mode 100644 index 0000000..fcfed7e --- /dev/null +++ b/01_primer/03_cargo/index.html @@ -0,0 +1,3177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.3   Cargo构建工具 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

Cargo构建工具

+

1. Cargo 简介

+

对于简单的程序,我们使用 rustc 命令即可编译,但对于较为复杂的程序,应当使用 Cargo 这个工具。Cargo 是 Rust 的构建系统和包管理工具,主要用于依赖库安装和代码构建。我们使用脚本安装 Rust 的时候已经安装上了 Cargo,使用以下查看 Cargo 版本的命令可以验证 Cargo 是否已经正常工作

+
cargo --version
+
+

2. 使用Cargo创建项目

+

2.1. 初始化项目

+

创建项目,如下命令

+
cargo new hello_cargo
+
+

命令执行之后,生成一个名为 hello_cargo 项目目录,目录中包含以下内容

+
    +
  • src: 这个目录用于存放源代码,并默认生成了一个 main.rs 文件
  • +
  • .gitignore: 默认生成的 gitignore 文件, 实际上,我们使用 Cargo 创建项目的时候,Cargo 帮我们在项目目录初始化了 git 仓库
  • +
  • Cargo.toml: TOML(Tom's Obvious, Minimal Language)格式,是 Cargo 配置文件的格式,以下是默认的内容
  • +
+
[package]
+name = "hello_cargo"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+

2.2. 包信息配置

+
    +
  • package: 是一个区域标题,在这里表示下方配置内容用来配置包(package)的
  • +
+

package下的配置项:

+
    +
  • name: 项目名
  • +
  • version: 项目版本
  • +
  • authors: 项目作者
  • +
  • edition: 项目使用的Rust版本
  • +
+

2.3. 依赖配置

+
    +
  • dependencies: 该区域之下的内容,标识项目依赖的代码包或代码库,在 Rust 里,代码的包或库被称为 crate
  • +
+

2.4. 文件结构说明

+

Cargo生成的 main.rs 在 src 目录下,而 Cargo.toml 在项目顶层下,源代码都应该在 src 目录下,顶层目录可以放置:README、许可信息、配置文件、其他与程序源代码无关的文件。

+

如果在创建项目的时候没有使用 Cargo 来创建,也可以把项目转化为 Cargo 项目,操作如下:

+
    +
  • 把源代码文件移到 src 目录下
  • +
  • 创建 Cargo.toml 并填写相应的配置
  • +
+

3. 构建项目

+

3.1. 编译代码

+

使用以下命令编译代码,生成二进制可执行文件

+
cargo build
+
+

如果是在Mac/Linux操作系统中,生成的二进制可执行文件路径如下

+
target/debug/hello_cargo
+
+

如果是在windows中,生成的二进制可执行文件路径如下

+
target/debug/hello_cargo.exe
+
+

第一次运行cargo build会在顶层目录生成 cargo.lock 文件,该文件负责追踪项目以来的确切版本,不需要手动去修改该文件

+

3.2. 编译并执行代码

+

我们还可以在编译代码后直接执行程序,使用如下命令

+
cargo run 
+
+

实际上,执行 cargo run 命令后,cargo 先编译程序生成可执行文件,再执行程序,如果之前编程成功过,并且源代码没有修改的情况下,那么就会直接运行二进制可执行文件。

+

3.3. 检查代码

+

使用以下命令可以检查代码,确保编译通过,但不会产生任何可执行文件

+
cargo check
+
+

实际上,cargo check要比cargo build执行快的多,编写代码的时候开发者可以连续反复的使用cargo check检查代码,确保自己的代码写的没有问题,最后需要执行的时候再使用 cargo build 生成二进制可执行文件,提高开发效率。

+

3.4. 构建发行版程序

+

实际上,在上面执行 cargo build 命令生成了一个用于开发调试使用的二进制可执行文件,当我们真正需要发版的时候,需要 加上 --release 参数进行编译,如下命令

+
cargo build --release
+
+

加上 --release 参数后,编译时代码会进行优化,编译的时候变长,但生成的二进制可执行文件执行效率也更高。生成的二进制可执行文件将保存在 target/release 目录中,而不是 target/debug 目录中。

+

最后得出以下结论:

+
    +
  • +

    在项目开发中,代码编译时使用 cargo build 命令

    +
  • +
  • +

    在项目发布时,代码编译使用 cargo build --release 命令

    +
  • +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/01_primer/img/01-01.png b/01_primer/img/01-01.png new file mode 100644 index 0000000..6e82002 Binary files /dev/null and b/01_primer/img/01-01.png differ diff --git a/02_guessing_game/01_guessing_game/index.html b/02_guessing_game/01_guessing_game/index.html new file mode 100644 index 0000000..2f521c5 --- /dev/null +++ b/02_guessing_game/01_guessing_game/index.html @@ -0,0 +1,3422 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2.1   猜数游戏 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

猜数游戏

+

一、概述

+

使用猜数游戏的程序,来学习以下内容:

+
    +
  • let、match等的方法
  • +
  • 相关的函数
  • +
  • 外部crate
  • +
+

游戏目标:

+
    +
  • 生成1-100之间的随机数
  • +
  • 提示玩家做一个猜测
  • +
  • 猜测完成之后,程序会提示太大或者太小
  • +
  • 如果猜测正确,打印一个庆祝信息,并退出程序
  • +
+

二、实战过程

+

2.1 生成随机数

+

新建名为 guessing_game 的项目,如下命令

+
cargo new guessing_game
+
+

我们编辑main.rs文件,修改为以下内容

+
use std::io;
+
+fn main() {
+    println!("猜数!");
+
+    println!("猜一个数字");
+
+    // 使用let声明变量
+    let mut guess = String::new();
+    io::stdin().read_line(&mut guess).expect("无法读取行");
+
+    println!("你猜的数字是: {}", guess);
+}
+
+

相关知识点:

+

2.1.1 use

+

我们在上面的第一行代码中使用了use关键字,该关键字是用于导入库。在main函数中我们使用到了read_line函数,该函数用于读取终端输入,该函数在io库中,而io库属于rust的标准库,所以我们使用了use std::io;进行引入。

+
+

rust默认会把一个叫做prelude的库导入到程序中,包含了一些基本的函数。如果我们使用的函数不在prelude库中,那么我们需要显式地导入到程序中,导入包的关键字就是use

+
+

2.1.2 let

+

在rust中使用let关键字声明变量,但是变量默认不可变,属于immutable状态,如果我们在声明变量的时候希望能修改它,那么还需加上mut关键字

+

2.1.3 关联函数

+

在以上示例代码中,包含以下的一行

+
let mut guess = String::new();
+
+

String是一个类,::符号用于调用关联函数new(),这样就生成了一个空的字符串。rust里的关联函数类似于Java的中静态方法。在Rust里,很多地方会出现new()函数,用于初始化对象。

+

2.1.4 取地址符号

+

在上面的io::stdin().read_line(&mut guess)这部分代码中,read_line函数需要传入了一个可变的变量,并且我们在变量前面加了&符号,代表传入的是这个变量的引用,变量的饮用指向变量的原始内存地址。把引用传到read_line函数里面,让函数能修改指针地址所存储的值。引用在rust里也是默认不可变的,需要添加mut关键字让变量可变。

+

2.1.5 io::Result

+

read_line这个方法会返回一个io::Result的结果,在rust的标准库里都有一个叫Result的类型,有泛型Result,也有子模块Result,如上面io::Result就属于io这个子模块的Result

+

这里的Result是一个枚举(enum)类型,一个枚举类型通常有几个固定的值,这些值称作这个枚举类型的变体。io::Result这个枚举类型一共有两个变体,一个是OK,另一个是ErrResult这个类型通常定义了一些方法,比如我们在代码里调用的expect方法。

+

假如返回的io.Result值是Err,那么expect就会中断当前的程序,并将传入的字符串信息写出来;如果返回的值是OK,那么expect就会提取OK中附加的值,并将这个值作为结果返回给用户。

+
+

如果我们调用read_line之后没有调用expect,那么编译代码时rust将会提示相应的警告,因为返回的io::Result是一个未使用的变量,程序可能存在潜在性的错误。这是rust的安全机制引起的,也因此我们在编写代码的时候就可以避免很多错误。

+
+

2.1.6 占位符

+

在以下的这个行代码中,我们使用到了{},在rust的双引号内,是一个占位符,在编译的时候会替换成后面的变量。如果双引号中里有多个花括号,在编译时会替换成后面的多个变量值。

+
println!("你猜的数字是: {}", guess);
+
+

2.2 生成神秘数字

+

2.2.1 下载依赖包

+

我们首先需要生成1到100之间的随机数,但是rust标准里并不包含生成随机数字的功能,不过官方提供了一个crate用于生成随机数,crate名称叫做randcrate仓库的官方网站是:[https://crates.io]https://crates.io,我们可以在crate官方网站中找到相关的crate

+

在rust里, crate我们可以叫做“库”或者“包”,crate可以分为两种,一是我们构建好的二进制可运行文件,另一种是library,library的功能就是给其他程序使用。

+

我们在项目的Cargo.toml文件中引入rand库,如下

+
[package]
+name = "guessing_game"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+rand = "0.3.14"
+
+

在上面的配置文件中,我们在dependencies中新加了一行rand = "0.3.14",左边代表库的名称,右边代表库的版本号,表示的是我们项目需要依赖这个库。当我们执行cargo build之后,cargo会去[crates.io]https://crates.io下载我们定义的rand库到本地,同时rand库依赖的库也会被下载到本地。下载完成之后会执行编译,如果本地存在了对应的库,则直接执行编译。

+

cargo第一次执行cargo build命令的时候,会生成Cargo.lock文件,该文件保存了当前项目依赖的所有库以及对应的库版本,如果下次继续执行build指令,cargo将直接从Cargo.lock中直接读取依赖库信息,并加载到我们的项目中。

+

当我们希望cargo读取的是Cargo.toml文件获取版本信息,而不是Cargo.lock获取版本信息时,我们可以通过cargo update命令更新依赖,cargo会根据Cargo.toml提供的版本信息更新依赖,并且再次写入到Cargo.lock中。

+

2.2.2 引入trait

+

trait类似于Java中的接口,但trait不是用于继承,而是引用,引用trait之后,可以使用trait定义的相关方法。下面我们引入rand库中的Rng,如下

+
use rand::Rng;
+
+

引入之后我们就可以使用生成随机数的函数了,我们先将生成的随机数打印出来,如下代码

+
let secret_number = rand::thread_rng().gen_range(1, 101);
+println!("神秘数字是{}", secret_number);
+
+

在上面代码中,我们调用rand::thread_rng()生成了一个随机数生成器,再调用gen_range()方法实现了随机数的生成,这个方法的第一个参数是最小值闭区间,第二个参数是最大值开区间。

+

2.3 比较猜测的数字与神秘数字

+

我们需要比较guessecret_number这两个变量,首先我们需要从标准库中引入如下cmp::Ordering,如下代码

+
use std::cmp::Ordering;
+
+

这里的Ordering是个枚举类型,包含三个值,如下

+
    +
  • Less: 小于
  • +
  • Greater: 大于
  • +
  • Equal: 等于
  • +
+

比较的代码如下

+
// 先将guess变量转换为整数类型
+let guess: u32 = guess.trim().parse().expect("Please type a number")
+
+
+match guess.cmp(&secret_number){
+    Ordering::Less => println!("Too small!"),
+    Ordering::Greater => print!("To big!"),
+    Ordering::Equal => println!("You win"),
+}
+
+

在上面的代码中,我们使用一个同名的u32类型的变量guess,这样,在该行代码之上定义的String类型的guess变量就会被隐藏(shadow)。在该行代码以后,遇到的guess则是u32类型的guess。这个特性通常使用在我们需要类型转换的场景中,这样我们无需新起一个变量名,而是直接使用之前的变量名。

+

trim: trim函数会把字符串两边的空白内容去掉 +parse: parse函数会把将字符串解析成u32类型

+

实际上,我们生成的随机数secret_number默认是i32类型,但是在往下的代码中,secret_number被用于与guess做对比,所以根据rust类型解析与推倒的机制,secret_number在编译的时候转变为了u32。

+

2.4 多次猜测

+

在以上的程序中,用户只能猜测一次数据是否正确,为了能实现多次猜测,我们需要做一个无线循环。我们吧才猜测的逻辑放在loop中,即可实现无限循环。

+
loop {
+    println!("猜一个数字");
+    let mut guess = String::new();
+    io::stdin().read_line(&mut guess).expect("无法读取行");
+    println!("你猜的数字是: {}", guess);
+
+    // 先将guess变量转换为整数类型
+    let guess: u32 = guess.trim().parse().expect("Please type a number");
+
+    match guess.cmp(&secret_number) {
+        Ordering::Less => println!("Too small!"),
+        Ordering::Greater => print!("To big!"),
+        Ordering::Equal => println!("You win"),
+    }
+}
+
+

但上面的程序中,即使我们猜对了,也不会停止。我们应该将逻辑改为,让输入正确之后,我们应当跳出循环,改为如下代码

+
loop {
+    println!("猜一个数字");
+    let mut guess = String::new();
+    io::stdin().read_line(&mut guess).expect("无法读取行");
+    println!("你猜的数字是: {}", guess);
+
+    // 先将guess变量转换为整数类型
+    let guess: u32 = guess.trim().parse().expect("Please type a number");
+
+    match guess.cmp(&secret_number) {
+        Ordering::Less => println!("Too small!"),
+        Ordering::Greater => print!("To big!"),
+        Ordering::Equal => {
+            println!("You win");
+            break;
+        }
+    }
+}
+
+

2.5 完善程序

+

2.5.1 完善健壮性

+

如果我们在在猜数的时候,输入的是一个非数字的字符,程序将被异常退出,我们在解析数字的时候使用match表达式,表达式里针对不同的枚举结果进行不同的处理,如下代码

+
let guess: u32 = match guess.trim().parse(){
+    // 解析正确时将数字返回
+    Ok(num) => num,
+    // 解析错误时,使用 continue 跳出本次循环,进行下一次循环
+    Err(_) => continue
+};
+
+

2.5.2 取消提示

+

还需注意的是,如果我们的目的是让用户玩这个游戏,那么就不应该把结果打印出来,所以去掉下面这行代码

+
println!("神秘数字是{}", secret_number);
+
+

三、运行程序

+

最终main.rs文件编写的程序代码如下

+
use rand::Rng;
+use std::cmp::Ordering;
+use std::io;
+
+fn main() {
+    println!("猜数游戏!");
+    let secret_number = rand::thread_rng().gen_range(1, 101);
+
+    loop {
+        println!("猜一个数字");
+        let mut guess = String::new();
+        io::stdin().read_line(&mut guess).expect("无法读取行");
+        println!("你猜的数字是: {}", guess);
+
+        // 先将guess变量转换为整数类型
+        let guess: u32 = match guess.trim().parse() {
+            // 解析正确时将数字返回
+            Ok(num) => num,
+            // 解析错误时,使用 continue 跳出本次循环,进行下一次循环
+            Err(_) => continue,
+        };
+
+        match guess.cmp(&secret_number) {
+            Ordering::Less => println!("Too small!"),
+            Ordering::Greater => print!("To big!"),
+            Ordering::Equal => {
+                println!("You win");
+                break;
+            }
+        }
+    }
+}
+
+

编译

+
cargo build
+
+

运行

+
target/debug/guessing_game
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/03_common_programming_concepts/01_variables/index.html b/03_common_programming_concepts/01_variables/index.html new file mode 100644 index 0000000..48a4517 --- /dev/null +++ b/03_common_programming_concepts/01_variables/index.html @@ -0,0 +1,3044 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3.1   变量与可变性 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

变量与可变性

+

一、变量

+
    +
  • +

    声明变量使用let

    +
  • +
  • +

    默认情况下,变量是不可变的

    +
  • +
  • +

    在声明变量的时候,在变量的前面加上mut,就可以使变量可变。

    +
  • +
+

我们先来回顾一下以下的代码

+
fn main() {
+    let x = 5;
+    println!("The value of x is {}!", x);
+}
+
+

我们先声明了一个x变量,并打印到终端上,x默认被推倒为i32类型,这个变量默认就是不可变的。因为没有mut关键字,如下示例代码

+
fn main() {
+    let mut x = 5;
+    println!("The value of x is {}!", x);
+    x = 6;
+    println!("The new value of x is {}!", x);
+}
+
+

二、常量

+

常量(constant),常量在绑定值以后也是不可变的,但是他与变量有很多区别

+
    +
  • +

    常量不可以使用mut,常量永远都是不可变的

    +
  • +
  • +

    声明常量使用constant关键字,它的类型必须被标注清楚

    +
  • +
  • +

    常量可以在任何作用域内进行申明,包括全局作用域

    +
  • +
  • +

    常量只可以绑定到常量表达式,无法绑定到函数调用结果或只能在运行时才能计算出的值

    +
  • +
+

在程序运行期间,常量在其作用域内一直有效,Rust里常量的命名规范是使用全大写字母,每个单词之间使用下划线分开,如下例子

+
const MAX_POINTS: u32 = 10_0000;
+
+

在rust里数字可以添加下滑线,来增强可读性,如以上的值时十万。

+

三、shadowing

+

可以使用相同的名称声明新的变量,新的变量就会shadow(隐藏)之前的同名变量,看到以下代码,如果我们执行编译将会报错,因为变量默认不可变

+
fn main() {
+    let x = 5;
+    x = 6;
+    println!("The value of x is {}!", x);
+}
+
+

但如果我们使用重新声明的方式,代码将能编译通过

+
fn main() {
+    let x = 5;
+    let x = x + 1;
+    println!("The value of x is {}!", x);
+}
+
+

如果在let x = 6;这行代码之后,再使用到x,该变量就是就是最新的变量了。因为后面x的声明把前面的x进行shaw(隐藏)了。show(隐藏)和把变量标记为mut是不一样。

+
    +
  • 如果不使用let关键字,那么重新给飞mut的变量赋值会导致编译错误
  • +
  • 而使用let声明的同名新变量,也是不变的
  • +
  • 使用let声明的同名新变量,它的类型可以和之前不同
  • +
+

示例代码:

+
fn main() {
+    let spaces = "   ";
+    let spaces = spaces.len();
+    println!("The spaces value of x is {}!", spaces);
+}
+
+

在以上代码中,第一个spaces是字符串类型,而第二个spaces则变成了usize类型(区分计算机位数的无符号整型)。如果我们把代码改成如下内容

+
fn main() {
+    let mut spaces = "   ";
+    spaces = spaces.len();
+    println!("The spaces value of x is {}!", spaces);
+}
+
+

此时编译会报错,因为在spaces = spaces.len();这行代码中,左边是包含mut关键字的字符串类型,右边的执行结果是usize类型。

+
+

rust的show功能,让我们可以灵活地复用变量名。

+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/03_common_programming_concepts/02_data_type/index.html b/03_common_programming_concepts/02_data_type/index.html new file mode 100644 index 0000000..baac0c0 --- /dev/null +++ b/03_common_programming_concepts/02_data_type/index.html @@ -0,0 +1,3483 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3.2   数据类型 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

数据类型

+

一、概述

+

Rust的数据类型包含两个大类,即:标量和复合类型。Rust是静态语言,在编译时必须知道所有变量类型

+
    +
  • +

    基于使用的值,编译器通常能够推断出它的具体类型

    +
  • +
  • +

    但如果可能的类型比较多,就必须添加类型声明标注,否则会编译报错。例如把String调用parse方法转为整形,我们必须标注接受值的变量为整形,是因为该方法能推断的类型种类比较多,如:i32u32等。

    +
  • +
+

我们可以使用以下示例代码声明一个整形变量,用于存储通过解析字符串得到的值。这样rust就知道我们要解析的值确定是u32类型

+
fn main() {
+    let guess: u32 = "42".parse().expect("Not a number!");
+    println!("You guessed: {}", guess);
+}
+
+

二、标量类型

+
    +
  • +

    一个标量类型代表一个单个的值

    +
  • +
  • +

    Rust有四个主要的标量类型

    +
  • +
  • +

    整数类型

    +
  • +
  • +

    浮点类型

    +
  • +
  • +

    布尔类型

    +
  • +
  • +

    字符类型

    +
  • +
+

2.1 整数类型

+

整数类型 +整数类型没有小数部分,例如u32就是一个无符号的整数类型,占据32位的空间。无符号整数类型以u开头,有符号的数据类型以i开发,rust的整数类型列表如下图

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
长度有符号类型无符号类型
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize
+

有符号的数字类型有正有负,无符号的数字类型只有正数。

+

有符号范围:-(2^n - 1) 到 2^(n-1) - 1

+

无符号范围:2^n - 1

+

arch类型由计算机的架构位数所决定,如果计算机是64位,那么isizeusize的位数就是64。使用isizeusize的主要场景是对某种集合进行索引操作。

+

整数字面值 +整数的字面值,可以有多种表示形式,如下表

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
字面值示例
十进制(decimal)98_222
十六进制(hex)0xff
八进制(Octal)0o77
二进制(Binary)0b1111_0000
字节(仅限与u8类型)b'A'
+

出了byte类型之外,所有的数值类型都允许使用类型后缀,例如:57u8

+

如果不清楚应该使用哪种类型,可以使用rust相应的默认类型,整数的默认类型是i32,总体上来说速度很快,即使在64位系统中

+

2.1.1 整数溢出

+

例如:u8的范围是0-255,如果你把一个u8变量的值设为256,那么:

+
    +
  • 在调试模式下编译,rust会检查整数溢出,如果发生溢出,程序在运行时就会发生panic
  • +
  • 在发布模式下(--release)编译,rust不会检查可能导致panic的整数溢出,如果发生溢出,rust会执行环绕操作,256变成0,257变成1,以此类推,但不会导致程序的panic
  • +
+

2.2 浮点类型

+

rust有两种基础的浮点类型,也就是含有小数部分的类型,包含以下两种

+
    +
  • +

    f32,占据32位长度,单精度

    +
  • +
  • +

    f64,占据64位长度,双精度

    +
  • +
+

rust的浮点数类型使用了 IEEE-754 标注来描述,f64 是默认类型,因为在现代计算机里上f64和f32运行速度差不多,但f64更加精确。

+

2.3 数值的操作

+
    +
  • 数值支持加、减、乘、除、余计算,
  • +
+

2.3 布尔类型

+

rust的布尔类型也有两个值:true和false,占用一个字节的大小

+

2.4 字符类型

+

rust语言中char类型被用来描述语言中最基础的单个字符,字符类型的字面值使用单引号,占用4个字节的大小,字符类型是Unicode标量值,可以表示比 ASCII 多很多的字符内容:拼音、中文、日文、韩文、零长度空白字符、emoji表情等。范围值是

+
    +
  • +

    U+0000 到 U+D7FF

    +
  • +
  • +

    U+E000 到 U+10FFFF

    +
  • +
+

但是,Unicode 中并没有“字符”概念,所以直觉上认为的字符也许与rust中的概念不相符。

+

三、复合类型

+

复合类型可以将多个值放在一个类型了,rust提供两种基础的复合类型:元组(tuple)、数组(数组)

+

3.1 元组

+

Tuple可以将多个类型的多个值放在一个类型里,Tuple的长度是固定的,一旦声明后就无法改变。创建Tuple的过程如下

+
    +
  • +

    在小括号里,将值用都好隔开

    +
  • +
  • +

    Tuple中的每个位置都对应一个类型,Tuple中各元素的类型不必相同

    +
  • +
+

3.1.1 声明元组

+

以下创建一个Tuple类型数据,并将数据内容输出到终端上

+
fn main() {
+    let tup: (i32, f64, u8) = (500, 6.4, 1);
+    let pat = expr;
+}
+
+

3.1.2 获取tuple的元素值

+

可以使用模式匹配来结构(destructure)一个Tuple来获取元素的值。例子:

+
fn main() {
+    let tup: (i32, f64, u8) = (500, 6.4, 1);
+    let (x, y, z) = tup;
+
+    println!("The value of x, y z, is: {},{},{}", x, y, z);
+}
+
+

3.1.3 访问tuple的元素

+

在rust中使用点标记法,后接元素索引号来访问tuple元素,如下

+
fn main() {
+    let tup: (i32, f64, u8) = (500, 6.4, 1);
+
+    println!("The value of tup items is: {},{},{}", tup.0, tup.1, tup.2);
+}
+
+

3.2 数组

+

数组也可以将多个值放在一个类型里,但数组中每个元素的类型必须相同,数组的长度也是固定的,一旦声明之后,不可以改变。

+

声明数组 +在中括号里,各个值使用都好隔开,如下例子

+
let a = [1, 2, 3, 4, 5];
+
+

以下再定义一个包含所有月份的数组,并打印第一个元素

+
fn main() {
+    let months = [
+        "January",
+        "February",
+        "March",
+        "April",
+        "May",
+        "June",
+        "July",
+        "August",
+        "September",
+        "October",
+        "November",
+        "December",
+    ];
+
+    print!("the first item is {}", months[0]);
+}
+
+

3.2.2 数组的用处

+

如果想让数据存储在栈(stack)内存上,而不是堆(heap)内存上,或者想保证有固定数量的元素,这时使用数组更有号去处。

+

数组是由预导入模块(prelude)提供,不过prelude也是标准库的一部分,的可以说标准库式prelude的超集。

+

实际上rust标准库还提供了一个和数组类型的数据类型--vector,数组没有vector灵活,vector的长度可以改变。如果我们不确定使用数组还是使用vector,这时候应当使用vector。

+

3.2.3 数组的类型

+

数组的类型以及长度以下面这种形式表示[类型;长度],如下代码

+
let a:[i32; 5] = [1, 2, 3, 4, 5];
+
+

如果数组的每个元素都相同,那么我们可以使用以下的声明方式

+
let a = [3; 5];
+
+

它就相当于以下表达式

+
let a = [3, 3, 3, 3];
+
+

3.2.4 数组的访问

+

数组是stack上分配的单个块内存,可以使用索引来访问数组元素,如下读取months这个数组的第一个元素

+
let first = months[0];
+
+

rust不允许继续访问其数据相邻的地址的内存,运行的时候会报数组越界的错误(runtime时会panic)。如果访问的数组索引超出了数组的范围,编译的时候可能不会通过,因为rust回对编译的代码进行简单的检查,如下

+
fn main() {
+    let months = [
+        "January",
+        "February",
+        "March",
+        "April",
+        "May",
+        "June",
+        "July",
+        "August",
+        "September",
+        "October",
+        "November",
+        "December",
+    ];
+
+    let index = 15;
+
+    print!("{}", months[index]);
+}
+
+

以上代码编译时将会报错,因为rust通过简单的判断能判定处months元素个数只有12个,使用15这个索引值访问是不被允许的。但如果我们把逻辑变得更加复杂,rust就没法在编译时进行检查了,只能在运行时进行代码检查,执行过程中逻辑出错rust将会报错并退出程序。如下代码

+
fn main() {
+    let months = [
+        "January",
+        "February",
+        "March",
+        "April",
+        "May",
+        "June",
+        "July",
+        "August",
+        "September",
+        "October",
+        "November",
+        "December",
+    ];
+
+    let index = [15, 16, 17];
+
+    print!("{}", months[index[0]]);
+}
+
+
+

因为有了编译时简单逻辑检查,也使得我们的程序更加安全。

+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/03_common_programming_concepts/03_function/index.html b/03_common_programming_concepts/03_function/index.html new file mode 100644 index 0000000..b4a8ee2 --- /dev/null +++ b/03_common_programming_concepts/03_function/index.html @@ -0,0 +1,3046 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3.3   函数 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

函数

+

1. 函数的声明

+

声明函数使用fn关键字,依照惯例,针对函数名和变量名,Rust使用snake case 命名规范,即所有字母都是小写,单词之间使用下花香分开。如下

+
fn main() {
+    println!("Hello, world!");
+    annother_function();
+}
+
+fn annother_function() {
+    println!("Another function.");
+}
+
+

2. 函数的参数

+

parameters是我们在定义函数时的参数,叫形参;arguments是我们在调用函数时的实际参数,叫实参。在函数的签名里,必须声明每个参数的类型,如下

+
fn main() {
+    println!("Hello, world!");
+    annother_function(5, 6); // arugment is 5 and 6
+}
+
+fn annother_function(x: i32, y: i32) {
+    // parameter is x and y
+    println!("the value of x is: {}", x);
+    println!("the value of y is: {}", y);
+}
+
+

3. 函数中的语句(statement)与表达式(expression)

+

函数有一系列的语句组成,可选的可以由一个表达式结束。语句是执行一些动作的指令,函数的定义也是语句,语句不返回值,所以不可以使用let将一个语句赋给一个变量。rust是一个基于表达式的语言,表达式会执行计算并产生一个值。

+

示例:

+
fn main() {
+    let x = 5;
+}
+
+

以上代码中,main函数的整体也是语句。其中let x = 5;是一条语句 ,该部分的右边是一个表达式,该表达式的值就是5,如果我们把该行改为let x = 5 + 6;,那么表达式就是5 + 6。表达式可与作为语句的一部分,调用函数是表达式,调用println!宏也是表达式。

+

3. 函数的返回值

+

->符号后面声明函数返回值的类型,但是不可以为返回值命名。在rust里,通常返回值就是函数体里面最后一个表达式的值。如果想提前返回,需使用returen关键字,并指定一个值,大多数函数默认使用最后一个表达式作为返回值。如下例子:

+
fn plus_five(x: i32) -> i32 {
+    x + 5
+}
+
+fn main() {
+    let x = plus_five(6);
+    println!("The value of x is: {}", x);
+}
+
+

在上面代码中,five函数的最后一个表达式值是x = 5,所以x + 5是该函数的返回结果。注意的是x + 5后不能加;,否则返回的是tuple,而不是i32

+

4. 函数的注释

+

函数可以使用单行注释或者多行注释的方式,单行注释符号为//,多行注释以/*开头,以*/结尾,如下示例

+
// 这是单行注释
+fn plus_five(x: i32) -> i32 {
+    x + 5
+}
+
+/**
+ * 这是多行注释
+ */
+fn main() {
+    let x = plus_five(6);
+    println!("The value of x is: {}", x);
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/03_common_programming_concepts/04_control_flow/index.html b/03_common_programming_concepts/04_control_flow/index.html new file mode 100644 index 0000000..22c8fd7 --- /dev/null +++ b/03_common_programming_concepts/04_control_flow/index.html @@ -0,0 +1,3183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3.4   控制流 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

控制流

+

1. if和else表达式

+

1.1. if-else表达式

+

if表达式允许你使用条件来执行不同的代码分支,这个条件必须是 bool类型。if表达式中,与条件相关量的代码叫做分支(arm),可选的在在if表达式之后加else表达式。如下示例代码

+
fn main() {
+    let num = 3;
+    if num < 5 {
+        println!("condition was true");
+    } else {
+        println!("condition was false");
+    }
+}
+
+

1.2. 使用else if处理多重条件

+

当逻辑条件比较多时,我们可以使用else if处理多重条件,如下示例代码

+
fn main() {
+    let number = 6;
+    if number % 4 == 0 {
+        print!("{} is divisible by 4", number);
+    } else if number % 3 == 0 {
+        print!("{} is divisible by 3", number);
+    } else if number % 2 == 0 {
+        print!("{} is divisible by 2", number);
+    } else {
+        print!("{} is not divisible by 4, 3, or 2", number);
+    }
+}
+
+

但如果程序里使用了多于一个else if,那么最好使用match来重构代码。

+

1.3. 在let语句中使用if

+

因为if是一个表达式,所以可以将它放在let语句中等号右边,如下示例

+
fn main() {
+    let condition = true;
+    let number = if condition { 5 } else { 6 };
+
+    print!("The value of number is: {}", number);
+}
+
+

单要注意的是,if表达式和紧跟着的else表达式,返回的结果必须是一种类型,否则rust将会编译报错。rust在编译时会进行相应的安全检查,编译的时候就必须明确变量的类型是什么,以便在其他地方使用。如果我们将上面的第三行代码改为如下内容,此时if和else返回值的类型是不同的,也就是说在编译时无法确定number变量的具体类型,将会编译不通过

+
let number = if condition { 5 } else { "6" };
+
+

2. rust的循环

+

rust提供了3中循环:loop、while、for

+

2.1. loop循环

+

loop关键字告诉rust反复执行一块代码,知道逻辑中主动让其停止。可以在loop循环中使用break关键字来告诉程序合适停止循环。如下示例代码

+
fn main() {
+    let mut counter = 0;
+
+    let rustlt = loop {
+        counter += 1;
+
+        if counter == 10 {
+            break counter * 2;
+        }
+    };
+
+    println!("The result is {}", rustlt);
+}
+
+

2.2. while条件循环

+

另一种常见的循环模式时每次执行循环之前都判断一次,while条件循环就是为这种模式而生

+
fn main() {
+    let mut number = 3;
+
+    while number != 0 {
+        println!("{}!", number);
+
+        number = number - 1;
+    }
+
+    println!("LIFTOFF!!!");
+}
+
+

2.3. 使用for循环来遍历集合

+

可以使用while循环或者loop来遍历集合,但是容易出错而且效率低,如下例子

+
fn main() {
+    let a = [10, 20, 30, 40, 50];
+    let mut index = 0;
+
+    while index < 5 {
+        println!("the value is: {}", a[index]);
+        index += 1;
+    }
+}
+
+

上面的程序具有以下缺点

+
    +
  • +

    容易出错:我们在 while的执行条件中使用了index < 5表达式,这里的5代表数组索引的数量,但如果我们数组元素很多,就会很容易写错索引的数量。

    +
  • +
  • +

    执行效率低:因为执行每次遍历之前,程序先会去检查index < 5这个条件。

    +
  • +
+

针对集合的遍历场景,使用for循环更加简洁紧凑,它可以针对集合中的每一个元素执行一些代码。由于for循环的安全性、简洁性,所以它在rust里用的最多。如下代码:

+
fn main() {
+    let a = [10, 20, 30, 40, 50];
+    for element in a.iter() {
+        println!("the value is: {}", element);
+    }
+}
+
+

for循环具有一下优点

+
    +
  • 永远不会出现索引错误
  • +
  • 每次循环都不需要额外进行判断
  • +
+

2.4. Range

+

Range由标准库提供,指定一个开始数字和一个结束数字(不含结束数字),Range可以生成它们之间的数字。rev方法可以反转Range。如果能结合 for 循环和 Range 来实现 while 循环的功能,将会在一定程度上提高程序的性能。如下示例代码

+
fn main() {
+    for number in (1..4).rev() {
+        println!("{}!", number);
+    }
+    println!("LIFTOFF!!!");
+}
+
+

上面的程序,将会输出3到1的结果。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/04_ownership/01_stack_and_heap/index.html b/04_ownership/01_stack_and_heap/index.html new file mode 100644 index 0000000..32dea75 --- /dev/null +++ b/04_ownership/01_stack_and_heap/index.html @@ -0,0 +1,3105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.1   栈内存和堆内存 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

栈内存和堆内存

+

1. 概述

+

所有权是Rust最独特的特性,它让Rust无需GC就可以保证内存安全。

+

1.1. 什么是所有权?

+
    +
  • +

    Rust的核心特性就是所有权

    +
  • +
  • +

    所有程序在运行时就必须管理它们使用内存的方式:有些语言有垃圾回收机制,在程序运行时,它们会不断地寻找不再使用的内存,将内存收集和释放,如Java;在其他语言中,程序员必须显示地分配和释放内存,如C/C++。

    +
  • +
  • +

    Rust使用了第三种方式:内存是通过所有权来管理的,其中包含一组编译器时检查的规则。当程序运行时,所有权特性不会减慢程序的运行速度。

    +
  • +
+

2. 栈内存和堆内存(stack vs heap)

+

在rust这样的系统级编程语言里,一个值是在stack上还是在heap上对语言的行为和你为什么要做某些决定是有更大的影响。

+

在我们运行代码的时候,stack和heap都是可用的内存,但它们的结构很不相同。

+

2.1. 存储数据

+

stack按值的接收顺序来存储,按相反的顺序将它们移除(后进先出,LIFO)

+
    +
  • +

    添加数据叫压栈或入栈

    +
  • +
  • +

    移除数据叫做出栈或弹出栈

    +
  • +
+

所有存储在stack上的数据必须是已知的固定大小,编译时大小未知的数据或者运行时大小可能发生改变的数据必须放在heap上。

+

heap内存的组织性差一些

+
    +
  • +

    当你把数据放入heap时,你会请求一定数量的空间

    +
  • +
  • +

    操作系统在heap里找到一块足够大的空间,把它标记为在用,并返回一个指针,也就是这个空间的地址

    +
  • +
+

把值压到stack上不叫分配,因为指针是已知固定大小的,可以把指针放在stack上。但如果想要实际数据,你必须用指针来定位。把数据压到stack上要比heap上分配快得多,因为操作系统不需要寻找存储新数据的空降,那个位置永远在stack的顶端;在heap上分配空间需要做更多的工作,操作系统首先需要找到一个足够大的空间来存储数据,然后做好记录方便下次再分配。

+

2.2. 访问数据

+
    +
  • +

    访问heap中的数据要比访问stack中的数据慢,因为需要通过指针才能找到heap中的数据,多了指针跳转的环节,属于间接访问。对于现代的处理器来说,由于缓存的缘故,如果指令在内存中跳转的次数越少,那么速度就越快

    +
  • +
  • +

    如果数据排列的距离比较近,那么处理器的处理速度就会更快一些(stack上)

    +
  • +
  • +

    如果数据之间的距离比较远,那么处理速度就会慢一些(heap上),在heap上分配大量空间,也是需要时间的

    +
  • +
+

2.3. 函数调用

+

当代码调用函数时,值被传入函数(也包括指向 heap 的指针),函数本地的变量被压在 stack 上,当函数结束后,这些值会从stack上弹出。

+

3. 所有权存在的原因

+

所有权解决的问题:

+
    +
  • +

    跟踪代码哪些部分正在使用 heap 的哪些数据

    +
  • +
  • +

    最小化heap上的重复数据

    +
  • +
  • +

    清理heap 上未使用的数据以避免空间不足

    +
  • +
+

一旦明白了所有权,那么就不需要经常去想stack或heap了。但是知道管理heap数据是所有权存在的原因,这有助于解释它为什么会这样工作!

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/04_ownership/02_ownership_rules/index.html b/04_ownership/02_ownership_rules/index.html new file mode 100644 index 0000000..c8329d6 --- /dev/null +++ b/04_ownership/02_ownership_rules/index.html @@ -0,0 +1,3115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.2   所有权规则 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

所有权规则

+
    +
  • +

    每个值都有一个变量,这个变量是该值的所有者

    +
  • +
  • +

    每个值同时只能有一个所有者

    +
  • +
  • +

    当所有者超出作用域的时候,该值将被删除

    +
  • +
+

1. 变量的作用域(scope)

+

scope就是程序中一个项目的有效范围,如下代码

+
fn main() {
+    // s不用
+    let s = "hello"; // s可用
+                     // 可以对 s 进行操作
+} // s的作用域到此结束,s不再可用
+
+

1.1. String类型

+
    +
  • +

    String比那些基础标量数据类型更复杂

    +
  • +
  • +

    字符串字面值:程序里手写的那些字符串值,它们是不可变的

    +
  • +
  • +

    Rust还有第二种字符串类型:String,在heap上分配,能够存储在编译时未知数量的文本

    +
  • +
+

可以使用 from 函数从字符串字面值创建出 String 类型,如下例子

+
let s = String::from("hello");
+
+

:: 表示 from 是String类型下的函数,这类字符串是可以被修改的,如下代码

+
fn main() {
+    let mut s = String::from("hello");
+    s.push_str(", world!");
+    println!("{}", s);
+}
+
+

为什么 String 类型的值可以被修改,而字符串字面值却不能修改呢?

+

因为它们处理内存的方式不同。

+
    +
  • +

    对于字符串字面值,在编译时就知道它的内容了,其中文本内容直接被硬编码到最终的可执行文件里。因为其不可变性,所以速度快、高效。

    +
  • +
  • +

    对于String类型,为了支持可变性,需要在heap上分配内存来保存编译时未知的文本内容。操作系统必须在运行时来请求内存,这步是通过调用 String::from 来实现,当用完String之后,需要使用某种方式将内存返回给操作系统。这一步,在拥有GC的语言中,GC会跟踪并清理不再使用的内存。没有GC,就需要我们去识别内存何时不再使用,并调用代码将它返回。如果忘了,那就量费内存;如果提前做了,变量就会非法;如果做了两次,也是导致bug,必须一次分配对应一次释放。

    +
  • +
+

Rust采用了不同的内存回收方式,对于某个值来说,当拥有它的变量走出作用范围时,内存会立即自动地交还给操作系统(立即释放)。当变量走出作用域之后,会调用drop函数。

+

1.2. 变量和数据的交互方式:移动(Move)

+

整型数据的赋值 +多个变量可以与一个数据使用一种独特的方式来交互,如下代码

+
fn main() {
+    let x = 5;
+    let y = x;
+}
+
+

整数是已知且固定大小的简单的值,这两个5被压到了stack中。

+

String的赋值 +示例代码如下

+
fn main() {
+    let s1 = String::from("hello");
+    let s2 = s1;
+}
+
+

虽然代码与上一个代码示例很相似,但是运行方式时完全不一样的。一个String由3部分组成:

+
    +
  • +

    指向存放字符串内容的内存的指针

    +
  • +
  • +

    字符串的长度

    +
  • +
  • +

    字符串的容量

    +
  • +
+

上面这些数据放在 stack 上,而字符串真正的内容在 heap 上,长度(len)是存放字符串内容所需的字节数,容量(capacity)是指 String 从操作系统总共获得内存的总字节数。如下图

+

10-01.png

+
    +
  • 当 s1 赋给 s2 ,String 的数据被复制了一份:在stack 上复制了一份指针、长度、容量,并没有复制指针所指向的heap上的数据,也就是说 rust 没有复制被分配的内存,如下图
  • +
+

10-02.png

+

根据rust的特性,当变量离开作用域时,Rust会自动调用drop函数,并将变量使用的heap内存释放,那么按照上图的模型,当s1、s2离开作用域时,它们都会尝试释放相同的内存,即导致二次释放(double free)bug。

+

不过,为了保证内存安全,当 s1 赋给 s2 后,rust 会让 s1 失效。当s1离开作用域的时候,rust不需要释放任何内存。

+

如果我们编译以下代码,将会报错,因为s1 赋给 s2 后,s1已经失效

+
fn main() {
+    let s1 = String::from("hello");
+    let s2 = s1;
+
+    print!("{}", s1);
+}
+
+

总结 +一下是总结内容

+
    +
  • +

    浅拷贝(shallow copy):浅拷贝只复制指向某个数据的指针,而不复制数据本身,新旧数据还是共享同一块内存

    +
  • +
  • +

    深拷贝(deep copy):深拷贝会另外创造一个一模一样的数据,新数据跟原数据不共享内存,修改新数据不会改到原数据

    +
  • +
+

但由于rust的赋值让旧变量失效了,所以说我们使用一个新的术语:移动(Move)。这隐含了一个rust的设计原则:Rust不会自动创建数据的深拷贝,就运行时性能而言,任何自动赋值的操作都是廉价的。

+

如果真想对heap上的String数据进行深度拷贝,而不仅仅是stack上的数据,可以使用clone方法,如下代码

+
fn main() {
+    let s1 = String::from("hello");
+    let s2 = s1.clone();
+
+    println!("s1 = {}, s2 = {}", s1, s2);
+}
+
+

克隆的操作是比较消耗资源的,主要针对heap上的数据。

+

针对于已知类型和大小的数据,数据在stack上,在变量赋值的时候直接在 stack 上进行数据的复制,变量赋值过后不会影响旧变量的使用,如下代码

+
fn main() {
+    let x = 5;
+    let y = x;
+
+    print!("{}, {}", x, y);
+}
+
+

以上的代码中x对y进行了浅拷贝,与调用clone函数在行为上没有任何差别。

+

rust提供了一个copy trait,可以用于像整数这样完全放在 stack 上面的类型,如果一个类型实现了copy这个trait,那么旧的变量在赋值后仍然可用。如果一个类型或者该类型的一部分实现了 drop trait ,那么rust不允许它再去实现 copy trait 了。

+

任何简单标量的组合类型都可以是copy的,任何需要分配内存或某种资源的都不是copy的。一些拥有copy trait的类型如下:

+
    +
  • +

    所有的整数类型,例如 u32

    +
  • +
  • +

    bool

    +
  • +
  • +

    char

    +
  • +
  • +

    所有浮点数类型,例如 f64

    +
  • +
  • +

    tuple(元组),如果其他所有字段都是 copy 的,如 (i32, i32)是, (i32, string)不是

    +
  • +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/04_ownership/03_ownership_and_function/index.html b/04_ownership/03_ownership_and_function/index.html new file mode 100644 index 0000000..daecbb3 --- /dev/null +++ b/04_ownership/03_ownership_and_function/index.html @@ -0,0 +1,3017 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.3   所有权与函数 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

所有权与函数

+

1. 概述

+

在语义上,将值传递给函数和把值赋给变量是类似的:将值传递给函数将会发生移动或复制。如下示例

+
fn main() {
+    // 声明String类型变量
+    let s = String::from("Hello world");
+    // s 被移动到函数里面
+    take_ownership(s); // 此行以后,s 不再有效
+
+    // x 是一个i32类型的整数,等于 5
+    let x = 5;
+    // 因为 i32 实现了 copy trait,所以往函数里传的 x 是一个拷贝
+    makes_copy(x); // 此行代码以后,x 依然有效
+
+    println!("x: {}", x)
+} // main函数执行完成后,x和s都离开了作用域,因为 s 已经被移动到了 take_ownership 函数里面,所以不在执行内存释放操作
+
+fn take_ownership(some_string: String) {
+    println!("{}", some_string)
+} // 执行完毕后,some_string 离开了作用域,内存被释放
+
+fn makes_copy(some_number: i32) {
+    println!("{}", some_number)
+} // 执行完毕后,some_number 离开了作用域,拷贝的值被释放
+
+

再看以下的代码

+
fn main() {
+    // s1 变量从 gives_ownershio 函数中获得所有权
+    let s1 = gives_ownership();
+
+    let s2 = String::from("hello");
+
+    // s2 将所有权转移给 takes_and_gives_back 函数,并返回一个 String 类型的数据,将所有权转移给 s3
+    let s3 = takes_and_gives_back(s2);
+}
+
+fn gives_ownership() -> String {
+    let some_string = String::from("hello");
+    some_string
+}
+
+fn takes_and_gives_back(a_string: String) -> String {
+    a_string
+}
+
+

一个变量的所有权总是遵循同样的模式:

+
    +
  • +

    把一个值赋给其他变量时就会发生移动

    +
  • +
  • +

    当一个包含 heap 数据的变量离开作用域,他的值就会被 drop 函数清理,除非数据的所有权移动到另一个变量上

    +
  • +
+

2. 让函数使用某个值,但不获得所有权

+

当我们将变量赋给函数时,所有权会丢失,在函数的最后再将所有权返回,即可实现 调用处保留了参数的所有权

+
fn main() {
+    let s1 = String::from("hello");
+
+    // 在main函数里将s1的所有权移动给calculate_length函数,在 calculate_length 再将所有权移交回来
+    let (s1, len) = calculate_length(s1);
+
+    println!("The length of '{}' is {}.", s1, len);
+}
+
+fn calculate_length(s: String) -> (String, usize) {
+    let length = s.len();
+
+    // calculate_length 函数再将s的所有权移动给 调用它的函数
+    (s, length)
+}
+
+

但以上这种做法我们不得不将参数传进函数里,再将参数返回回来,而Rust中的“引用”特性可以完美地解决这个问题。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/04_ownership/04_reference_and_borrow/index.html b/04_ownership/04_reference_and_borrow/index.html new file mode 100644 index 0000000..cb9186d --- /dev/null +++ b/04_ownership/04_reference_and_borrow/index.html @@ -0,0 +1,3083 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.4   引用与借用 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+ +
+
+ + + +
+
+ + + + + + + + + + + + +

引用与借用

+

1. 引用与借用

+

我们使用引用参数来计算字符串的长度,如下代码

+
fn main() {
+    let s1 = String::from("hello");
+
+    let len = calculate_length(&s1);
+
+    println!("The length of '{}' is {}.", s1, len);
+}
+
+fn calculate_length(s: &String) -> usize {
+    s.len()
+}
+
+

在以上代码中,main函数并没有转移s1参数的所有权,而是将s1参数的引用传到计算的函数,在传入参数前面使用&符号代表传递一个“引用”。&s1代表创建了s1的引用,但不拥有s1,当&s1在计算函数内走出作用域时,它指向的值(s1)并不会在清理掉。

+

在calculate_length函数里,参数的类型是 &String,而不是 String,&符号就标识引用,引用可以做到 引用某些值而不取得其所有权。如下图

+

12-01.png

+

在计算函数中,s的作用域与其他参数类似,但它不会在自己离开作用域后销毁其指向的数据,因为它并不拥有该数据的所有权,我们就把以引用作为函数参数的行为叫做“借用”。默认情况下,借用的值不可变。

+

2. 让引用的数据可变

+

通过前面的文章中我们知道mut关键字标识变量可变,同样,我们可以给原变量和引用同时加上mut关键字,让引用的数据可变。如下示例代码

+
fn main() {
+    let mut s1 = String::from("hello");
+
+    let len = calculate_length(&mut s1);
+
+    println!("The length of '{}' is {}.", s1, len);
+}
+
+fn calculate_length(s: &mut String) -> usize {
+    s.push_str(", world");
+    s.len()
+}
+
+

但要注意的是,可变引用有一个重要的限制,在特定的作用域内,对某一块数据,只能有一个可变引用,这样的好处是可在编译时防止数据竞争。如下示例代码将会编译报错

+
fn main() {
+    let mut s = String::from("hello");
+    let s1 = &mut s;
+    let s2 = &mut s;
+    println!("{},{}", s1, s2);
+}
+
+

在以下三种行为都满足的话可能会发生数据竞争

+
    +
  • 两个或多个指针同时访问同一个数据
  • +
  • 至少有一个指针用于写入数据
  • +
  • 没有使用任何机制来同步数据的访问
  • +
+

这种竞争机制在运行时是很难发现的,所以rust做了一个根本的解决,那就是编译的时候就防止 两个指针同时指向一个数据。

+

我们可以通过创建新的作用域,来允许非同时地创建多个可变的引用,如下示例代码

+
fn main() {
+    let mut s = String::from("hello");
+
+    {
+        let s1 = &mut s;
+    }
+
+    let s2 = &mut s;
+}
+
+

以上代码中,s1的作用域结束了之后,即可创建s的新引用赋给 s2。

+

3. 不变引用

+

不可以同时拥有一个可变引用和一个不可变引用,如下代码将会编译报错

+
fn main() {
+    let mut s = String::from("hello");
+    let r1 = &s;
+    let r2 = &s;
+    let s1 = &mut s;
+
+    println!("{}, {}, {}", r1, r2, r3);
+}
+
+

但允许多个不变引用同时存在的。

+

4. 悬空引用(Dangling References)

+

悬空指针(Dangling Pointer):一个指针引用了内存的某个地址,而这块地址内存可能已经释放并分配给其他人使用了。

+

在Rust里,编译器可以保证引用永远不是悬空引用,如果我们引用了某些数据,编译器将保证在引用离开作用域之前数据不会离开作用域。我们看以下的一个例子

+
fn main() {
+    let r = dangling();
+}
+
+fn dangling() -> &String {
+    let s = String::from("hello");
+    &s
+}
+
+

在dangling函数中,我们返回s引用,但实际上,当dangling函数执行完毕后,s变量指向的数据已经被释放,那么&s将是一个悬空引用。不过,rust不允许这种情况发生,以上的代码并不会编译通过。

+

5. 总结

+

在任何给定的时刻,引用 只能满足下列条件之一:

+
    +
  • +

    一个可变的引用

    +
  • +
  • +

    任意数量不可变的引用

    +
  • +
+

另外,引用必须一直有效。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/04_ownership/05_slice/index.html b/04_ownership/05_slice/index.html new file mode 100644 index 0000000..23f7642 --- /dev/null +++ b/04_ownership/05_slice/index.html @@ -0,0 +1,3115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5   切片 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

切片

+

1. 概述

+

Rust的另一种不持有所有权的数据类型:切片(slice)

+

我们先思考一个问题,编写一个函数,让它实现下列功能:

+
    +
  • 它能接收字符串作为参数
  • +
  • 返回它在这个字符串里找到的第一个单词
  • +
  • 如果函数没有找到任何空格,那么整个字符串就返回
  • +
+

我们可以写出如下函数

+
fn first_world(s: &String) -> usize {
+    let bytes = s.as_bytes();
+
+    for (i, &item) in bytes.iter().enumerate() {
+        if item == b' ' {
+            return i;
+        }
+    }
+    s.len()
+}
+
+

我们再添加调用的主函数

+
fn main() {
+    let mut s = String::from("Hello world");
+    let wordIndex = first_world(&s);
+
+    s.clear();
+    print!("{}", wordIndex)
+}
+
+

我们清空变量s之后,如果我们我们还用wordIndex去提取字符串内容,将会报错,引起bug。所以这类的函数设计我们必须随时关注wordIndex的有效性,确保字符串的索引和字符串具有同步性。但是这类工作往往比较繁琐,而且容易出错。Rust为这类问题提供了一个解决方案,就是我们要学习的字符串切片。

+

2. 概念

+

字符串切片是指向字符串其中一部分内容的引用

+

形式:[开始索引..结束索引]

+
    +
  • 开始索引:切片起始位置的索引值
  • +
  • 结束索引:切片终止索引位置的下一个索引值
  • +
+

如下示例代码

+
fn main() {
+    let s = String::from("Hello world");
+    let hello = &s[0..5];
+    let world = &s[6..11];
+
+    println!("{}, {}", hello, world);
+}
+
+

如果是切片的开始是字符串的开始位置,切片的结束是字符串的结束位置,我们可以写成如下语法

+
let hello = &s[..5];
+let world = &s[6..];
+
+

如果我们的切片是整个字符串,则可以写成如下形式

+
let whole = &s[..];
+
+

3. 重写获取字符串第一个单词

+

我们现在可以使用切片的功能重写1中获取字符串中第一个单词的逻辑,如下代码

+
fn first_world(s: &str) -> &str {
+    let bytes = s.as_bytes();
+
+    for (i, &item) in bytes.iter().enumerate() {
+        if item == b' ' {
+            return &s[..i];
+        }
+    }
+    &s[..]
+}
+
+

4. 字符串字面值是切片

+

字符串字面值被直接存储在二进制程序中,如下代码

+
let s = "Hello, World!";
+
+

变量s的类型是&str,他是一个指向二进制程序特定位置的切片

+
    +
  • &str 是不可变引用,所以字符串字面值也是不可变的
  • +
+

5. 将字符切片作为参数传递

+

在上面的代码中,存在如下关键内容

+
fn first_world(s:&String) -> str {
+
+

而有经验的Rust开发者会采用&str作为参数类型,因为这样就可以同时接收 String 和 &str 类型的参数了。如下代码

+
fn first_world(s:&str) -> str {
+
+
    +
  • 如果使用字符串切片,直接调用该函数
  • +
  • 如果使用String,可以创建一个完整的String切片来调用该函数
  • +
+

定义函数时,使用字符串切片来代替字符串引用会使我们的API更加通用,且不会损失任何功能。如下代码

+
fn main() {
+    let my_string = String::from("Hello world");
+    let word_index = first_world(&my_string[..]);
+
+    let my_string_literal = "hello world";
+    let word_index1 = first_world(my_string_literal);
+
+    println!("{} {}", word_index, word_index1)
+}
+
+fn first_world(s: &str) -> &str {
+    let bytes = s.as_bytes();
+
+    for (i, &item) in bytes.iter().enumerate() {
+        if item == b' ' {
+            return &s[..i];
+        }
+    }
+    &s[..]
+}
+
+

6. 其他类型的切片

+

处理字符串切片,其他数据类型也可以用切片,如下代码

+
fn main() {
+    let a = [1, 2, 3, 4, 5];
+    let slice = &a[1..3];
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/04_ownership/img/10-01.png b/04_ownership/img/10-01.png new file mode 100644 index 0000000..2858447 Binary files /dev/null and b/04_ownership/img/10-01.png differ diff --git a/04_ownership/img/10-02.png b/04_ownership/img/10-02.png new file mode 100644 index 0000000..2c43db1 Binary files /dev/null and b/04_ownership/img/10-02.png differ diff --git a/04_ownership/img/12-01.png b/04_ownership/img/12-01.png new file mode 100644 index 0000000..4762311 Binary files /dev/null and b/04_ownership/img/12-01.png differ diff --git a/05_struct/01_struct/index.html b/05_struct/01_struct/index.html new file mode 100644 index 0000000..a371204 --- /dev/null +++ b/05_struct/01_struct/index.html @@ -0,0 +1,3188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.1   结构体 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

struct

+

1. 概述

+

struct,结构体

+
    +
  • 自定义的数据类型
  • +
  • 为相关联的值命名,打包组成有意义的组合
  • +
+

2. struct的使用

+

2.1. 定义struct

+
    +
  • 使用struct关键字,并为整个struct命名
  • +
  • 在花括号里,为所有字段(field)定义名称和类型
  • +
+

例如:

+
struct User{
+    username: String,
+    email: String,
+    sign_in_count: u64,
+    active: bool,
+}
+
+

在定义每一个字段的后面都需要包含一个“逗号”,即使是最后一个字段也需要包含逗号

+

2.2. 实例化struct

+

想要使用struct,需要创建struct的实例:

+
    +
  • 为每个字段指定具体值
  • +
  • 无需按照声明的顺序进行指定
  • +
+

2.3. 取得struct里面的某个值

+

使用点标记法,如下代码

+
let mut user1 = User{
+    email: String::from("some@example.com"),
+    username: String::from("someusername123"),
+    active: true,
+    sign_in_count: 1,
+}
+
+user1.email = String::from("another@example.com")
+
+
+

注意:一旦struct的实例是可变的,那么实例中所有字段都是可变的

+
+

2.4. struct作为函数的返回值

+

struct可以作为函数的返回值,如下代码

+
fn build_user(email: String, username: String)-> User{
+    User {
+        email: email,
+        username: username,
+        active: true,
+        sign_in_count: 1,
+    }
+}
+
+

当字段名与字段值对应的变量名相同时,就可以使用字段初始化简写的方式,如下代码

+
fn build_user(email: String, username: String)->User{
+User{
+    email,
+    username,
+    active: true,
+    sign_in_count: 1,
+}
+}
+
+

2.5. struct更新语法

+

当你想基于某个struct实例来创建一个实例的时候,可以使用struct的更新语法,我们先看不使用更新语法的代码

+
let user2 = User{
+    email: String::from("another@example.com"),
+    username: String::from("anotheruser567"),
+    active: user1.active,
+    sign_in_count: user1.sign_in_count
+};
+
+

使用更新语法之后,可以写成如下代码

+
let user2 = User{
+    email: String::from("another@example"),
+    username: String::from("anotherusername567"),
+    ..user1
+};
+
+

2.6. Tuple struct

+

可以定义Tuple的struct,叫做tuple struct

+
    +
  • Tuple struct整体有个名,但里面的元素没有名
  • +
  • 适用:想给整个tuple起名,并让它不同于其他tuple,而且又不需要给每个元素起名
  • +
+

定义tuple struct:使用struct关键字,后面是名字,以及里面元素的类型

+
struct Color(i32, i32, i32);
+struct Point(i32, i32, i32);
+let black = Color(0, 0, 0);
+let origin = Point(0, 0, 0);
+
+

black和origin是不通的类型,是不通的tuple struct实例

+

2.7. Unit-Like Struct(没有任何字段)

+

可以定义没有任何字段的struct,叫做Unit-Like struct(因为与(),单元类型类似)

+
    +
  • 适用于某个类型上实现某个trait,但是在里面又没有想要存储的数据
  • +
+

2.8. struct数据的所有权

+

我们先看如下代码

+
struct User{
+    username: String,
+    email: String,
+    sign_in_count: u64,
+    active: bool,
+}
+
+

上面代码中,字段使用了String而不是&str:

+
    +
  • 该struct实例拥有其所有的数据
  • +
  • 只要struct实例是有效的,那么里面的字段数据也是有效的
  • +
+

struct里也可以存放引用,但需要使用生命周期(生命周期后面会讲到)

+
    +
  • +

    生命周期保证只要struct实例是有效的,那么里面的引用也是有效的

    +
  • +
  • +

    如果struct里存储引用,而不使用生命周期,那么就会报错

    +
  • +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/05_struct/02_struct_sample/index.html b/05_struct/02_struct_sample/index.html new file mode 100644 index 0000000..d3ab8a6 --- /dev/null +++ b/05_struct/02_struct_sample/index.html @@ -0,0 +1,3035 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.2   结构体示例 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

rust示例

+

1. 概述

+

我们先编写一个函数,用于计算长方形的的面积,如下代码

+
fn main() {
+    let width = 30;
+    let length = 50;
+    println!("{}", area(width, length));
+}
+
+fn area(width: u32, length: u32) -> u32 {
+    width * length
+}
+
+

在上面代码中,我们可以看到 width 和 length 似乎没有关联,它们两看起来没有任何关系。如果我们能把长和宽组合到一起,代码将更容易理解和维护。我们可以使用学习过的元组来行来存储长和宽,如下代码

+
fn main() {
+    let rect = (30, 50);
+    println!("{}", area(rect));
+}
+
+fn area(dim: (u32, u32)) -> u32 {
+    dim.0 * dim.1
+}
+
+

然而换成元组之后,虽然两个变量在一起了,但是每一个元素没有名称,我们不知道哪个是长,哪个是宽。此时我们就可以使用结构体来存储长放心的长与宽。

+

2. 结构体的使用

+

我们使用结构体来实现计算长方形面积的功能,如下代码

+
struct Rectangle {
+    width: u32,
+    length: u32,
+}
+
+fn main() {
+    let rect = Rectangle {
+        width: 30,
+        length: 50,
+    };
+
+    println!("{}", area(&rect));
+}
+
+fn area(rect: &Rectangle) -> u32 {
+    rect.width * rect.length
+}
+
+

如果我们使用println!宏和{}占位符直接打印reat,将会报错,如下代码

+
println!("{}", rect);
+
+

因为可以使用println!来打印的数据,它们都实现了std::fmt::Display这个接口,rust里的基本数据类型已实现了该接口,但是结构体没有实现该接口。不过我们可以使用{:?}或者{:#?}来打印结构体,但使用这种方法需要实现debug,如下代码

+
#[derive(Debug)]
+struct Rectangle {
+    width: u32,
+    length: u32,
+}
+
+fn main() {
+    let rect = Rectangle {
+        width: 30,
+        length: 50,
+    };
+
+    // area 函数借用了area的实例,调用完成之后主函数仍然可以调用reat
+    println!("{}", area(&rect));
+
+    println!("{:?}", rect)
+}
+
+fn area(rect: &Rectangle) -> u32 {
+    rect.width * rect.length
+}
+
+

在上面的代码中,我们用到derive这个关键词,代表派生一个Trait,实际上rust提供很多可派生的Trait,以上用到的Debug只是其中的例子。

+

3. 总结

+

在这篇文章,我们介绍了权限的打印方法,下面做一个总结

+

rust的基本数据类型实现了std::fmt::Display这个接口,可以使用{}占位符进行打印;如果我们希望自定义的结构体可以实现打印功能,需要实现std::fmt::Debug接口,实现的方式是在结构体定义的头部添加上以下一行代码

+
#[derive(Debug)]
+
+

加入一个结构体实现了std::fmt::Debug接口,那么在打印的时候就可以使用{:?}占位符或者{:#?}占位符。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/05_struct/03_struct_method/index.html b/05_struct/03_struct_method/index.html new file mode 100644 index 0000000..c72c4e8 --- /dev/null +++ b/05_struct/03_struct_method/index.html @@ -0,0 +1,3098 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.3   结构体方法 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

struct的方法

+

一、概述

+

方法和函数很类似:使用fn作为关键字,有方法名称、参数、返回值

+

方法和函数不同之处:

+
    +
  • 方法是在struct(或enum、trait对象)的上下文定义
  • +
  • 方法的第一个参数总是self,表示方法被调用的struct实例
  • +
+

二、方法的使用

+

2.1 方法的实现

+

我们可以把计算长方形面积使用方法来实现,如下代码

+
#[derive(Debug)]
+struct Rectangle {
+    width: u32,
+    length: u32
+}
+
+impl Rectangle {
+    fn area(&self) -> u32 {
+        self.width * self.length
+    }
+}
+
+fn main() {
+    let ract = Rectangle {
+        width: 30,
+        length: 50,
+    };
+
+    println!("{}", ract.area())
+}
+
+

在main函数中,我们也可以拥有 ract 可变的借用,如下代码

+
impl Rectangle {
+    fn area(&mut self) -> u32 {
+        self.width * self.length
+    }
+}
+
+fn main() {
+    let mut ract = Rectangle {
+        width: 30,
+        length: 50,
+    };
+
+    println!("{}", ract.area())
+}
+
+

在impl块里定义方法,方法的第一个参数可以是 &self,也可以获得其所有权或可变借用。和其他参数一样。使用方法可以让代码拥有更好的组织。每个struct可以允许拥有多个impl块。

+

2.2 方法的运算符

+
    +
  • +

    C/C++:object->something() 和 (*object).something() 一样

    +
  • +
  • +

    Rust没有 -> 运算符

    +
  • +
  • +

    Rust会自动引用或者解引用这种行为

    +
  • +
+

在调用方法时,Rust根据情况自动添加&&mut*,以便 object 可以匹配方法的签名,如下来两行代码时等价的

+
p1.distance(&p2)
+(&p1).distance(&p2)
+
+

三、关联函数

+

可以在impl 块里定义不把self作为第一个参数的函数,它们叫做关联函数(不是方法)

+

例如

+
String::form()
+
+

关联函数通常用于构造器,如我们可以创建 长方形的关联函数square,如下代码

+
#[derive(Debug)]
+struct Rectangle {
+    width: u32,
+    length: u32
+}
+
+impl Rectangle {
+    fn area(&self) -> u32 {
+        self.width * self.length
+    }
+
+    fn square(size: u32) -> Rectangle {
+        Rectangle { width: size, length: size }
+    }
+}
+
+fn main() {
+    let ract = Rectangle {
+        width: 30,
+        length: 50,
+    };
+
+    println!("{}", ract.area());
+
+    let s = Rectangle::square(20);
+    println!("{}", s.area());
+}
+
+

::符号用于关联函数,还可以用于模块创建命名空间。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/06_enum/01_enum/index.html b/06_enum/01_enum/index.html new file mode 100644 index 0000000..f64af6a --- /dev/null +++ b/06_enum/01_enum/index.html @@ -0,0 +1,3000 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6.1   枚举 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

枚举

+

IP地址一共有两种类型:IPv4、IPv6,所以IP地址要么是IPv4要么是IPv6,就因为这个特性,IP地址就非常适合用枚举来表示,我们把枚举所有可能的值叫做枚举的变体。我们可以使用如下代码定义IP地址的枚举数据

+
enum IpAddrKind {
+    V4,
+    V6,
+}
+
+

可以使用以下的方式使用枚举值

+
let four = IpAddrKind::V4;
+let six = IpAddrKind::V6;
+
+

枚举的变体都位于枚举所在标准命名空间的下面。以下是枚举值的应用示例代码

+
enum IpAddrKind {
+    V4,
+    V6,
+}
+
+fn main() {
+    let four = IpAddrKind::V4;
+    let six = IpAddrKind::V6;
+
+    route(four);
+    route(six);
+    route(IpAddrKind::V6);
+}
+
+fn route(ip_kind: IpAddrKind) {}
+
+

我们还可以使用struct来存储枚举类型,如下代码

+
enum IpAddrKind {
+    V4,
+    V6,
+}
+
+struct IpAddr {
+    kind: IpAddrKind,
+    address: String
+}
+
+fn main() {
+    let home = IpAddr {
+        kind: IpAddrKind::V4,
+        address: String::from("127.0.0.1")
+    };
+
+    let loopback = IpAddr {
+        kind: IpAddrKind::V6,
+        address: String::from("::1")
+    };
+}
+
+

不过在rust里,允许直接将数据附加刀片枚举的变体中,如

+
enum IpAddr {
+    V4(String),
+    V6(String),
+}
+
+

优点:

+
    +
  • 不需要额外的struct存储相关的数据
  • +
  • 每个变体可以用有不同类型以及关联的数据的量
  • +
+
enum IpAddrKind {
+    V4(u8, u8, u8, u8),
+    V6(String),
+}
+
+fn main() {
+    let home = IpAddrKind::V4(127, 0, 0, 1);
+    let loopback = IpAddrKind::V6(String::from("::1"))
+}
+
+

我们再来看标准库中的IPAddr

+
struct Ipv4Addr {
+    // --snip--
+}
+
+struct Ipv6Addr {
+    // --sn
+}
+
+enum IpAddr {
+    V4(Ipv4Addr),
+    V6(Ipv6Addr),
+}
+
+

不论是字符串、数值、结构体,甚至是另外一种枚举类型,都可以作为枚举的变体。如下代码

+
enum Message {
+    Quit,
+    Move { x: i32, y: i32 },
+    Write(String),
+    ChangeColor(i32, i32, i32),
+}
+
+fn main() {
+    let q = Message::Quit;
+    let m = Message::Move { x: 12, y: 24 };
+    let w = Message::Write(String::from("Hello"));
+    let c = Message::ChangeColor(0, 255, 255);
+}
+
+

还可以使用impl关键字定义枚举方法,如下来两行代码

+
enum Message {
+    Quit,
+    Move { x: i32, y: i32 },
+    Write(String),
+    ChangeColor(i32, i32, i32),
+}
+
+impl Message {
+    fn call(&self) {}
+}
+
+fn main() {
+    let q = Message::Quit;
+    let m = Message::Move { x: 12, y: 24 };
+    let w = Message::Write(String::from("Hello"));
+    let c = Message::ChangeColor(0, 255, 255);
+
+    m.call();
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/06_enum/02_option_enum/index.html b/06_enum/02_option_enum/index.html new file mode 100644 index 0000000..f387c00 --- /dev/null +++ b/06_enum/02_option_enum/index.html @@ -0,0 +1,2996 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6.2   Option枚举 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

option 枚举

+

一、概述

+
    +
  • option枚举定义于标准库中
  • +
  • 在prelude(预导入模块)中
  • +
  • 描述了:某个值可能存在(某种类型)或不存在的情况
  • +
+

二、Rust没有NULL

+

在很多编程语言都有NULL,NULL是一个值,它表示“没有值”。这些语言中,一个变量可以处于两种状态:空值(NULL)、非空。NULL的问题在于,当你尝试使用NULL值那样使用NULL值的时候,就会引起某种错误。rust没有NULL,但是NULL的概念还是有用的,因为某种原因变得无效或缺省的值。

+

rust中提供了类似NULL概念的枚举:Option<T>,在标准库中定义如下

+
enum Option<T> {
+    Some(T),
+    None,
+}
+
+

该枚举包含在Prelude(预导入模块)中。可以直接使用:

+
Option<T>
+
+

该枚举的两个变体也可以直接使用

+
Some(T)
+None
+
+

具体示例如下代码

+
fn main() {
+    // 有效的值
+    let some_number = Some(5);
+    let some_string = Some("A String");
+
+    // 无效的值
+    let some_number: Option<i32> = None;
+}
+
+

三、Option<T>Null好在哪里?

+

Option<T>T是不同的类型,不可以把Option<T>直接当成T,若想使用Option<T>中的T,必须将它转换为T。避免了使用非有效值进行计算而导致错误,如在C#中假设某个变量值为NULL,参与计算时将导致错误,如下代码

+
string a = null;
+string b = a + "12345";
+
+

所以rust的Option<T>的设计就避免了NULL值泛滥的情况,使得程序更加安全!

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/06_enum/03_match_expression/index.html b/06_enum/03_match_expression/index.html new file mode 100644 index 0000000..5e5bb47 --- /dev/null +++ b/06_enum/03_match_expression/index.html @@ -0,0 +1,3067 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6.3   math表达式 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

match表达式

+

一、概述

+

match是一个控制流运算符,用于匹配值与模式,并执行相应的代码。

+
    +
  • match允许一个值与一系列模式进行匹配,并执行匹配的模式对应的代码
  • +
  • 模式可以是字面值、变量名、通配符...
  • +
+

match表达式会一次判断模式列表,如果符合条件,将执行对应的代码。如果包含多行代码,可以使用{},如下示例

+
enum Coin {
+    Penny,
+    Nickel,
+    Dime,
+    Quarter
+}
+
+fn value_in_cents(coin: Coin) -> u8 {
+    match coin {
+        Coin::Penny => {
+            println!("panny");
+            1
+        }
+        Coin::Nickel => 5,
+        Coin::Dime => 10,
+        Coin::Quarter => 25,
+    }
+}
+
+
+fn main() {}
+
+

二、绑定值的模式

+

匹配的分支可以绑定到被匹配对象的部分值,因此可以从enum变体中提取,示例代码如下

+
#[derive(Debug)]
+
+enum UsState {
+    Alabama,
+    Alaska
+}
+
+enum Coin {
+    Penny,
+    Nickel,
+    Dime,
+    Quarter(UsState)
+}
+
+fn value_in_cents(coin: Coin) -> u8 {
+    match coin {
+        Coin::Penny => {
+            println!("panny");
+            1
+        }
+        Coin::Nickel => 5,
+        Coin::Dime => 10,
+        Coin::Quarter(state) => {
+            println!("State quarter from {:?}!", state)
+            25
+        },
+    }
+}
+
+
+fn main() {
+    let c = Coin::Quarter(UsState::Alaska);
+    println!("{}", value_in_cents(c));
+}
+
+

三、匹配Option枚举

+

macth可以匹配枚举值,如下示例代码

+
fn main() {
+    let five = Some(5);
+    let six = puls_one(five);
+    let none = puls_one(None);
+}
+
+fn puls_one(x :Option<i32>) -> Option<i32> {
+    match x {
+        None => None,
+        Some(i) => Some(i + 1)
+    }
+}
+
+

四、注意

+

match必须穷枚举所有可能的值,特别是 Option类型的数据,如果不想穷枚举所有可能的值,需要使用_来代替,如下示例代码

+
fn main() {
+    let v = 0u8;
+    match v {
+        1 => println!("one"),
+        3 => println!("three"),
+        5 => println!("five"),
+        7 => println!("seven"),
+        _ => (),
+    }
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/06_enum/04_if_let/index.html b/06_enum/04_if_let/index.html new file mode 100644 index 0000000..fb161b5 --- /dev/null +++ b/06_enum/04_if_let/index.html @@ -0,0 +1,2905 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6.4   if let - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

if let

+

if let 是一个简单的控制流,它值关心一种匹配忽略其他匹配的情况。如下示例代码

+
fn main() {
+    let v = Some(0u8);
+
+    if let Some(3) = v {
+        println!("three");
+    }
+}
+
+

在只关心一种控制流匹配值的情况下,使用 if let 代替 match 让我们编写更少的代码。也放弃了穷枚举的可能。我们可以把if let看作match的语法糖。也就是针对match的某一种特定模式,可以使用 if let来代替。if let还可以搭配else来使用,如下代码

+
fn main() {
+    let v = Some(0u8);
+
+    if let Some(3) = v {
+        println!("three");
+    } else {
+        println!("others");
+    }
+}
+
+

如果我们使用 match 表达式来是实现,则写法如下

+
match v {
+    Some(3) => println!("three"),
+    _ => println!("others");
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/07_project_management/01_package_crate_module/index.html b/07_project_management/01_package_crate_module/index.html new file mode 100644 index 0000000..2961736 --- /dev/null +++ b/07_project_management/01_package_crate_module/index.html @@ -0,0 +1,3149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 7.1   package、crate、module - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

package、crate、module

+

一、概述

+

1.1 名词

+

package:包

+

crate:单元包

+

module:模块

+

1.2 代码组织

+

rust的代码组织主要包括以下

+
    +
  • 哪些细节可以暴露,哪些细节是私有的
  • +
  • 作用域内哪些名称有效
  • +
+

这些功能有时候统称为模块系统,模块系统包含如下内容

+
    +
  • Package(包):Cargo的特性,让你构建、测试、共享crate
  • +
  • Crate(共享包):一个模块树,他可以产生一个library或者可执行文件
  • +
  • Module(模块)、use:让你控制代码的组织、作用域、私有路径
  • +
  • Path(路径):为struct、function或者module等项命名的方式
  • +
+

二、package和Crate

+

2.1 Crate的类型

+
    +
  • binary
  • +
  • library
  • +
+

2.2 Crate Root

+
    +
  • 是源代码文件
  • +
  • Rust编译器是从这里开始,组成你的Crate的根的Module,也就是说它是一个入口文件
  • +
+

2.3 Package

+
    +
  • 包含一个Cargo.toml,它描述了如何构建这些Crates
  • +
  • 最多包含1个library crate
  • +
  • 可以包含任意数量的binary crate
  • +
  • 必须至少包含一个crate(library或binary)
  • +
+

当我们使用cargo new命令创建一个项目时,对应的项目就是一个binary类型的package。

+

cargo会将src/main.rs最为binary crate的根,crate的名与package的名是相同的;如果我们的项目下存在src/lib.rs文件,那么该package就是包含一个library crate,该crate是library crate的根,这个crate的名与package的名也是相同的。

+

cargo会把crate root文件交给rustc来构建library或binary。

+

2.3 cargo的惯例

+

一个package可以同时包含src/mian.rssrc/lib.rs,一个 binary crate,一个 library crate,名称与package相同。

+

一个package可以有多个binary crate,文件放在src/bin下,放在该目录下的咩哥文件都是单独的binary crate。

+

2.4 crate的作用

+

crate将相关的功能组合到一个作用域内,便于在项目间进行共享,防止冲突。例如使用rand crate,访问它的功能需要通过他的名字:rand。

+

三、定义Module

+

定义module来控制作用域和私有性,Module的作用如下

+
    +
  • 在一个crate内,将代码进行分组
  • +
  • 增加可读性,易于复用
  • +
  • 控制项目(item)的访问属性性,public、private
  • +
+

通过module关键字来建立module,module是可嵌套的,在子module可包含其他项目(struct、enum、常量、trait、函数等)的定义。如下示例代码

+
mod front_of_house {
+    mod hosting {
+        fn add_to_wailist() {}
+        fn seat_at_table() {}
+    }
+
+    mod serving {
+        fn take_order() {}
+        fn serving() {}
+        fn take_payment() {}
+    }
+}
+
+

以上代码结构如下图所示

+

21-01

+

src/main.rssrc/lib.rs叫做crate roots。这两个文件(任意一个)的内容形成了名为crate的模块,位于整个模块树的根部。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/07_project_management/02_path/index.html b/07_project_management/02_path/index.html new file mode 100644 index 0000000..1440118 --- /dev/null +++ b/07_project_management/02_path/index.html @@ -0,0 +1,3098 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 7.2   路径 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

路径

+

一、概述

+

在rust里,如果我们想要找到某个模块,我们必须知道其路径,rust的路径有两种形式。

+

绝对路径:从crate root开始,使用 crate 名或字面值 crate。相对路径:从当前模块开始,使用self、super,或者当前模块的标识符。不干事绝对路径还是相对路径,只有由一个表示符组成,如果存在多个标识符,标识符之间使用::进行分割。

+

示例代码如下src/lib.rs

+
mod front_of_house {
+    mod hosting {
+        fn add_to_waitlist() {}
+    }
+}
+
+pub fn eat_at_restaurant() {
+    // 使用绝对路径调用函数
+    crate::front_of_house::hosting::add_to_waitlist();
+    // 使用相对路径调用函数
+    front_of_house::hosting::add_to_waitlist();
+}
+
+

以上的代码分别使用绝对路径和相对路径调用函数,front_of_house模块相对eat_at_restaurant函数在同一个级别,所以函数里可以直接调用。不过上面的代码看似没有问题,但当我们编译的时候会报错。我们将在下文解决这个问题。

+

二、私有边界(privacy boundary)

+

模块不仅可以组织代码,还可以定义私有边界。如果想把函数或者struct等设置为私有,就可以将它放到某个模块中。Rust中所有条目(函数、方法、struct、enum、模块、常量)默认是私有的。父级模块无法访问子级模块中的私有条目,在子模块里,可以使用所有祖先模块的条目。

+

2.1 pub关键字

+

使用pub关键字可以将某些条目标记为公共的。我们将“一”中的代码改为如下,将不会再出现错误

+
mod front_of_house {
+    pub mod hosting {
+        pub fn add_to_waitlist() {}
+    }
+}
+
+pub fn eat_at_restaurant() {
+    // 使用绝对路径调用函数
+    crate::front_of_house::hosting::add_to_waitlist();
+    // 使用相对路径调用函数
+    front_of_house::hosting::add_to_waitlist();
+}
+
+

front_of_house之所以不需要添加pub关键字,因为front_of_houseeat_at_restaurant都在文件根级,可以互相调用。

+

2.2 super关键字

+

super关键字用来访问父级模块中的内容,类型文件系统中的..。如下示例代码

+
fn serve_order() {}
+
+mod back_of_house {
+    fn fix_incorrect_order() {
+        cook_order();
+        // 使用相对路径的方式调用
+        super::serve_order();
+        // 使用绝对路径的方式调用
+        crate::serve_order();
+    }
+
+    fn cook_order() {}
+}
+
+

2.3 pub struct

+

pub 放在struct 之前,代表公共的结构体,struct 里面的字段默认是私有的,想把哪个字段设置为公有,就在对应的字段前面加上pub关键字。如下示例代码

+
mod back_of_house {
+    pub struct Breakfast {
+        pub toast: String,
+        seasonal_fruit: String,
+    }
+
+    impl Breakfast {
+        pub fn summer(toast: &str) -> Breakfast {
+            Breakfast {
+                toast: String::from(toast),
+                seasonal_fruit: String::from("peachers"),
+            }
+        }
+    }
+}
+
+pub fn eat_at_restaurant() {
+    let mut meal = back_of_house::Breakfast::summer("Rye");
+    meal.toast = String::from("Wheat");
+    println!("I'd like {} toast please", meal.toast);
+}
+
+

2.4 pub enum

+

pub 放在 enum 前面是将enum声明为公共。枚举中的变体都变成公共的。如下示例代码

+
mod back_of_house {
+    pub enum Appetizer {
+        Soup,
+        Salad,
+    }
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/07_project_management/03_use/index.html b/07_project_management/03_use/index.html new file mode 100644 index 0000000..bab9f2b --- /dev/null +++ b/07_project_management/03_use/index.html @@ -0,0 +1,3157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 7.3   use - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

use

+

一、概述

+

可以使用use关键字将路径导入到当前作用域内,引入的内容仍然遵循私有性规则,也就是说只有公共的模块引入进来才可以使用。如下示例代码

+
mod front_of_house {
+    pub mod hosting {
+        pub fn add_to_waitlist() {}
+        fn some_function();
+    }
+}
+
+use crate::front_of_house::hosting;
+
+pub fn eat_at_restaurant() {
+    hosting::add_to_waitlist();
+}
+
+

在上面的示例代码中,虽然我们引入了hosting模块,但是仍然不能调用 some_function 这个私有函数。

+

也可以使用相对路径的方式引入模块,如下示例代码

+
use front_of_house::hosting;
+
+

在引入代码的时候,我们通常引用到父级模块即可,再通过父级模块调用函数,而不是引入到具体的函数,这样我们就能容易的区分函数是外部引入的,还是本模块就存在的。

+

二、use的管用做法

+

2.1 函数

+

将函数的父级模块引入作用域,指定到其父级即可;

+
    +
  • struct、enum、其他
  • +
+

对于非函数条目,则指定完整路径,即指定到其本身。如下示例代码

+
use std::collection::HashMap;
+
+fn main() {
+    let mut map = HashMap::new();
+    map.insert(1, 2);
+}
+
+

2.2 同名条目

+

如果在不同的模块存在两个同名条目,而在一个源码文件中需要同时使用这两个模块,这时候use引用指定到父级即可。如我们需要std::fmt::Resultstd::io::Result这两个Result,则需要将其父模块同时引入,如下示例代码

+
use std::fmt;
+use std::io;
+
+fn f1() -> fmt::Result {
+    // 函数内容,此处省略
+}
+
+fn f2() -> io::Result {
+    // 函数内容,此处省略
+}
+
+

除此,我们还可以使用as关键字解决该问题,as 关键字可以为引入的路径指定本地的别名,如下示例代码

+
use std::fmt::Result;
+use std::io::Result as IOResult;
+
+fn f1() -> Result {
+    // 函数内容省略
+}
+
+fn f2() -> IOResult {
+    // 省略函数体
+}
+
+

2.3 使用pub use重新导出名称

+

使用use将路径(名称)导入到作用域后,该名称在此作用域是私有的。如果我们使用pub use关键字进行引用,那么在外部就可以使用,如下示例代码

+
mod front_of_house {
+    pub mod hosting {
+        pub fn add_to_waitlist() {}
+        fn some_function();
+    }
+}
+
+pub use crate::front_of_house::hosting;
+
+

pub use将条目引入到作用域内,同时该条目可以被外部代码引入到它们的作用域内。

+

三、使用外部包(package)

+

Cargo.toml添加依赖的包(package)的声明,当我们运行cargo build时,cargo将从https://crate.io添加对应包到本地,下载好之后,使用use关键字将特定条目引入到作用域。

+

不过使用使用原网站下载依赖可能会很慢,因为https://crate.io的服务器在国外,我们可以使用修改文件文件将下载的源指定改为国内的cargo镜像地址,修改文件~/.cargo/config,添加以下内容

+
[source.crates-io]
+replace-with = 'tuna'
+
+[source.tuna]
+registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"
+
+

以上的配置指定了当cargo去拉去包依赖的时候,将使用清华镜像站。

+

标准库std也被当作外部包,不需要修改Cargo.toml来包含std。不过在需要使用具体条目的时候,还是需要使用use关键字将std中特定的条目引入到当前作用域。

+

四、使用嵌套路径清理大量的的use语句

+

如果使用同一个包或者模块下的多个条目,则可以使用嵌套路径在同一行内将上述条目进行引入,引入格式如下

+
路径相同的部分::{路径差异的部分}
+
+

+
use std::cmp::Ordering;
+use std::io;
+
+

可以改为

+
std std::{
+    cmp::Ordering,
+    io
+}
+
+

+
use std::io;
+use std::io::Write;
+
+

可以改为

+
use std::io::{
+    self,
+    Write
+}
+
+

*代表通配符号,我们还可以使用*把路径中所有的公共条目都引入到当前的作用域。如下示例代码

+
use std::collections::*;
+
+

但这种引入方式需要谨慎使用,适合以下应用场景

+
    +
  • 测试。将所有被测试代码引入到tests模块中
  • +
  • 有时被用于预导入(prelude)模块
  • +
+

五、将模块内容移动到其他文件

+

定义模块时,如果模块后面是“;”,而不是代码块,Rust会从模块同名的文件中加在内容,模块树的结构不会变化。随着模块逐渐变大,该技术让你可以把模块的内容移动到其他文件中。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/07_project_management/img/21-01.png b/07_project_management/img/21-01.png new file mode 100644 index 0000000..a22e7e3 Binary files /dev/null and b/07_project_management/img/21-01.png differ diff --git a/08_aggregate_types/01_vector/index.html b/08_aggregate_types/01_vector/index.html new file mode 100644 index 0000000..53fda26 --- /dev/null +++ b/08_aggregate_types/01_vector/index.html @@ -0,0 +1,3056 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8.1   vector - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

vector

+

一、概述

+

Vector,叫做Vector,又一个标准库提供,可存储多个值,只能存储相同的数据类型,这些值在内存中连续存放。

+

使用Vec::new创建Vector,如下示例代码

+
fn main() {
+    let v: Vec<i32> = Vec::new();
+}
+
+

也可以使用初始值创建Vec<T>,使用vec!宏,如下示例代码

+
fn main() {
+    let v = vec![1, 2, 3];
+}
+
+

二、更新vector

+

可以使用push方法添加元素,如下示例代码

+
fn main() {
+    let mut v = Vec::new();
+    v.push(1);
+    v.push(2);
+    v.push(3);
+}
+
+

三、读取vector的元素

+

两种方式可以引用vector里的值,索引、get方法,如下示例代码

+
fn main() {
+    let v = vec![1, 2, 3, 4, 5];
+    let third: &i32 = &v[2];
+    println!("The third element is {}", third);
+
+    match v.get(2) {
+        Some(third) => println!("The third element id {}", third);
+
+        None => println!("There is no third element");
+    }
+}
+
+

遍历vector

+
fn main() {
+    let v = vec![100, 32, 57];
+    for i in &v {
+        println!("{}", i);
+    }
+}
+
+

我们还可以在编译的过程中修改vector元素的值

+
fn main() {
+    let mut v = vec![100, 32, 57];
+    for i in &mut v {
+        *i += 50;
+    }
+
+    for i in v {
+        println!("{}", i);
+    }
+}
+
+

四、删除vector

+

与其他元素struct一样,当vector离开作用域后,它就被清理掉,它所有的元素也被清理掉。

+

五、所有权和借用规则

+

不能在同一作用域内同时拥有可变和不可变引用。如下示例代码

+
fn main() {
+    // 声明了一个可变的vec
+    let mut v = vec![1, 2, 3, 4, 5];
+    // 引用了一个不可变的借用
+    let first = &v[0];
+    // 又向v中添加值,这里发生了可变的借用,将导致报错
+    v.push(6);
+    println!("The first element is {}", first);
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/08_aggregate_types/02_vector_and_enum/index.html b/08_aggregate_types/02_vector_and_enum/index.html new file mode 100644 index 0000000..6af1336 --- /dev/null +++ b/08_aggregate_types/02_vector_and_enum/index.html @@ -0,0 +1,2894 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8.2   vector和枚举 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

vector 和 enum

+

在vector里只能存放相同的数据,当我们需要存储不同的数据时。这时候我们就使用使用enum加上vector来存储数据。enum的变体可以附加不同类型的数据,enum的变体定义在同一个enum类型下。示例代码如下

+
enum SpreadsheetCell {
+    Int(i32),
+    Float(f64),
+    Text(String)
+}
+
+fn main() {
+    let row = vec![
+        SpreadsheetCell::Int(3),
+        SpreadsheetCell::Text(String::from("blue")),
+        SpreadsheetCell::Float(12.12),
+    ];
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/08_aggregate_types/03_string/index.html b/08_aggregate_types/03_string/index.html new file mode 100644 index 0000000..d5600be --- /dev/null +++ b/08_aggregate_types/03_string/index.html @@ -0,0 +1,3189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8.3   创建和使用字符串 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

创建和使用字符串

+

一、概述

+

Rust开发者特别时新手经常被字符串所困扰,二困扰的原因主要有三个

+
    +
  • Rust倾向于包喽可能的错误
  • +
  • 字符串数据结构复杂
  • +
  • 使用UTF8编码
  • +
+

1.1 字符串是什么?

+

字符串是 Byte 的集合,并且提供了一些方法,这些方法能够将 byte 解析为文本。

+

Rust的核心语言层面,只有一个字符串类型:字符串切片 str (或&str)。

+

字符串切片是 存储在其他地方、并且使用UTF8编码的字符串的引用。字符串字面值存储在二进制文件中,也是字符串切片。

+

1.2 String类型

+

String类型来自标准库而不是核心语言,它是可增长、可修改、可拥有(可获得所有权)的一种数据类型,采用UTF8编码。

+
    +
  • 在rust中说的字符是指?
  • +
+

通常指的是 String 或者 字符串切片(&str)这两种类型,而不是其中的一种。

+

Rust标准库中还包含了很多其他的字符串类型,例如:OsString、OsStr、CString、CStr。以String结尾的通常与String相关,通常拥有所有权,以Str结尾通常与str相关,通常可以借用所有权。它们可以存储不同编码的文本或在内存中以不同的形式展现。

+

二、创建一个新的字符串(String)

+

很多Vec<T>的操作都可用于String。使用String::new()函数创建一个空的字符串。但很多时候使用初始值创建String,以下介绍两种方法

+

2.1 使用 to_string方法

+

to_string方法可用于实现了Display trait的类型,包括字符串字面值,如下示例代码

+
fn main() {
+    let data = "initial contents";
+    let s = data.to_string();
+
+    let s1 = "initial contents".to_string();
+}
+
+

2.2 使用String::from函数

+

String::from函数可以从字面值来创建字符串,如下示例代码

+
fn main() {
+    let s = String::from("initial contents");
+}
+
+

三、如何更新String

+

3.1 push_str

+

push_str()方法,把一个字符串切片附加到String上,如下示例代码

+
fn main() {
+    let mut s = String::from("foo");
+    s.push_str("bar");
+
+    println!("{}", s);
+}
+
+

3.2 push

+

push()方法,把单个字符添加到String上,如下示例代码

+
fn main() {
+    let mut s = String::from("lo");
+    s.push('l');
+}
+
+

3.3 +运算符

+

+运算符,可以对字符串进行拼接,+号前面的变量是String类型,+后面的变量是字符串切片,如下示例代码

+
fn main() {
+    let s1 = String::from("Hello, ");
+    let s2 = String::from("World");
+
+    let s3 = s1 + &s2;
+
+    println!("{}", s3);
+}
+
+

+运算符实现了类似这个签名的方法fn add(self, s: &str) -> String {...}。上面示例代码中,s2是一个String类型,则&s2是一个String类型的引用。

+

+运算符之后的希望是一个字符串切片, 这里之所以能够编译通过,是因为rust在编译的时候,使用了一种“解引用强制转换”(deref coercion)的技术。它把String的引用转为了字符串切片,所以编译可以顺利通过。

+

在执行拼接代码之后,s1的所有权被移到add函数,如果在下面的代码中继续使用将会报错;而s2使用了&来标注,add函数只是借用了s2的所有权,在下面的代码仍然可以继续使用。

+

3.4 format!宏

+

format!宏可以连接多个字符串,假设我们希望将多个单词使用特定符号进行拼接,使用+运算符的实现如下代码

+
fn main() {
+    let s1 = String::from("tic");
+    let s2 = String::from("tac");
+    let s3 = String::from("toe");
+
+    let s4 = s1 + "-" + &s2 + "-" + &s3;
+    println!("{}", s4);
+}
+
+

如果我们使用format!宏将字符串拼接,如下示例代码

+
let s = format!("{}-{}-{}", s1, s2, s3);
+println!("{}", s);
+
+

format!不会取得任何参数的所有权,而是将多个参数格式之后返回一个新的字符串。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/08_aggregate_types/04_string_slice/index.html b/08_aggregate_types/04_string_slice/index.html new file mode 100644 index 0000000..ab0a4db --- /dev/null +++ b/08_aggregate_types/04_string_slice/index.html @@ -0,0 +1,3072 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8.4   字符串表现形式和切割 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+ +
+ + + +
+
+ + + + + + + + + + + + +

字符串表示形式和切割

+

一、概述

+

如果按索引语法访问String的某个部分,会报错,如下示例代码

+
fn main() {
+    let s1 = String::from("hello");
+    // 以下代码将会报错
+    let h = s1[0];
+}
+
+

Rust的字符串不支持索引语法访问

+

二、String类型的内部表示

+

string是Vec<u8>的包装。Rust有三种看待字符串的方式,分别是:字节(Bytes)、Scalar Values(Unicode标量值)、Grapheme Cluster(字形簇)。

+

Rust不允许对String进行索引的最后一个原因:索引操作应消耗一个常量时间 O(1),而String无法保证该时间,因为rust需要遍历所有内容,来确定有多少个合法的字符。

+

三、切割string

+

可以使用[]和一个范围来创建字符串的切片,如下示例代码

+
fn main() {
+    let hello = "小明在学习";
+    let s = &hello[0..6];
+
+    print!("{}", s)
+}
+
+

我们切割了6个字节,此时输出的中文字符串是小明,代表一个中文字符占用了3个字节。在字符串切割时,必须谨慎,比如某种语言的每个字符占用2个字节,那切割的位置索引必须整除2。如果我们切割的位置不在字符的边界,程序运行时将会产生panic。也就是说我们在切割字符串的时候,必须验证字符的边界来切割。

+

四、遍历字符串的方法

+

4.1 获得字节

+

可以使用bytes()方法将字符串按单个字节的方式进行拆分。如下示例代码

+
fn main() {
+    let w = "小明在学习";
+
+    for b in w.bytes() {
+        println!("{}", b)
+    }
+}
+
+

4.2 获得标量值

+

可以使用chars()方法将字符串按单个标量值的方式进行拆分。如下示例代码

+
fn main() {
+    let w = "小明在学习";
+
+    for c in w.chars() {
+        println!("{}", c)
+    }
+}
+
+

4.3 获取字形簇

+

对于字形簇,很复杂,标准库未提供相关接口,如果想快速获取字符串的字形簇,我们可以通过调用三方crate来实现

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/08_aggregate_types/05_hash_map/index.html b/08_aggregate_types/05_hash_map/index.html new file mode 100644 index 0000000..57a980a --- /dev/null +++ b/08_aggregate_types/05_hash_map/index.html @@ -0,0 +1,3050 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8.5   使用哈希映射存储键值对 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

使用哈希映射存储键值对

+

一、概述

+

HashMap是以键值对的形式存储数据,一个键(key)对应一个值(value),HashMap的内部实现使用了Hash函数,Hash函数决定了如何在内存中存放key和value。HashMap适用场景为:通过K(任何类型)来寻找数据,而不是通过索引。

+

二、创建HashMap

+

使用HashMap::new()函数来创建空的HashMap,再通过insert()方法来添加数据。如下示例代码

+
use std::collections::HashMap;
+
+fn main() {
+    let mut scopes = HashMap::new();
+
+    scopes.insert(String::from("Blue"), 10);
+    scopes.insert(String::from("Yellow"), 50);
+}
+
+

HashMap使用比较少,所以不在prelude中。如需要使用HashMap,需要手动引入。标准库对HashMap的支持也比较少,没有内置的宏来创建HashMap。HashMap的数据存放在heap内存上。

+

HashMap是同构的,在一个HashMap中,所有的 K 必须是同一个类型,所有的 V必须是同一个类型。

+

另一种创建HashMap的方式是通过collect方法。在元素类型为Tuple的Vector上使用collect方法,可以组建一个HashMap。要求Tuple有两个值:一个作为K,一个作为V。collect方法可以把数据整合成很多种集合类型,包括HashMap,返回值需要显式指明类型。如下示例代码

+
use std::collections::HashMap;
+
+fn main() {
+    let teams = vec![String::from("Blue"), String::from("Yellow")];
+    let intial_scores = vec![10, 50];
+
+    // zip函数有拉链的意思,将两个数组拉到一起,在使用 collect 方法集合数据,最后指定生成的集合数据为 HashMap
+    let scores: HashMap<_, _> = teams.iter().zip(intial_scores.iter()).collect();
+}
+
+

三、HashMap和所有权

+

对于实现了Copy trait的类型(例如i32),值会会被复制到HashMap中。对于拥有所有权的值(例如String),值会被移动,所有权会转给HashMap。

+

如果将值的引用插入到HashMap,值本身不会移动,在HashMap有效的期间,被引用的值必须保持有效。

+

四、访问HashMap中的值

+

使用get方法访问HashMap的值,参数为K,返回Option<&V>,如下示例代码

+
use std::collections::HashMap;
+
+fn main() {
+    let mut scores = HashMap::new();
+
+    scores.insert(String::from("Blue"), 10);
+    scores.insert(String::from("Yellow"), 10);
+
+    let team_name = String::from("Blue");
+    let scores = scores.get(&team_name);
+
+    match scores {
+        Some(s) => println!("{}", s),
+        None => println!("team not exits"),
+    }
+}
+
+

五、遍历HashMap

+

通常使用HashMap的引用进行遍历,因为在遍历之后,我们通常还需要是使用到HashMap,如下示例代码

+
use std::collections::HashMap;
+
+fn main() {
+    let mut scores = HashMap::new();
+
+    scores.insert(String::from("Blue"), 10);
+    scores.insert(String::from("Yellow"), 10);
+
+    for (k, v) in &scopes {
+        println!("{}: {}", K, v);
+    }
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/08_aggregate_types/06_update_hash_map/index.html b/08_aggregate_types/06_update_hash_map/index.html new file mode 100644 index 0000000..8969fbb --- /dev/null +++ b/08_aggregate_types/06_update_hash_map/index.html @@ -0,0 +1,3073 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8.6   更新哈希映射 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

更新哈希映射

+

一、概述

+

HashMap的大小是可以变化,也就是说键值对的数量可以变化,但是每个K同时只能对应一个V。更新HashMap中的数据有以下几种选择

+

当K存在时

+
    +
  • 替换现有的V
  • +
  • 保留现有的V,忽略新的V
  • +
  • 基于现有的V来更新V
  • +
+

当K不存在时

+
    +
  • 添加一对新的K, V
  • +
+

二、更新操作

+

2.1 替换现有的V

+

如果向HashMap插入一对KV,然后再插入同样的K,但是不同的V,那么原来的V会被替换掉,如下示例代码

+
use std::collections::HashMap;
+
+fn main() {
+    let mut scores = HashMap::new();
+
+    scores.insert(String::from("Blue", 10));
+    scores.insert(String::from("Blue", 25));
+
+    println!("{:?}", scores);
+}
+
+

2.2 只在K不存在任何值的情况下才插入V

+

entry方法,会检查指定的K是否存在一个对应的V,参数为K,返回值为 enum Enrty,代表是否存在。Entry的or_insert()方法,如果K存在,返回对应的V的一个可变引用;如果K不存在,将方法作为K的新值插进去,返回到这个值的可变引用。如下示例代码

+
use std::collections::HashMap;
+
+fn main() {
+    let mut scores = HashMap::new();
+
+    scores.entry(String::from("Blue")).or_insert(50);
+    scores.entry(String::from("Blue")).or_insert(50);
+
+    println!("{:?}", scores);
+}
+
+

2.3 基于现有的V来更新V

+

如下示例代码

+
use std::collections::HashMap;
+
+fn main() {
+    let text = "hello world wonderful world";
+
+    let mut map = HashMap::new();
+
+    for word in text.split_whitespace() {
+        let count = map.entry(word).or_insert(0);
+        *count += 1;
+    }
+
+    println!("{:#?}", map);
+}
+
+

以上代码中,字符串调用split_whitespace方法实现按照空格分割成可遍历的单词集合数据,检测map集合该单词对应的K,如果不存在则调用or_insert方法插入0。最后将返回的值的引用进行 +1,即可实现单词计数的效果。

+

三、Hash函数

+

在默认情况下,HashMap使用了加密功能强大的HashMap函数,Hash函数可以抵抗拒绝服务(Dos)攻击。Hash函数不是可用的最快的Hash算法,但具有更好的安全性。

+

我们可以指定不同的hasher来切换到另一个函数,这里的hasher指的是实现了BuildHasher trait的类型。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/09_error_handling/01_panic/index.html b/09_error_handling/01_panic/index.html new file mode 100644 index 0000000..beca4dd --- /dev/null +++ b/09_error_handling/01_panic/index.html @@ -0,0 +1,2992 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 9.1   不可恢复错误与panic - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

不可恢复错误与panic

+

一、概述

+

Rust的可靠性:错误处理,在大部分情况下,编译时提示错误,并处理。

+

Rust错误的分类如下:

+
    +
  • 可恢复:例如文件未找到,可在此尝试
  • +
  • 不可恢复:bug的另外所说法,通常是逻辑错误,比如访问的索引超出范围
  • +
+

二、Rust的错误处理

+

Rust没有类似异常的机制,针对可恢复的错误,使用 Result<T, E>,针对不可恢复的错误,Rust提供了panic!宏,当执行panic!宏时,程序会执行以下逻辑

+
    +
  • 打印一个错误信息
  • +
  • 展开(unwind)、清理调用栈(Stack)
  • +
  • 退出程序
  • +
+

为了应对panic,展开或终止(abort)调用栈,两种过程有以下区别

+

程序展开调用栈(工作量大):Rust沿着调用栈往回走,清理每个遇到的函数中的数据;立即终止调用栈:不进行清理,直接停止程序,内存需要OS进行清理。如果想让二进制文件更小,可以把设置从“展开”改为“终止”。修改步骤如下:

+
    +
  • cargo.toml中适当的profile部分设置panic='abort',如下示例代码
  • +
+
[package]
+name = "s27_string_cut"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[profile.release]
+panic = 'abort'
+
+

三、使用panic!产生的回溯信息

+

panic!可能出现在我们写的代码中,也可能出现在我们依赖的代码中。通过调用panic!的函数回溯信息来定位引起问题的代码。具体的做法是设置环境变量RUST_BACKTRACE=full可得到回溯信息。为了获取带有调试信息的回溯,必须启用调试符号(不带 --release)。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/09_error_handling/02_result_enum_1/index.html b/09_error_handling/02_result_enum_1/index.html new file mode 100644 index 0000000..fd42127 --- /dev/null +++ b/09_error_handling/02_result_enum_1/index.html @@ -0,0 +1,3071 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 9.2   Result枚举与可恢复错误1 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

Result枚举与可恢复错误1

+

一、概述

+

通常情况下,错误都没有严重到要停止整个应用的地步。当某个函数运行失败,一般都有一些可以简单解释并做出可以做响应的原因所引起的,比如说我们程序想打开某个文件,但这个文件却不存在,这个时候通常是要创建该文件,而不是终止程序。在Rust里我们可以使用Result枚举类型,来处理可能失败的情况。该枚举类型的结构如下

+
enum Result<T, E> {
+    Ok(T),
+    Err(E),
+}
+
+

T:操作成功情况下,Ok变体里返回的数据类型 +E:操作失败的情况下,Err变体里返回的错误的类型

+

二、错误的处理

+

2.1 通常的处理方式

+

处理Result的一种方式:match表达式。和Option枚举一样,Result及其变体也是由prelude代码作用域。错误处理过程如下示例代码

+
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt");
+
+    let f = match f {
+        Ok(file) => file,
+        Err(error) => {
+            panic!("Error opening file {:?}", error)
+        }
+    };
+}
+
+

2.2 匹配不同的错误

+

可以在match里嵌套match,如下示例代码

+
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt");
+
+    let f = match f {
+        Ok(file) => file,
+        Err(error) => match error.kind() {
+            // 文件不存在创建文件
+            ErrorKind::NotFound => macth File::create("hello.txt") {
+                Ok(fc) => fc,
+                Err(e) => panic!("Error creating file: {:?}", e),
+            },
+            // 其他的错误类型,可以自定义变量名,这里叫做 other_error
+            other_error => panic!("Error opening the file: {:?}", other_error),
+        }
+    };
+}
+
+

我们可以使用unwrap_or_else方式实现以上代码实现的功能,如下示例代码

+
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt").unwrap_or_else(|error| {
+        if error.kind() == ErrorKind::NotFound {
+            File::create("hello.txt").unwrap_or_else(|error| {
+                panic!("Error creating file: {:?}", error);
+            })
+        } else {
+            panic!("Error opening the file: {:?}", error);
+        }
+    });
+}
+
+

2.3 unwrap和expect

+

unwrap可以被看作match表达式的一个快捷方法。如果Result的结果是OK,则返回OK的值,如果Result的结果是Err,则调用panic!宏。如下示例代码

+
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt").unwrap();
+}
+
+

我们还可以使用expect方法,改方法和unwrap类似,但可指定错误信息,如下示例代码

+
let f = File::open("hello.txt").expect("无法打开文件");
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/09_error_handling/03_result_enum_2/index.html b/09_error_handling/03_result_enum_2/index.html new file mode 100644 index 0000000..cdab392 --- /dev/null +++ b/09_error_handling/03_result_enum_2/index.html @@ -0,0 +1,3065 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 9.3   Result枚举与可恢复错误2 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

Result枚举与可恢复错误2

+

一、传播错误

+

我们除了在函数出处处理错误;还可以将错误返回给函数的调用者,让调用者进一步进一步处理错误,这个过程就叫做传播错误。如下示例代码

+
use std::fs::File;
+use std::io;
+use std::io::Read;
+
+fn read_username_from_file() -> Result<String, io::Error> {
+    let f = File::open("hello.txt");
+
+    let mut f = match f {
+        Ok(file) => file,
+        Err(e) => return Err(e)
+    };
+
+    let mut s = String::new();
+    match f.read_to_string(&mut s) {
+        Ok(_) => Ok(s),
+        Err(e) => Err(e)
+    }
+}
+
+fn main() {
+    let result = read_username_from_file();
+}
+
+

二、?运算符

+

2.1 ?运算符的使用

+

?运算符是传播错误的一种快捷方式。如果Result的结果是OKOK中的值就是表达式的结果,然后继续执行程序;如果ResultErrErr就是整个函数的返回值,就像使用了return

+

那么,在“一”中的函数可以简写如下

+
fn read_username_from_file() -> Result<String, io::Error> {
+    let mut f = File::open("hello.txt")?;
+
+    let mut s = String::new();
+    f.read_to_string(&mut s)?;
+    Ok(s)
+}
+
+

2.2 ?与from

+

Trait std::convert::From上的from函数,用于错误之间的转换。

+

?所应用的错误,会隐式的被from函数处理,当?调用from函数时,它所接收的错误类型会被转化为当前函数返回类型所定义的错误类型。

+

这种情况用于:针对不同错误类型,返回同一种错误类型。只要相关的错误类型实现了转换为所返回的错误类型的from函数,就可以被转换为返回的错误类型。如同“二”中的示例。

+

我们可以把“二”中的代码改为链式调用,如下内容

+
fn read_username_from_file() -> Result<String, io::Error> {
+    let mut s = String::new();
+    File::open("hello.txt")?.read_to_string(&mut s)?;
+    Ok(s)
+}
+
+

2.3 ?运算符只能用于返回Result的函数

+

如果我们直接在main使用?运算符,如下代码,将会报错

+
use std::fs::File;
+
+fn main() {
+    let f = File::open("hello.txt")?;
+}
+
+

此时的main函数返回单元类型(),相当于什么都没返回,因此会报错。我们可以修改函数如下

+
use std::error::Error;
+use std::fs::File;
+
+fn main() -> Result<(), Box<dyn Error>>{
+    let f = File::open("hello.txt")?;
+    Ok(())
+}
+
+

此时main函数返回一个Result枚举,编辑将不会报错。枚举的变体为单元类型()或者Box<dyn Error>Box<dyn Error>是一个trait对象,此处可以简单理解为:“任意可能的错误类型”。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/09_error_handling/04_when_to_panic/index.html b/09_error_handling/04_when_to_panic/index.html new file mode 100644 index 0000000..8b7667b --- /dev/null +++ b/09_error_handling/04_when_to_panic/index.html @@ -0,0 +1,3062 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 9.4   何时使用panic - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

何时使用panic

+

一、总体原则

+

在rust里错误重要分类两类,一类是可恢复的,另一类是不可恢复的,通过调用panic!宏代表该错误不可恢复。通过返回Result则相当于错误进行了传播,而这类错误是可恢复的。

+

当我们自己写的代码,在某些情况下决定为不可恢复错误的时候,我们可以使用panic!。如果我们写的函数,如果某些情况决定为错误可以恢复,可以返回Resul,这时候将错误的处理权交给了代码的调用者,代码的调用者要根据实际情况决定是否恢复此错误。总结来说如下:

+
    +
  • 在定义一个可能失败的函数时,优先考虑返回Result
  • +
  • 如果某种情况出现的失败失败肯定不可恢复的,就使用panic!
  • +
+

二、使用使用panic!的场景如下

+
    +
  • 演示某些概念:使用unwrap函数,发生错误将使用panic!标记;
  • +
  • 原型代码:unwrap、expect函数,在原型代码里还不知道如何错误错误,使用unwrap、expect这类方法将使用panic!标记错误,发生错误时便于跟踪问题;
  • +
  • 测试:使用unwrap、expect函数,测试的代码如果出现错误,会被panic!立即停止。
  • +
+

三、错误处理的指导性建议

+

当代码最终可能处理损坏状态时,最好使用panic!

+

损坏状态(bad state)表示某些假设、保证、约定或不可变性被打破,如下列情况都可以被认定为损坏状态:

+
    +
  • 非法的值、矛盾的值、或者空缺的值被传入了代码
  • +
  • 这种状态并不能预测,而是偶尔发生
  • +
  • 如果你的代码处理这种状态就无法运行
  • +
  • 在你使用的数据类型中没有一个更好的方法来将这些(处于损坏状态)的信息进行编码
  • +
+

如果不理解以上的概述,我们可以参考一下几个建议使用panic!或者返回Resul的场景

+
    +
  • 当你的代码被调用时,传入了无意义的参数值:panic!
  • +
  • 调用外部不可控代码,返回非法状态,你无法修复:panic!
  • +
  • 如果失败是可以预期的:Result,比如我们想把一个字符串解析成数字,这时候可能成功,可能失败,是可预期的
  • +
  • 当你的代码对某些之进行操作,首先验证这些值的合法性,如果不合法,进行panic!。如果输入数据不合法,往往是因为调用者逻辑出现了问题,也不应该由调用者进行解决,应该就地进行panic!
  • +
+

四、数据验证逻辑的优化

+

针对调用者可能传入非法值的情况,我们可以进行逻辑优化,在创建我们需要的数据类型时,我们在构造函数里实现验证逻辑,我们则无需担心接收的值的有效性。如在该系列文章的“猜数游戏”中,可以有以下逻辑,验证逻辑

+
// ... 前面的逻辑忽略掉
+// 先将guess变量转换为整数类型
+let guess: i32 = match guess.trim().parse() {
+    // 解析正确时将数字返回
+    Ok(num) => num,
+    // 解析错误时,使用 continue 跳出本次循环,进行下一次循环
+    Err(_) => continue,
+};
+
+// 验证传入数值必须大于1、小于100
+if guess < 1 || guess > 100 {
+    println!("The secret number will be between 1 and 100");
+    continue;
+}
+
+// ... 后面的逻辑忽略掉
+
+

我们改为以下逻辑

+
pub struct Guess {
+    value: i32,
+}
+
+impl Guess {
+    // 定义一个关联函数,相当于构造函数
+    pub fn new(value: i32) -> Guess {
+        if value < 1 || value > 100 {
+            panic!("The secret number will be between 1 and 100");
+        }
+
+        Guess { value }
+    }
+
+    // 定义获取值的方法
+    pub fn value(&self) -> i32 {
+        self.value
+    }
+}
+
+fn main() {
+    loop {
+        // ...省略前面的逻辑
+
+        // 先将guess变量转换为整数类型
+        let guess: u32 = match guess.trim().parse() {
+            // 解析正确时将数字返回
+            Ok(num) => num,
+            // 解析错误时,使用 continue 跳出本次循环,进行下一次循环
+            Err(_) => continue,
+        };
+
+        let guess = Guess::new(guess);
+
+        // ...省略后面的逻辑
+    }
+}
+
+

在以上代码中,我们将解析的数据传如Guess::new,如果不发生panic!,代码数据合法,通过了验证;如果发生了panic!,说明传入数据没有通过验证。使用此方式进行数据,优化了验证的过程,无需把验证的逻辑到处写,提高了代码的可读性。

+

要注意的是:在上面的Guess结构体中,必须把value属性设置为私有,再通过结构体方法获取属性的数据。我们不允许通过Guess结构体直接修改value的值,在模块外的代码必须使用Guess::new函数来创建Guess的实例。这样能确保value值通过了有效性的检查,通过检查则产生实例,否则则发生panic!

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/10_generics_trait_lifetime/01_generics/index.html b/10_generics_trait_lifetime/01_generics/index.html new file mode 100644 index 0000000..390d9f2 --- /dev/null +++ b/10_generics_trait_lifetime/01_generics/index.html @@ -0,0 +1,3143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10.1   泛型 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

泛型

+

一、概述

+

泛型是具体类型或其他属性的抽象代替。能提高代码复用的能力,用于处理重复代码的问题。我们可以理解为,我们编写的代码不是最终代码,而是一种模板,里面有一些“占位符”。编译器在编译的时会讲“占位符”替换为具体的类型。例如

+
fn lagest<T>(list: &[T]) -> T {}
+
+

泛型的类型参数通常很短,通常是一个字母。同时使用CameCase命名规范,而字母Ttype的缩写,所以通常使用T作为泛型参数。

+

泛型的使用

+

2.1 函数中定义的泛型

+

函数中使用泛型,通常是参数类型手机用泛型或者返回类型使用泛型,通常在函数名的后面在<>里声明泛型,如下示例

+
fn largest<T>(list: &[T]) -> {
+    ...
+}
+
+

2.2 在结构体中使用泛型

+

在结构体名称后面使用<>申明泛型类型,泛型类型可以用于一个或者多个字段,如下示例代码

+
struct Point<T> {
+    x: T,
+    y: T,
+}
+
+fn main() {
+    let integer = Point { x: 5, y: 10 };
+    let float = Point { x: 1.0, y: 4.0 };
+}
+
+

我们还可以声明多个泛型数据,如下示例代码

+
struct Point<T, U> {
+    x: T,
+    y: U,
+}
+
+fn main() {
+    let mix = Point { x: 5, y: 10.0 };
+}
+
+

可以使用多个泛型类型参数,但是太多类型参数,可能回影响代码的阅读。

+

2.3 Enum定义中的泛型

+

可以让枚举的变体持有泛型数据类型,如下示例

+
enum Option<T> {
+    Some(T),
+    None,
+}
+
+enum Result<T, E> {
+    Ok(T),
+    Err(E),
+}
+
+fn main() {}
+
+

2.4 在方法定义中的泛型

+

为struct或enum实现方法的时候,可在定义中使用泛型,如下示例代码

+
struct Point<T> {
+    x: T,
+    y: T,
+}
+
+// 使用泛型实现struct的方法,在所有的Point<T>类型中都包含x方法
+impl<T> Point<T> {
+    fn x(&self) -> &T {
+        &self.x
+    }
+}
+
+// 使用具体类型实现struct的方法,只有在Point<i32>中才有x1方法,其他的Point<T>类型中不包含x1方法
+impl Point<i32> {
+    fn x1(&self) -> &i32 {
+        &self.x
+    }
+}
+
+

T放在impl关键字后面,表示在类型T上实现方法。如下示例代码

+
struct Point<T> {
+    x: T,
+    y: T,
+}
+
+impl<T, U> Point<T, U> {
+    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
+        Point {
+            x: self.x,
+            y: other.y,
+        }
+    }
+}
+
+fn main() {
+    let p1 = Point { x: 5, y: 4 };
+    let p2 = Point { x: "Hello", y: "c" };
+    let p3 = p1.mixup(p2);
+
+    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
+}
+
+

三、泛型代码的性能

+

使用泛型的代码和使用具体类型的代码运行速度是一样的。因为rust在编译的过程中会执行一个单态化(monomorphization)的过程,即在编译时将泛型替换为具体类型的过程。下面是一段使用泛型原始代码

+
fn mian() {
+    let integer = Some(5);
+    let float = Some(5.0);
+}
+
+

在编译过程中,将泛型进行单态化,变更为类似如下代码

+
enum Option_i32 {
+    Some(i32),
+    None,
+}
+
+enum Option_f64 {
+    Some(f64),
+    None,
+}
+
+fn mian() {
+    let integer = Option_i32::Some(5);
+    let float = Option_f64::Some(5.0);
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/10_generics_trait_lifetime/02_trait/index.html b/10_generics_trait_lifetime/02_trait/index.html new file mode 100644 index 0000000..9d38e39 --- /dev/null +++ b/10_generics_trait_lifetime/02_trait/index.html @@ -0,0 +1,3319 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10.2   Trait - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

trait

+

一、概述

+

Trait告诉Rust编译器,某种类型具有哪些并且可以与其他类型共享的功能,可以使用抽象的方式定义共享行为。Trait bounds(约束)指的是泛型类型参数指定实现了特定行为的类型。

+

Trait与其他语言的借口(interface)类似,但是有区别。

+

二、定义Trait

+

定义Trait的目的是把方法签名放在一起,来实现某种目的所必须的一组行为。定义trait的相关规则如下

+
    +
  • 关键字:trait
  • +
  • 只有方法签名,没有具体实现
  • +
  • trait可以有多个方法,每个方法占一行,以;结尾
  • +
  • 实现该trait的类型必须提供具体方法的实现
  • +
+

如下定义trait的示例代码

+
pub trait Summary {
+    fn summarize(&self) -> String;
+}
+
+

三、在类型上实现trait

+

与类型的实现方法类似,如下示例代码

+
pub trait Summary {
+    fn summarize(&self) -> String;
+}
+
+pub struct NewArticle {
+    pub headline: String,
+    pub location: String,
+    pub author: String,
+    pub content: String,
+}
+
+impl Summary for NewArticle {
+    fn summarize(&self) -> String {
+        format!("{}, by {} ({})", self.headline, self.author, self.location)
+    }
+}
+
+pub struct Tweet {
+    pub username: String,
+    pub content: String,
+    pub reply: bool,
+    pub retweet: bool,
+}
+
+impl Summary for Tweet {
+    fn summarize(&self) -> String {
+        format!("{}: {}", self.username, self.content)
+    }
+}
+
+fn main() {
+    let tweet = Tweet {
+        username: String::from("horse_ebooks"),
+        content: String::from("of course, as you probably already know, people"),
+        reply: false,
+        retweet: false
+    };
+
+    println!("1 new tweet: {}", tweet.summarize());
+}
+
+

四、实现某个trait的约束

+

可以在某个类型上实现某个trait的前提条件为:这个类型或这个trait在本地crate里定义的。

+

无法为外部类型来实现外部的trait,这个限制是程序属性的一部分(也就是一致性),更具体地说是**孤儿原则**,之所以这样命名是因为父类型不存在。此规则确保他人的代码不能破坏你写的代码,反之亦然。如果没有这个规则,两个crate可以为同一个类型实现同一个trait,rust就不知道应该使用哪个实现了。

+

五、默认实现

+

trait可以拥有默认实现,当我们为某个类型实现trait时,可以选择重载或者保留原有的默认实现。默认实现的方法也可以调用trait中其他的方法,即使这些方法没有默认实现。如下示例代码

+
pub trait Summary {
+    fn summarize_author(&self) -> String;
+
+    fn summarize(&self) -> String {
+        format!("(Read more from {}...)", self.summarize_author())
+    }
+}
+
+pub struct NewArticle {
+    pub headline: String,
+    pub location: String,
+    pub author: String,
+    pub content: String,
+}
+
+impl Summary for NewArticle {
+    fn summarize_author(&self) -> String {
+        format!("@{} ", self.author)
+    }
+}
+
+

需要注意的是,我们无法在方法的重写实现里调用默认的实现。

+

六、使用trait作为参数

+

6.1 impl Trait

+

使用impl Trait的语法,可以适用于简单的函数传参情况。加入我们希望传入的参数实现了一个Summary这个trait,那么我们可以使用如下的参数声明方式

+
pub fn notify(item: impl Summary) {
+    println("breaking news! {}", item.summarize());
+}
+
+

那么我们在调用此函数的使用,可以传入Tweet,也可以传入NewArticle

+

6.2 Trait bound

+

使用Trait bound可以与复杂的情况,如下示例代码

+
pub fn notify<T: Summary>(item: T) {
+    println("breaking news! {}", item.summarize());
+}
+
+

如果宝航两个参数,使用Trait bound的写法如下

+
pub fn notify<T: Summary>(itema: T, item2: T) {
+    println("breaking news! {}", item1.summarize());
+}
+
+

实际上impl TraitTrait bound的语法糖。

+

6.3 使用+指定多个trait bound

+

如果使用impl Trait的方式,示例代码如下

+
use std::fmt::Display;
+
+pub fn notify(item: impl Summary + Display) {
+    println("breaking news! {}", item.summarize());
+}
+
+

使用Trait bound的写法如下

+
use std::fmt::Display;
+
+pub fn notify<T: Summary + Display>(item: T) {
+    println("breaking news! {}", item.summarize());
+}
+
+

6.4 在方法签名后面使用where子句

+

如果一个函数要求的变量需要实现过多的Trait,代码的可读性会变低,很不直观,如下示例代码

+
pub fn notify<T: Summary + Display, U: Clone + Debug>(a: T, b: U) -> String {
+    format!("breaking news! {}", a.summarize());
+}
+
+

我们可以使用where子句还简化Trait的约束,如下示例代码

+
pub fn notify<T, U>(a: T, b: U) -> String
+where T: Summary + Display,
+    U: Clone + Debug,
+{
+    format!("breaking news! {}", a.summarize());
+}
+
+

七、使用trait作为返回值类型

+

使用impl Trait声明返回的类型为Trait,如下示例代码

+
pub fn notify(flag: bool) -> impl Summary {
+    // ......
+}
+
+

使用imple Trait只能返回确定的同一种类型,返回可能不同类型的代码会报错,如下代码将报错

+
pub fn notify(flag: bool) -> impl Summary {
+    if flag {
+        NewArticle {
+            // ......
+        }
+    } else {
+        Tweet {
+            // ......
+        }
+    }
+}
+
+

八、使用PartialOrd实现数据比较

+

如下示例代码

+
use std::result;
+
+fn largest<T: PartialOrd +Clone>(list: &[T]) -> &T {
+    let mut largest = &list[0];
+
+    for item in list.iter() {
+        if item > &largest {
+            largest = item;
+        }
+    }
+
+    largest
+}
+
+fn main() {
+    let str_list = vec![String::from("hello"), String::from("world")];
+    let result = largest(&str_list);
+    println!("The largest word id {}", result);
+}
+
+

九、使用Trait Bound有条件的实现方法

+

在使用泛型类型参数的 impl 块上使用Trait bound,我们可以有条件的胃了实现特定 trait 的类型来实现。如下示例代码

+
use std::fmt::Display;
+
+struct Pair<T> {
+    x: T,
+    y: T,
+}
+
+// 所有Pair<T> 都拥有new函数
+impl <T> Pair<T>{
+    fn new(x: T, y: T) -> Self {
+        Self { x, y }
+    }
+}
+
+// 实现了Display 和 PartialOrd 的Pair<T> 猜拥有com_display函数
+impl <T: Display + PartialOrd> Pair<T> {
+    fn cmp_display(&self) {
+        if self.x >= self.y {
+            println!("the largest member is x = {}", self.x);
+        } else {
+            println!("the largest member is y = {}", self.y);
+        }
+    }
+}
+
+fn main(){
+
+}
+
+

也可以为实现其他Trait的类型有条件的实现某个Trait。为满足Trait Bound的所有类型上实现Trait叫做覆盖实现(Blanket implementations)。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/10_generics_trait_lifetime/03_lifetime/index.html b/10_generics_trait_lifetime/03_lifetime/index.html new file mode 100644 index 0000000..c23261f --- /dev/null +++ b/10_generics_trait_lifetime/03_lifetime/index.html @@ -0,0 +1,3440 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10.3   生命周期 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

生命周期

+

一、什么是生命周期?

+

Rust的每个引用都有生命周期,生命周期是可以理解是引用保持有效的作用域。大多数情况下,生命周期是隐式的、可被推断的。当引用的生命周期可能以不同的方式相互关联的时候,必须手动标注生命周期。

+

二、借用检查器

+

生命周期的主要目标是避免垂悬引用(dangling reference),如下示例的错误代码

+
fn main() {
+    {
+        let r;
+        {
+            let x = 5;
+            r = &x;
+        }
+        println!("r: {}", r);
+    }
+}
+
+

在最里层的大括号中,r指向了x,但是在最里层括号的外面,调用了println!,编译会报错,因为在最里层括号外面,x已经被释放了。rust为了保证程序的安全性,任何基于r的操作无法正常运行。

+

Rust编译器的借用检查器,用来比较引用的作用域从而判断所有的借用是否合法。如下加上注释的示例代码

+
fn main() {
+    {
+        let r;                  // ------+-- 'a
+        {                       //
+            let x = 5;          // ------+- 'b
+            r = &x;             // 
+        }                       // ------+- 'b
+        println!("r: {}", r);   //
+    }                           // ------+-- 'a
+}
+
+

可以看到x的生命周期比它的引用者r要短,所以rust不允许编译通过。要解决这个问题,必须让x的生命周期不小于r的生命周期。如下示例代码

+
fn main() {
+    let x = 5;
+    let r = &x;
+
+    println!("r: {}", r);
+}
+
+

在以上代码中,x的生命周期完全覆盖了它的调用者r的生命周期,所以能顺利通过编译。

+

三、函数中的泛型生命周期

+

我们先看下面一段错误的代码

+
fn main() {
+    let string1 = String::from("abcd");
+    let string2 = "xyz";
+
+    let result = longest(string1.as_string(), string2);
+
+    println!("The longest string is {}", result);
+}
+
+fn longest(x: &str, y: &str) -> &str {
+    if x.len() > y.len() {
+        x
+    } else {
+        y
+    }
+}
+
+

在以上代码中,longest函数的目的是返回两个字符串中比较长的值,并在main函数中打印出来。传入参数为xy两个字符串切片,返回值也是字符串切片。xy的生命周期可能不一样,而返回的值生命周期应当是确定的,但是函数签名里返回的值不知道借用自x还是y,所以代码编译时会产生错误。

+

我们先给出一个解决方案,如下示例代码

+
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
+    if x.len() > y.len() {
+        x
+    } else {
+        y
+    }
+}
+
+

在以上代码中'a代表生命周期泛型,同时指定了xy的生命周期为'a,同时返回的值生命周期也为'a,此时代码正常通过编译。不过,如果我们在这里说xy和返回的值的生命周期是一样的,这种说法是不太准确的。在这里,'a被叫做生命周期标注,在下文我们进一步探讨此概念。

+

四、生命周期标注语法

+

生命周期的标注不会改变引用的生命周期长度,当某个函数指定了泛型生命周期参数,函数可以接受带有生命周期的引用。生命周期的标注描述了多个引用的生命周期的关系,但不影响生命周期。

+

参数周期标注,遵循以下语法规则

+
    +
  • +

    参数名:以'开头;使用全小写且非常短,很多人使用'a

    +
  • +
  • +

    标注的位置:在引用的&符号后;使用空格将标注和引用类型分开

    +
  • +
+

生命周期标注例子如下

+
    +
  • &i32:一个普通引用
  • +
  • &'a i32:带有显式生命周期的引用
  • +
  • &'a mut i32:带有显式生命周期的可变引用
  • +
+

单个生命周期标注本省没有意义,当多个参数使用同一的标注时,代表多个参数必须拥有一样的生命周期。泛型生命周期参数声明在函数名和参数列表之间的<>里。到这里,我们在回顾*三*中的代码

+
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
+    if x.len() > y.len() {
+        x
+    } else {
+        y
+    }
+}
+
+

代码解读如下

+
    +
  • longest函数声明了一个'a生命周期
  • +
  • xx的生命周期必须大于等于'a
  • +
  • 返回值的生命周期也必须大于等于'a
  • +
+

当我们在函数体里面指明生命周期参数的时候,我们并没有改变传入的值和返回的值的生命周期,我们只是向*借用检查器*指出了一些用于检查非法调用的一些=约束而已。在上面的例子中,longest函数本身并不需要知道xy具体的生命周期,而只需要某个作用域可以被用来代替'a,同时满足函数的签名约束即可。

+

如果函数引用外部的代码或者被外部代码引用的时候,想单靠rust本身来确定参数和返回值的生命周期几乎是不可能的,这样的话函数所使用的生命周期可能在每次调用中都会发生变化,正式因此我们才需要手动对生命周期进行标注。

+

示例代码中的'a实际上代表xy重叠的那部分生命周期(也就是xy两个生命周期中较小的生命周期),而返回的值则在xy重叠的那部分生命周期是有效的。

+

五、深入理解生命周期

+

指定生命周期参数的方式依赖于函数所做的事情。当函数返回引用时,返回类型的生命周期参数需要与其中一个参数的生命周期匹配。

+

如果返回的引用没有指向任何参数,那么它只能引用函数内创建的值,该值在函数结束时就走出了作用域。如下示例代码的错误代码

+
fn main() {
+    let string1 = String::from("abcd");
+    let string2 = "xyz";
+
+    let result = longest(string1.as_string(), string2);
+
+    println!("The longest string is {}", result);
+}
+
+fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
+    let result = String::from("abc");
+    result.as_str()
+}
+
+

longest函数中,result.as_str()的结果是字符串切片,其实就是一个引用。此时编译将会出错,因为result.as_str()返回的值是引用longest函数内部所持有的数据。当内部数据离开作用域的时候,函数返回的引用仍然指向数据存储的内存,返回的引用给main函数中的result,并在main函数中进行使用,而该引用指向的内存,在longest之行结束的时候已经被释放了,这就发生了悬垂引用。rust不会允许此类不安全的操作,所以编译不通过。

+

如果我们想把函数内部的值直接返回出去,可以改成直接返回值,而不是返回引入,修改如下

+
fn longest<'a>(x: &'a str, y: &'a str) -> String {
+    let result = String::from("abc");
+    result
+}
+
+

此时相当于把所有权移交给函数的调用者,如果想清理内存,由函数的调用者来清理即可。

+

从根本上讲,生命周期语法是用来关联函数的不同参数以及返回值之间的生命周期,一旦它们之间取得了某种联系,rust将获得足够的信息来支持保证内存安全的操作,并且阻止那些可能会导致垂悬指针或者其他违反内存安全的行为。

+

六、Struct定义中的生命周期标注

+

Struct里可以包括自持有类型(如i32等)和引用类型,如果是引用类型,需要在每个引用上添加生命周期标注。结构体的引用字段,生命周期必须比结构体本身长。如下示例代码

+
struct ImportantExcerpt<'a> {
+    part: &'a str,
+}
+
+fn main() {
+    let novel String::from("Call me Ishmael. Some years ago...");
+
+    // 下面一行first_sentence的生命周期开始
+    let first_sentence = novel.split('.').next().expect("Could not found a '.'");
+
+    // 下面一行i的生命周期开始
+    let i = ImportantExcerpt {
+        part: first_sentence
+    };
+}
+
+

在以上代码中,可以看到字符切片引用的生命周期比struct ImportantExcerpt的生命周期长,所以编译通过。

+

七、生命周期的省略

+

7.1 生命周期省略规则

+

我们知道,每个引用都有生命周期,需要为使用生命周期的函数或struct指定生命周期参数。我们先来看一段在本系列切片代码

+
fn first_world(s: &str) -> &str {
+    let bytes = s.as_bytes();
+
+    for (i, &item) in bytes.iter().enumerate() {
+        if item == b' ' {
+            return &s[..i];
+        }
+    }
+    &s[..]
+}
+
+

在上面的函数中,返回的是一个字符串切片,但是没有标注任何生命周期,而仍然可以通过编译。这就涉及到rust语言发展的历史了,在rust的早期版本,这段代码是无法编译通过的,因为当时要求每个引用都必须有一个显式的生命周期。如下示例代码

+
fn first_world<'a>(s: &'a str) -> &'a str {
+    let bytes = s.as_bytes();
+
+    for (i, &item) in bytes.iter().enumerate() {
+        if item == b' ' {
+            return &s[..i];
+        }
+    }
+    &s[..]
+}
+
+

在编写很多代码之后,rust团队发现,在某些特定的情况下,rust程序员总是一遍又一遍地编译同样的生命周期标识,而且这些场景是可以预测的,而且有一些明确的模式,于是rust团队直接将这些模式写入了编译器代码,使得借用检查器在这些情况下可以自动地对生命周期进行推倒而无需显式地标注。了解这段历史很有必要,因为在未来可能有更多的模式添加到编译器中,也就是说在未来手动标注生命周期的情况可能会越来越少。

+

在Rust引用分析中所编入的模式成为**生命周期省略规则**,这些规则无需开发者来遵循,它们是一些特殊情况,由编译器来考虑,如果你的代码符合这些情况,那么就无需显式标注生命周期。

+

**生命周期省略规则**不会提供完整的推断,如果应用规则后,引用的生命周期仍然模糊不清,那边编译不会继续推断,将会产生错误。这时候的解决办法是:需要手动添加生命周期标注,表明引用间的相互关系。

+

7.2 输入、输出生命周期

+

如果生命周期出现在函数/方法的**参数**中,那么这个生命周期就叫输入生命周期;如果生命周期出现在函数/方法的**返回值**中,就叫输出生命周期。

+

7.3 生命周期省略的三个规则

+

7.3.1 规则定义

+

编译器使用3个规则在没有显示标注生命周期的情况下,来确定引用的生命周期。规则1应用于输入生命周期,规则2、3应用于输出生命周期。如果编译器应用完3个规则后,仍然无法确定生命周期的引用,则编译器会报错。这些规则适用于fn定义和impl块。

+
    +
  • +

    规则1: 每个应用类型的参数都有自己的生命周期,换句话说,单参数的函数它拥有一个生命周期参数,拥有两个参数的函数就拥有两个不同的生命周期参数,以此类推;

    +
  • +
  • +

    规则2: 如果只有一个输入生命周期参数,那么该生命周期被赋给所有的生命周期参数;

    +
  • +
  • +

    规则3: 如果有多个输入生命周期参数,但其中一个是&self&mut self(是方法),那么self的生命周期会被赋予给所有的输出生命周期参数。

    +
  • +
+

7.3.2 生命周期省略规则示例1

+

下面是第一个示例函数

+
fn first_world(s: &str) -> &str {
+    // .....
+}
+
+

根据第一个规则,rust编译器会为每个输入参数增加一个生命周期,此时变成如下代码

+
fn first_world<'a>(s: &'a str) -> &str {
+    // .....
+}
+
+

因为该函数只有一个输入生命周期参数时,这个生命周期被会赋给所有输出生命周期参数,此时变成如下代码

+
fn first_world<'a>(s: &'a str) -> &'a str {
+    // .....
+}
+
+

现在所有的引用都有了生命周期,所以编译器可以继续分析代码,而无需程序员手动标注这个函数签名里的生命周期了。

+

7.3.3 生命周期省略规则示例2

+

首先看下面的函数签名

+
fn longest(x: &str, y: &str) -> str {
+    // ......
+}
+
+

上面的函数中,适用第1个规则,每个参数都有自己的生命周期,此时变成如下代码

+
fn longest(x: &'a str, y: &'b str) -> str {
+    // ......
+}
+
+

由于现在这个函数拥有两个输入生命周期,所以第2条规则不适用;而且由于longest是个函数不是方法,没有self参数,所以第3条规则也不适用。所以应用完这三条规则之后,我们仍然无法计算返回类型的生命周期,所以编译器会报错。也就是说,当编译器使用了全部生命周期省略规则之后,却无法计算出函数签名中引用的生命周期,编译器就会报错。

+

八、方法中的生命周期标注

+

struct上使用生命周期实现方法,语法和泛型语法一样。在哪里声明和使用生命周期参数,依赖于生命周期参数是否和字段、方法的参数或返回值有关。在struct字段的生命周期名需要在impl后声明,在struct名后使用,这些生命周期是struct类型的一部分。

+

impl块内的方法签名中,引用必须绑定于struct字段引用的生命周期,或者引用是独立的也可以。生命周期省略规则经常使得方法中的生命周期标注不是必须的。如下示例代码

+
struct ImportantExcerpt<'a> {
+    part: &'a str,
+}
+
+impl<'a> ImportantExcerpt<'a> {
+    fn level(&self) -> i32 {
+        3
+    }
+
+    fn announce_and_return_part(&self, announcement: &str) -> &str {
+        println!("Attention please: {}", announcement);
+        self.part
+    }
+}
+
+

结构体字段生命周期的名总是被声明在impl之后,应用于结构体名ImportantExcerpt之后,不能忽略。

+

level方法的返回值是i32类型,不会引用任何数据,根据第1个省略规则,可以不为&self标注生命周期。

+

announce_and_return_part这个方法拥有两个参数,都是引用,返回类型也是一个引用。根据第1条省略规则,就会为这两个参数添加各自的生命周期,由于其中一个参数是&self,根据第3条省略规则,它的返回的引用的生命周期就是self的生命周期。

+

九、静态生命周期

+

'static是一个特殊的生命周期,它表示整个程序的生命周期。例如:所有字符串字面值都拥有'static生命周期,如下

+
let s:&'static str = "I have a static lifetime.";
+
+

实际上,所有字符串字面值的生命周期都是'static,即整个程序的生命周期。但我们要注意的是,在为引用指定'static生命周期之前要考虑:是否需要引用在整个生命周期内都存活。因为在大部分情况下,错误的原因都在于尝试创建一个悬垂引用,或者是可用生命周期不匹配。这个时候应该尝试去解决这个问题,而不是来指定'static生命周期。

+

十、泛型参数类型、Trait Bound、生命周期

+

下面是一个同时使用了泛型类型、Trait Bound、生命周期的综合示例

+
use std::fmt::Display;
+
+fn longest_with_an_announcement<'a, T>
+(x: &'a str, y: &'a str, ann: T) -> &'a str where T:Display {
+    println!("Announcement! {}", ann);
+
+    if x.len() > y.len() {
+        x
+    } else {
+        y
+    }
+}
+
+
+fn main() {
+    let str1 = "abc";
+    let str2 = "abcde";
+
+    let data = "initial contents";
+    let ann = data.to_string();
+
+    let longest_str = longest_with_an_announcement(str1, str2, ann);
+
+    println!("The longest str is {}", longest_str);
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/11_testing/01_how_to_test/index.html b/11_testing/01_how_to_test/index.html new file mode 100644 index 0000000..3e16e20 --- /dev/null +++ b/11_testing/01_how_to_test/index.html @@ -0,0 +1,3287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 11.1   如何编写测试 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

如何编写测试

+

一、什么是测试

+

在rust里,一个测试就是一个函数,他被用于测试其他的非测试代码的功能是否和预期一致。所以在测试的函数体里,通常会执行3个操作,如下

+
    +
  • 准备数据/状态
  • +
  • 运行被测试的代码
  • +
  • 断言(Assert)结构
  • +
+

二、解剖测试函数

+

测试函数需要使用test属性(attribute)进行标注,Attribute就是一段Rust代码的元数据,它只是对代码进行修饰,不会对代码原有的逻辑进行修改。在函数上加#[test],可以把函数变成测试函数。

+

当我们编写完成测试函数之后,使用cargo test命令运行所有的测试。运行过程中,Rust会构建一个Test Runner可执行文件,他会逐个运行标注了 test 的函数,并报告其运行是否成功。

+

当我们使用cargo创建library项目的时候,会生成一个 test module,里面有一个 test 函数。我们可以参照它来编写其他的测试函数,实际上,我们可以添加任意数量的test module或test函数。

+

三、测试示例

+

我们使用cargo new adder --lib即创建了一个库项目,可以看到默认生成的代码lib.rs如下

+
#[cfg(test)]
+mod tests {
+    #[test]
+    fn it_works() {
+        let result = 2 + 2;
+        assert_eq!(result, 4);
+    }
+}
+
+

it_works这个函数是测试函数,是因为该函数上面加了#[test]注释,并不是因为它在test模块中,test模块中还可以拥有普通的(非测试)函数。assert_eq!是一个断言宏。使用cargo test执行测试过程。

+

四、测试失败

+

符合测试失败扽条件如下

+
    +
  • 测试函数出现panic就表示测试失败
  • +
  • 每个测试运行在一个新线程,当主线程看到某线程挂掉,那个测试则标记为失败
  • +
+

五、断言(Asert)

+

5.1 使用assert!宏检查测试结果

+

assert!宏,来自标准库,用来确定某个状态是否为trueassert!宏接受一个布尔类型的参数,当参数为true时,测试通过,当参数为false时,则调用panic,测试失败。下面我们编写一个例子

+

首先定义一个矩形,如下

+
// 定义一个矩形
+#[derive(Debug)]
+pub struct Rectangle {
+    length: u32,
+    width: u32,
+}
+
+// 定义一个函数,用于判断该矩形是否能容纳另一个矩形
+impl Rectangle {
+    pub fn can_hold(&self, other: &Rectangle) -> bool {
+        self.length > other.length && self.width > other.width
+    }
+}
+
+

我们在同一个文件内添加测试模块,如下代码

+
#[cfg(test)]
+mod test {
+    // 以下代码表示导入外部模块所有内容
+    use super::*;
+
+    #[test]
+    fn larger_can_hold_smaller() {
+
+        // 准备数据:声明两个矩形
+        let larger = Rectangle {
+            length: 8,
+            width:7 ,
+        };
+
+        let smaller = Rectangle {
+            length: 5,
+            width: 1,
+        };
+
+        // 执行被测试代码
+        assert!(larger.can_hold(&smaller));
+    }
+}
+
+

5.2 使用assert_eq!和assert_ne!测试相等性

+

这两个宏都来自标准库,用于判断两个参数是否相等或不等。实际上,他们使用的就是==!=运算符。

+

如果断言失败,将自动打印出两个参数的值。使用debug格式打印参数,要求参数实现了PartialEqDebug Traits(所有的基本类型和标准库里大部分类型已实现了这两个Trait)。对于自定的结构体和枚举来说,需要自行实现这两个Trait。

+

下面我们写一个加2的函数,并使用测试代码进行测试,如下

+
pub fn add_two(a: i32) -> i32 {
+    a + 2
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn it_adds_two() {
+        assert_eq!(4, add_two(2));
+    }
+}
+
+

在rust里,用于断言是否相等的宏,第一个参数可以是期待的值,第二个参数可以是计算的值。他们可以对换过来,即第一个参数是计算的值,第二个参数是期待的值。所以我们把rust断言是否相等的宏的第一个参数叫做坐值,第二个参数叫做右值

+

六、自定义的错误信息

+

可以向assert!assert_eq!assert_ne!添加可选的自定义错误信息。如果添加了自定的错误信息,这些自定义的错误信息和失败消息都会打印出来。

+

assert!: 第1个参数必填,自定义消息作为第2个参数。

+

assert_eq!assert_ne!: 前2个参数必填,自定义消息作为第3个参数。

+

自定义消息参数会被传递给format!宏,可以使用{}占位符。下面是一个示例

+
pub fn greeting(name: &str) -> String {
+    format!("Hello!")
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn greeting_contains_name() {
+        let  result = greeting("Carol");
+        assert!(result.contains("Carol"), "Greeting didn't contain name, value was '{}'", result);
+    }
+}
+
+

以上的测试将会失败,并且提示出我们的自定义错误信息。

+

七、验证错误处理的情况

+

7.1 使用should_panic更精确

+

测试出了验证代码的返回值是否正确,还需验证代码是否如预期的处理了发生错误的情况。如我们可以验证代码在特定情况下是否发生了panic。此时我们需要使用should_panic属性,如果使用了该属性,那么函数如果发生panic则测试通过,函数如果没有panic则测试失败。如果示例

+
impl Guess {
+    pub fn new(value: u32) -> Guess {
+        if value < 1 || value > 100 {
+            panic("Guess value must be between 1 and 100, got {}.", value);
+        }
+
+        Guess { value }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    #[should_panic]
+    fn greater_than_100() {
+        Guess::new(200);
+    }
+}
+
+

使用should_panic标注了测试函数应该出现panic,实际上函数也出现了panic,所以测试通过。

+

7.2 让should_panic更精确

+

为should_panic属性添加一个可选的expected参数,这样的话测试过程将会检查失败的消息中是否包含所指定的文字。如下示例

+
impl Guess {
+    pub fn new(value: u32) -> Guess {
+        if value < 1 {
+            panic("Guess value must be between 1 and 100, got {}.", value);
+        } else if value > 100 {
+            panic("Guess value must be less than or equal to 100, got {}", value);
+        }
+
+        Guess { value }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    #[should_panic(expected = "Guess value must be less than or equal to 100")]
+    fn greater_than_100() {
+        Guess::new(200);
+    }
+}
+
+

在上面的示例中,将会执行到value > 100的逻辑产生panic,并且panic的错误信息完全包含了expected参数,所以测试通过。

+

八、在测试中使用Result

+

到目前为止,测试运行失败,都是因为panic,但导致测试失败,不仅仅是因为panic,还可以使用Result<T, E>枚举来达到同样目的。即在编写测试的时候无需panic,可以使用Result <T, E>作为返回类型编写测试。如果返回OK,测试通过,如果返回Err则测试失败。如下例子

+
#[cfg(test)]
+mod tests {
+    #[test]
+
+    fn it_works -> Result<(), String> {
+        if 2 + 2 == 4 {
+            Ok(())
+        } else {
+            Err(String::from("two plus two does not equal four"))
+        }
+    }
+}
+
+

在以上代码中,将会执行2 + 2 == 4的逻辑,所以测试通过。需要注意的是,不要在Result<T, E>编写的测试上标注#[should_panic]。因为在运行失败的时候,会执行返回Err,不回发生panic。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/11_testing/02_control_test_running/index.html b/11_testing/02_control_test_running/index.html new file mode 100644 index 0000000..744bc2a --- /dev/null +++ b/11_testing/02_control_test_running/index.html @@ -0,0 +1,3051 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 11.2   控制测试运行 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+ +
+ + + +
+
+ + + + + + + + + + + + +

控制测试运行

+

一、概述

+

cargo testcargo run命令类型,cargo test会在测试模式下编译代码,并生成一个二进制可执行文件,该二进制可执行文件用于测试。我们可以通过添加参数来改变cargo test的行为,如果不添加任务参数,rust将会执行默认的测试行为。默认行为如下

+
    +
  • 并行测试
  • +
  • 执行所有测试
  • +
  • 捕获(不显示)所有输出,只会输出测试失败的相关输出,使读取与测试结果相关的输出更容易
  • +
+

命令行参数如下

+
    +
  • 针对cargo test的参数:紧跟在 cargo test 之后,使用cargo test --help查看所有参数列表
  • +
  • 针对测试可执行程序:放在 -- 之后,使用cargo test -- --help查看所有参数列表
  • +
+

二、并行运行测试

+

在默认情况下,使用多线程并行运行多个测试。这样运行更快,我们更早得的到反馈。但由于是并行运行,所以我们要确保运行的过程中不回相互依赖,而且不依赖某个共享的状态(环境、工作目录、环境变量等等)。因为如果两测试如果共享一个状态,其中一个测试运行完成的之后把某个状态修改了,将会影响另一个测试。

+

如果我们不想并行运行测试,或者希望精确控制测试时,所启用的线程的数量,那么就可以传递该参数。该参数传递给二进制文件,可以使用--test-threads参数,后面跟着线程的数量,例如:cargo test -- --test-threads=1

+

这样的话,如果执行多个测试,相比默认情况下它会耗费更多的时间,但是它试顺序执行的。如果多个测试使用共享状态,在该模式下运行,出现状态干扰的情况将被减少。

+

三、显式函数的输出

+

默认情况下,如果测试通过,Rust的test库会捕获所有打印到标准输出的内容,即所有打印到标准输出的内容不会显示了。例如:如果被测试的代码中用到了println!。如果测试通过,将不会在终端看到println!打印的内容。如果测试失败,将会看到println!打印的内容和失败信息。

+

如果我们想让测试通过的时候也把println!的输出内容也打印出来,就需要加一个参数--shou-output,最终执行命令为cargo test -- --show-output

+

四、按名称运行测试的子集

+

选择运行测试是将测试的名称(一个或多个)作为cargo test的参数。如下例子

+
pub fn add_two(a: i32) -> i32 {
+    a + 2
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn add_two_and_two() {
+        assert_eq!(4, add_two(2));
+    }
+
+    #[test]
+    fn add_three_and_two() {
+        assert_eq!(5, add_two(3));
+    }
+
+    #[test]
+    fn one_hundred() {
+        assert_eq!(102, add_two(100));
+    }
+}
+
+

在以上代码中,一共包含了三个测试,如果我们直接使用cargo test命令,将会运行所有的测试。如果只想运行一个测试,我们直接把测试的名称作为cargo test的参数即可。如cargo test one_hundred

+

但是,有时候我们需要运行多个测试,则需要指定测试名(模块名也可以)的一部分,这样任何匹配这个名称都会被执行。如在上列示例代码中,我们使用cargo test add命令将会执行前两个测试函数,我们使用cargo test tests命令将会执行tests模块中所有的测试函数。

+

五、忽略测试

+

我们可以通过参数实现忽略某些测试,运行剩余的测试。有些情况下,我们不希望运行一些耗时的测试,则可以通过ignore属性来对要忽略的测试函数进行标记,如下示例

+
#[cfg(test)]
+mod tests {
+    #[test]
+    fn it_works() {
+        assert_eq!(4, 2 + 2);
+    }
+
+    #[test]
+    #[ignore]
+    fn expensive_test() {
+        assert_eq!(5, 1 + 1 + 1 + 1 + 1);
+    }
+}
+
+

通过默认的测试命令,rust将会忽略有ignore标记的测试函数,如果我们希望单独运行ignore标记的测试函数,则需添加--ignored参数,最终执行命令为cargo test -- --ignored

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/11_testing/03_test_structure/index.html b/11_testing/03_test_structure/index.html new file mode 100644 index 0000000..84763dd --- /dev/null +++ b/11_testing/03_test_structure/index.html @@ -0,0 +1,3156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 11.3   测试的组织结构 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

测试的组织结构

+

一、测试的分类

+

Rust对测试的分类:

+

1.1 单元测试

+

测试范围比较小,同时比较专注;一次对一个模块进行隔离的测试;可以测试private接口。

+

1.2 集成测试

+

完全位于代码库之外,和其他外部代码一样使用你的代码;集成测试可以访问public接口;可能在每个测试中使用到多个模块。

+

二、单元测试与集成测试的区别

+

单元测试的目的是将一小段代码单独隔离出来,从而迅速地确定这段代码的功能是否符合预期。我们通常把单元测试和被测试代码都放在src目录下的同一个文件中,同时约定每个源代码文件都要建立一个test模块放放测试函数,并使用#[cfg(test)]来标注测试模块。使用#[cfg(test)]标注之后,那么只有在运行cargo test命令的时候,测试模块的内容才会被编译并且运行,运行cargo build则不会。

+

集成测试在不同的目录中,它不需要使用#[cfg(test)]进行标注。

+

说到这里,我们说一下cfg,实际上是配置的英文单词configuration的缩写,使用cfg标注的含义是:告诉rust下面条目只有在指定配置选项下才被包含。如:配置选项test,由rust提供,用来编译和运行测试,只有使用cargo test才会编译代码,包括模块中的helper函数和#[test]标注的函数。

+

三、测试私有函数

+

Rust允许测试私有函数,如下示例代码

+
pub fn add_two(a: i32) -> i32 {
+    internal_adder(a, 2)
+}
+
+fn internal_adder(a: i32, b: i32) -> i32 {
+    a + b
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn it_works() {
+        assert_eq(4, internal_adder(2, 2));
+    }
+}
+
+

四、集成测试

+

4.1 集成测试概述

+

在rust里,集成测试完全位于被测试库的外部。集成测试调用库的方式,和其他代码调用库的方式是一样的。同时也意味着,集成测试纸能调用被测试库对外开放的API。集成测试的目的是:测试被测试库的多个部分是否能正确的一起工作。

+

能够正常工作的代码代码,他们集成在一起运行时,也可能会发生各种问题,所以集成测试的覆盖率很重要的指标。

+

4.2 测试过程

+

为了创建集成测试,我们需要创建一个tests目录,这个目录和src目录时并列的,这个目录也比较特殊,cargo会自动在这里目录下寻找测试文件。我们可以在tests目录下创建任意多的测试文件,而该目录下的每个测试文件都是单独的一个crate

+

在执行测试后的输出结果中,每个集成测试文件都会有一个单独的区域,同时每个测试函数都会有一个单独的行。通常在执行集成测试的时候,我们都会有选择地执行。

+

在测试文件中,需要将被测试测试库导入,每个测试文件无需标注#[cfg(test)],因为tests目录会被cargo特殊对待。在执行cargo test时,tests目录下的文件将会被编译。

+

4.3 运行指定的集成测试

+

运行一个特定的集成测试:cargo test 函数名

+

运行某个测试文件内的所有测试:cargo test --test 文件名

+

4.4 集成测试中的自模块

+

tests目录下每个文件会被编译成单独的crate,这些文件不会共享行为(与src下的文件规则不同)。如果我们想创建一个helper函数,并且在多个测试文件中使用它,我们可以在tests目录下创建一个空目录,目录名可以叫common,并把helper函数所在的文件放在该目录中。

+

rust在测试的过程中,不会把tests目录下的子目录的文件当作测试文件,该目录只是一个普通的模块而已,现在里面的函数将可以应用于不同的集成测试文件中。如下示例的集成测试代码

+
// adder为项目名,代表把项目中的所有模块导入到测试文件中
+use adder;
+
+// 使用tests目录下的common模块,common模块对应common目录
+mod common;
+
+#[test]
+fn it_adds_two() {
+    // 调用common模块下的函数
+    common::setup();
+    // 调用项目中的函数
+    assert_eq!(4, adder::add_two(2));
+}
+
+

4.5 针对binary crate的集成测试

+

如果项目时binary crate,只含有src/main.rs没有src/lib.rs,这时候不能在tests目录下创建集成测试,集成测试也无法把main.rs的函数导入到作用域。因为只有library crate才能暴露函数给其他crate使用,而binary crate意味着要独立运行。

+

rust项目通常把逻辑都放在src/lib.rs里,而在src/main.rs只有简单的调用,包含少量的胶水代码。这样在集成测试的时候,就可以把项目视为library,通过使用use关键字来访问项目中核心的逻辑代码。当核心的逻辑代码没问题了,通常核心功能也没有问题。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/12_cli/01_io_example_project/index.html b/12_cli/01_io_example_project/index.html new file mode 100644 index 0000000..9619494 --- /dev/null +++ b/12_cli/01_io_example_project/index.html @@ -0,0 +1,2989 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 12.1   IO示例项目 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

IO示例项目

+

一、概述

+

本节编写一个命令行程序的项目实例,项目源代码在文档目录下的src/minigrep目录。开发顺序如下

+
    +
  • 接收命令行参数
  • +
  • 读取文件
  • +
  • 重构:改进模块和错误处理
  • +
  • 使用TDD(测试驱动开发)开发库功能
  • +
  • 使用环境变量
  • +
  • 将错误消息写入标准错误而不是标准输出
  • +
+

二、测试驱动开发

+

TDD(Test-Driven Development),即测试驱动开发,一般遵循以下开发步骤

+
    +
  • 编写一个会失败的测试,运行该测试,确保它是按照预期的原因导致失败
  • +
  • 编写或修改足够的代码,让测试通过
  • +
  • 重构刚刚添加或修改的代码,在重构期间,确保测试始终会通过
  • +
  • 返回最开始的步骤,继续下一轮(下一个功能)的开发
  • +
+

测试驱动开发,只是众多软件开发方式中的一种,但他对代码的设计工作起到指导和帮助的作用。先编写测试,然后再编写能够通过测试的代码,也有助于保证较高的测试覆盖率。

+

三、标准输出 VS 标准错误

+

大多数终端提供两种输出,如下:

+
    +
  • +

    标准输出:stdout,rust可以通过println!进行终端输出,一般的信息应该输出到标准输出中

    +
  • +
  • +

    标准错误:stderr,rust可以通过eprintln!进行终端输出,错误的信息应该输出到标准错误里

    +
  • +
+

这种区分有好处,如:我们可以是正常的输出重定向到一个输出文件里,而错误信息输出到屏幕上,达到过滤输出内容的效果,反之亦然。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/13_functional_programming/01_closure/index.html b/13_functional_programming/01_closure/index.html new file mode 100644 index 0000000..a4725d4 --- /dev/null +++ b/13_functional_programming/01_closure/index.html @@ -0,0 +1,3402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 13.1   闭包 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

闭包

+

一、什么是闭包

+

1.1 闭包的特性

+

闭包:可以捕获其所在环境的匿名函数。闭包具体以下特性

+
    +
  • 闭包是个匿名函数
  • +
  • 可以保存为变量,或者作为参数传给另外一个函数,或者作为另外一个的返回值
  • +
  • 可以在某个地方创建闭包,然后在另一个上下文调用闭包来完成运算
  • +
  • 可从其定义的作用域捕获值
  • +
+

1.2 生成自定义运动计划程序

+

下面有一个程序示例,该程序的算法逻辑我们不关心,重点是算法中的计算过程需要几秒钟的时间。而我们优化的目标是不让用户发生不必要的等待,仅在必要时调用该算法,只调用一次。程序如下

+
use std::thread;
+use std::time::Duration;
+
+fn main() {
+    let simulated_user_specified_value = 10;
+    let simulated_random_number = 7;
+
+    generate_workout(simulated_user_specified_value, simulated_random_number);
+}
+
+// 生成运动计划
+fn simulated_expensive_calculation(intensive: u32) -> u32 {
+    println!("calculating slowly ...");
+    thread::sleep(Duration::from_secs(2));
+    intensive
+}
+
+/**
+ * 参数1:强度
+ * 参数2:随机数
+ */
+fn generate_workout(intensity: u32, random_number: u32) {
+    if intensity < 25 {
+        // 在以下代码中,调用了两次耗时函数,可以优化
+        println!(
+            "Today, do {} pushups",
+            simulated_expensive_calculation(intensity)
+        );
+
+        println!(
+            "Next, do {} situps!",
+            simulated_expensive_calculation(intensity)
+        );
+    } else {
+        if random_number == 3 {
+            println!("Take a break today! Remember to stay hydrated")
+        } else {
+            println!(
+                "Today, run for {} minutes!",
+                simulated_expensive_calculation(intensity)
+            );
+        }
+    }
+}
+
+

我们先进行第一测优化,优化后的generate_workout函数如下

+
fn generate_workout(intensity: u32, random_number: u32) {
+
+    let expensive_result = simulated_expensive_calculation(intensity);
+
+    if intensity < 25 {
+        println!(
+            "Today, do {} pushups",
+            expensive_result
+        );
+
+        println!(
+            "Next, do {} situps!",
+            expensive_result
+        );
+    } else {
+        if random_number == 3 {
+            println!("Take a break today! Remember to stay hydrated")
+        } else {
+            println!(
+                "Today, run for {} minutes!",
+                expensive_result
+            );
+        }
+    }
+}
+
+

此时,针对intensity < 25的情况,耗时的函数不再需要调用两次,调用是在if判断之前执行的。但是此时也引入了一个其他的问题,在else的逻辑里,当随机数等于3时,是不需要调用耗时函数的。而现在的写法是无论任何情况下都会调用函数,这显得有点浪费了。

+

1.3 使用闭包

+

而我们真正希望的是函数定义在一个地方,在需要结果的时候才执行相关代码,这正是闭包的用武之地。下面我们使用闭包来解决该问题。使用闭包优化之后的generate_workout函数如下

+
fn generate_workout(intensity: u32, random_number: u32) {
+    // 定义一个闭包,使用一个变量来接收
+    let expensitive_closure = |num| {
+        println!("calculating slowly ...");
+        thread::sleep(Duration::from_secs(2));
+        num
+    };
+
+    if intensity < 25 {
+        println!("Today, do {} pushups", expensitive_closure(intensity));
+        println!("Next, do {} situps!", expensitive_closure(intensity));
+    } else {
+        if random_number == 3 {
+            println!("Take a break today! Remember to stay hydrated")
+        } else {
+            println!("Today, run for {} minutes!", expensitive_closure(intensity));
+        }
+    }
+}
+
+

二、闭包的类型推断和标注

+

2.1 闭包的类型推断

+

闭包不强制要求标注参数和返回值类型。而函数强制标注参数和返回值类型,因为函数是暴露给用户的接口的一部分,严格定义接口有助于所有人对参数和返回值的类型取得共识。但是闭包并不会被用于这样的暴露接口,闭包会被存在变量里,在使用的时候不需要命名,也不会暴露给代码库的用户,所以闭包并不强制要求标注参数和返回值类型。

+

闭包通常很短小,只在狭小的上下文中工作,编译器通常能可靠地推断出类型。如果我们明确标注出参数类型也不是不可以,如下示例代码

+
let expensitive_closure = |num: u32| {
+    println!("calculating slowly ...");
+    thread::sleep(Duration::from_secs(2));
+    num
+};
+
+

在上面的闭包示例代码中,我们手动添加了参数类型标注,如果我们不添加也完全没有问题,因为rust能自动推断出类型。

+

2.2 函数和闭包的定义语法

+

首先我们看一个简单的函数示例

+
fn add_one_v1 (x: u32) -> u32 { x + 1 }
+
+

我们可以使用闭包的方式来实现函数的功能,如下示例代码

+
let add_one_v2 = |x: u32| -> u32 { x + 1 };
+
+

由于闭包可以省略参数类型标注,可以优化成以下的闭包代码

+
let add_one_v2 = |x| { x + 1 };
+
+

又由于闭包中只有一个表达式,所以可以省略大括号,最终优化成以下代码

+
let add_one_v2 = |x|  x + 1;
+
+

2.3 注意

+

闭包的定义最终只会为参数/返回值推断出唯一具体类型,如下示例代码

+
fn main() {
+    let example_closure = |x| x;
+
+    // 执行到以下的一行代码,将闭包参数绑定为String类型
+    let s = example_closure(String::from("hello"));
+
+    // 在下面一行,传了int类型,将会导致编译报错,因为rust已将闭包参数推断其为String类型
+    let n = example_closure(5);
+}
+
+

三、使用泛型参数和Fn Trait来存储闭包

+

在“一”中的代码还有一个问题,当执行intensity < 25的逻辑的时候,会执行两次耗时函数。我们继续来优化“一”中的代码。我们创建一个struct,把闭包和调用结果存到结构体中。则做到:只会在需要结果时才执行闭包,并切可以缓存结果。这种模式通常叫做记忆话(memozation)或延迟计算(lazy evaluation)。需要注意的条目如下

+
    +
  • struct的定义需要知道所有字段的类型,则需指明闭包的类型
  • +
  • 每个闭包实例都有自己唯一的匿名类型,即使两个闭包签名完全一样
  • +
+

为了在结构体里使用闭包,我们需要使用泛型和Trait Bound约束。在标准库中,提供了一系列的Fn Trait,所有的闭包都至少实现了以下的Trait之一

+
    +
  • Fn
  • +
  • FnMut
  • +
  • FnOnce
  • +
+

下面我们来优化“一”中的代码,下面是优化的过程

+

首先定义一个结构体

+
struct Cacher<T> 
+where T: Fn(u32) -> u32
+{
+    calculation: T,
+    value: Option<u32>
+}
+
+

我们把闭包叫做calculation,其次把要缓存的值叫做value,在运行闭包之前,value的值是null,当运行闭包之后,就会把闭包的结果存放到value的字段中,这就是缓存了。如果以后再次请求这个闭包的结果,有值则直接取即可。

+

现在我们为结构体添加一个关联函数,再实现取值的方法,如下示例代码

+
impl<T> Cacher<T>
+where
+    T: Fn(u32) -> u32,
+{
+    fn new(calculation: T) -> Cacher<T> {
+        Cacher {
+            calculation,
+            value: None,
+        }
+    }
+
+    fn value(&mut self, arg: u32) -> u32 {
+        match self.value {
+            Some(v) => v,
+            None => {
+                let v = (self.calculation)(arg);
+                self.value = Some(v);
+                v
+            }
+        }
+    }
+}
+
+

最后修改generate_workout函数如下

+
fn generate_workout(intensity: u32, random_number: u32) {
+    // 定义一个闭包,使用一个变量来接收
+    let mut expensitive_closure = Cacher::new(|num| {
+        println!("calculating slowly ...");
+        thread::sleep(Duration::from_secs(2));
+        num
+    });
+
+
+
+    if intensity < 25 {
+        println!("Today, do {} pushups", expensitive_closure.value(intensity));
+        println!("Next, do {} situps!", expensitive_closure.value(intensity));
+    } else {
+        if random_number == 3 {
+            println!("Take a break today! Remember to stay hydrated")
+        } else {
+            println!("Today, run for {} minutes!", expensitive_closure.value(intensity));
+        }
+    }
+}
+
+

四、使用缓存器(Cacher)实现的限制

+

在“三”的示例中,缓存器存在一些问题,下面我们将探讨解决方案

+
    +
  • +

    Cacher实例假定针对不同的参数arg,value方法总汇得到同样的值:可以使用HashMap代替单个值,key为arg参数,value为执行闭包的结果。

    +
  • +
  • +

    只能接收一个u32类型的参数和u32类型的返回值:引入两个或两个以上的泛型参数即可。

    +
  • +
+

五、使用闭包捕获环境

+

5.1 使用闭包能捕获环境

+

闭包可以捕获他们所在的环境,在上面的闭包示例中,我们可以把它当作内部的匿名函数来使用。除此之外,闭包还有函数所不具备的功能,闭包可以访问定义它的作用域内的变量,而普通函数则不能。如下示例代码

+
fn main() {
+    let x = 4;
+    let euqal_to_x = |z| z == x;
+
+    let y = 4;
+
+    assert!(equal_to_x(y));
+}
+
+

但是捕获他们所在的环境是需要内存开销的,而大部分请求我们不需要捕获环境,也不想为它产生额外的内存开销。所以函数就永远不会产生这类的开销。

+

5.2 闭包从所在环境捕获值的方式

+

与函数获取参数的三种方式一样,如下示例代码

+
    +
  • 取得所有权:FnOnce
  • +
  • 可变借用:FnMut
  • +
  • 不可变借用:Fn
  • +
+

当我们创建闭包时,通过闭包对环境的使用,Rust可以推断出具体使用哪个Trait。所有的闭包都实现了FnOnce;那些没有移动捕获变量的闭包实现了FnMut;那些无需可变访问捕获变量的闭包实现了Fn。实际上追踪其底层原理还是比较复杂,我们现在可以这么理解即可:所有实现了FnMut的闭包都实现了Fn,所有的闭包都实现了FnOnce。

+

5.3 move关键字

+

在参数列表前使用move关键字,可以强制闭包取得它所使用的环境值的所有权。用于:当闭包传递给新线程以移动数据使其归新线程所有时。如下示例的错误代码

+
fn main() {
+    let x = vec![1, 2, 3];
+    let equal_to_x = move |z| z == x;
+
+    // 在上面的代码中,x 的所有权已经被移动到了闭包里,下面这行代码继续借用,将会发生错误
+    println!("can't use x here: {:?}", x);
+}
+
+

当指定Fn Trait bound之一时,首先用Fn,基于闭包里的情况,如果需要FnOnce或FnMut,编译器会再告诉你。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/13_functional_programming/02_iterator/index.html b/13_functional_programming/02_iterator/index.html new file mode 100644 index 0000000..8e0c364 --- /dev/null +++ b/13_functional_programming/02_iterator/index.html @@ -0,0 +1,3249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 13.2   迭代器 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

迭代器

+

一、什么是迭代器?

+

迭代器模式是对一系列项执行某些任务。在这个过程中迭代器负责遍历每个项目,并确定序列(遍历)何时完成。

+

Rust里的迭代器是惰性的,除非调用消费迭代器的方法,否则迭代器本身没有任何效果。可以这么理解:迭代器在不使用它的时候,它什么都不做。当我们使用了某些可以消耗迭代器的方法的时候,这时候迭代器才真正起到了迭代器的作用。我们先来看个示例:

+
fn main() {
+    let v1 = vec![1, 2, 3];
+    let v1_iter = v1.iter();
+}
+
+

上面的示例代码中,v1使用调用iter方法得到了一个迭代器,但是迭代器没有使用,那么迭代器不产生任何效果。我们可以对迭代器进行遍历,如下示例代码

+
for val in v1_iter {
+    println!("Got: {}", val);
+}
+
+

上面的代码相当于迭代器里的每一个元素用在了循环里,具体的用户就是把它打印出来。

+

二、Iterator trait和next方法

+

所有的迭代器都实现了 Iterator trait,其定义于标准库,大致如下

+
pub trait Iterator {
+    type Item;
+
+    fn next(&mut self) -> Option<Self::Item>;
+
+    // methods with default implementations elided
+}
+
+

type Item和Self::Item定义了与此trait的关联类型(前面还没提到关联类型)。现在我们只需要知道:实现Iterator trait需要你定义一个Item类型,它用于next方法的返回类型(迭代器的返回类型)。

+

所以Iterator Trait仅要求实现一个方法:next。next方法每次返回迭代器中的一项,即迭代器中的一个元素,返回的结果包裹在Some里,迭代结束的时候,返回None。在实际使用中,我们可以直接调用next方法。如下示例的测试代码

+
#[cfg(test)]
+mod tests {
+    #[test]
+    fn iterator_demonstration() {
+        let v1 = vec![1, 2, 3];
+        let mut v1_iter = v1.iter();
+
+        assert_eq!(v1_iter.next(), Some(&1));
+        assert_eq!(v1_iter.next(), Some(&2));
+        assert_eq!(v1_iter.next(), Some(&3));
+    }
+}
+
+

三、几个迭代方法

+

迭代器存在几个迭代方法,分别如下

+
    +
  • +

    iter方法:在不可变引用(指的是元素不可变引用,并不是迭代器本身)上创建迭代器。

    +
  • +
  • +

    into_iter方法:创建迭代器会获得所有权。

    +
  • +
  • +

    iter_mut方法:迭代可变引用。

    +
  • +
+

四、消耗和产生迭代器的方法

+

4.1 消耗型迭代器

+

在标准库中,Iterator trait有一些默认实现的方法,其中有一部分方法会调用next方法。所以如果我们想实现 Iterator trait时就必须实现next方法。我们把调用next的方法叫做“消耗型适配器”,最终调用next方法的过程会把迭代器耗尽。

+

例如:sum方法会消耗迭代器,并且取得迭代器的所有权,sum方法就是反复调用next来编译元素,每次迭代,把当前元素添加到一个总和里,迭代结束,返回总和。如下示例的测试代码

+
#[cfg(test)]
+mod tests {
+    #[test]
+    fn iterator_sum() {
+        let v1 = vec![1, 2, 3];
+        let v1_iter = v1.iter();
+
+        let total: i32 = v1_iter.sum();
+
+        assert_eq!(total, 6);
+    }
+}
+
+

4.2 产生其他迭代器的方法

+

定义在 Iterator trait 上的另一个方法叫做“迭代器适配器”,它们的作用是把迭代器转换为不同类型的迭代器。可以通过链式调用使用多个迭代器来执行复杂的操作,这种调用可读性高。

+

例如:map方法,接收一个闭包作为参数,这个闭包作用域迭代器的每个元素,他把当前迭代器的元素转化为另外一个元素,然后这些另外的元素就组成了一个新的迭代器。但要注意的是,迭代器是惰性的,如果不消耗它们,他们将什么都不会操作,如下示例的测试代码

+
#[cfg(test)]
+mod tests {
+    #[test]
+    fn iterator_sum() {
+        let v1 = vec![1, 2, 3];
+        v1.iter().map(|x| x+1);
+    }
+}
+
+

上面的代码中,并不会对v1中的3个元素进行加1操作。如果我们此时调用一个消耗型的collect方法,它会把所有的结果收集到一个某个数据类型的集合里。至于是收集的集合是什么类型,我们使用<_>标注让rust推断即可,如下示例代码

+
#[cfg(test)]
+mod tests {
+    #[test]
+    fn iterator_sum() {
+        let v1 = vec![1, 2, 3];
+        let v2: Vec<_> = v1.iter().map(|x| x+1).collect();
+
+        assert_eq!(v2, vec![2, 3, 4]);
+    }
+}
+
+

4.3 总结

+

通过“4.1”、“4.2”中的示例,总结如下

+
    +
  • “消耗型适配器”方法:sum、collect
  • +
  • “迭代器适配器”方法:map
  • +
+

五、使用闭包捕获环境

+

我们可以通过filter方法来捕获环境,该方法接收一个闭包作为参数。这个闭包在遍历迭代器的每个元素时,都会返回bool类型。如果闭包返回true,则当前元素将会包含在filter产生的迭代器中;如果闭包返回false,当前元素将不会包含在filter产生的迭代器中。如下示例代码

+
#[derive(PartialEq, Debug)]
+struct Shoe {
+    size: u32,
+    style: String,
+}
+
+fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
+    shoes.into_iter().filter(|x| x.size == shoe_size).collect()
+}
+
+#[test]
+fn filter_bu_size() {
+    let shoes = vec![
+        Shoe {
+            size: 10,
+            style: String::from("sneaker"),
+        },
+        Shoe {
+            size: 13,
+            style: String::from("sandal"),
+        },
+        Shoe {
+            size: 10,
+            style: String::from("hoot"),
+        },
+    ];
+
+    let in_my_size = shoes_in_my_size(shoes, 10);
+
+    assert_eq!(
+        in_my_size,
+        vec![
+            Shoe {
+                size: 10,
+                style: String::from("sneaker"),
+            },
+            Shoe {
+                size: 10,
+                style: String::from("hoot"),
+            },
+        ]    
+    );
+}
+
+

六、创建自定义的迭代器

+

使用Iterator trait来创建自定义迭代器,我们只需要执行一步操作,实现next方法即可。我们下面创建一个迭代器,该迭代器能从1遍历到5,如下示例代码

+
struct Counter {
+    counter: u32,
+}
+
+impl Counter {
+    fn new() -> Counter {
+        Counter { counter: 0 }
+    }
+}
+
+impl Iterator for Counter {
+    type Item = u32;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.counter < 5 {
+            self.counter += 1;
+            Some(self.counter)
+        } else {
+            None
+        }
+    }
+}
+
+#[test]
+fn calling_next_directly() {
+    let mut counter = Counter::new();
+
+    assert_eq!(counter.next(), Some(1));
+    assert_eq!(counter.next(), Some(2));
+    assert_eq!(counter.next(), Some(3));
+    assert_eq!(counter.next(), Some(4));
+    assert_eq!(counter.next(), Some(5));
+    assert_eq!(counter.next(), None);
+}
+
+

接下来我们来扩展一下,使用其他的Iterator的方法。上面的迭代器,从1迭代到5,我们想让这样的两个迭代器,他们的每队元素进行相乘。第一个迭代器元素从1到5,第二个迭代器从2开始,到5结束。产生新的迭代器必须能够被3整除,所以需要跑过滤,过滤之后,我们把剩余的元素求和并返回。实现逻辑如下代码

+
#[test]
+fn using_other_iterator_trait_methods() {
+    let sum: u32 = Counter::new()
+        .zip(Counter::new().skip(1))
+        .map(|(a, b)| a * b)
+        .filter(|x| x % 3 == 0)
+        .sum();
+
+    assert_eq!(18, sum);
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/13_functional_programming/03_io_with_iterator/index.html b/13_functional_programming/03_io_with_iterator/index.html new file mode 100644 index 0000000..2937383 --- /dev/null +++ b/13_functional_programming/03_io_with_iterator/index.html @@ -0,0 +1,3052 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 13.3   使用迭代器优化IO项目 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

使用迭代器优化IO项目

+

在前面两节,我们开发了一下给予IO操作的示例项目,我们现在来优化之前的项目。本节对应的代码目录src/s43_minigrep_optmize

+

一、使用迭代器优化Config结构体方法

+

回顾之前的一个代码片段,在Config的new函数中包含如下代码

+
impl Config {
+    pub fn new(args: &[String]) -> Result<Config, &'static str> {
+        if args.len() < 3 {
+            return Err("not enought arguments");
+        }
+
+        let query = args[1].clone();
+        let filename = args[2].clone();
+        // CASE_SENSITIVE 环境变量出现则代表区分大小写,而并不关心起具体值
+        let case_sensitive = env::var("CASE_SENSITIVE").is_err();
+
+        Ok(Config { query, filename, case_sensitive })
+    }
+}
+
+

在去参数的时候,之所以使用clone方法,是因为new函数并不拥有args参数的所有权,传进来仅仅只字符串切片,而返回的Config需要拥有字符串的所有权。针对queryfilename两个字段,必须克隆出一份,这样Config才拥有它两的所有权。

+

而迭代器有用数据的所有权,这样就不需要使用切片了,我门还可以使用迭代器自带的功能进行长度检查和索引。在main函数中,env::args()产生了个迭代器,我们可以直接将迭代器传给new函数。改造如下

+
impl Config {
+    pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
+        if args.len() < 3 {
+            return Err("not enought arguments");
+        }
+
+        args.next(); // 放出第一个元素
+
+        // 取第二个元素
+        let query = match args.next() {
+            Some(arg) => arg,
+            None => return Err("Didn't get a query string"),
+        };
+
+        // 取第三个元素
+        let filename = match args.next() {
+            Some(arg) => arg,
+            None => return Err("Didn't get a query string"),
+        };
+
+        // CASE_SENSITIVE 环境变量出现则代表区分大小写,而并不关心起具体值
+        let case_sensitive = env::var("CASE_SENSITIVE").is_err();
+
+        Ok(Config {
+            query,
+            filename,
+            case_sensitive,
+        })
+    }
+}
+
+

二、使用迭代器优化搜索函数

+

我们还可以优化search方法,使用迭代器替换原来的逻辑,如下示例代码

+
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
+    // let mut results = Vec::new();
+
+    // for line in contents.lines() {
+    //     if line.contains(query) {
+    //         results.push(line);
+    //     }
+    // }
+
+    // results
+
+    contents
+        .lines()
+        .filter(|line| line.contains(query))
+        .collect()
+}
+
+

使用迭代器的好处是让开发者能专注于高层的业务逻辑,而不必陷入写循环、维护临时变量的细节工作里。

+

search_case_insensitive函数也可以优化成如下示例代码

+
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
+    let query = query.to_lowercase();
+    // let mut results = Vec::new();
+
+    // for line in contents.lines() {
+    //     if line.to_lowercase().contains(&query) {
+    //         results.push(line);
+    //     }
+    // }
+    // results
+
+    contents
+        .lines()
+        .filter(|line| line.contains(&query))
+        .collect()
+}
+
+

三、零开销抽象

+

在上面的“二”中,如果我们把一本小说的内容放到一个String里面,搜索“the”。实际上测试的结果是,使用迭代器比手写循环还要快。

+

不过重点不在这,而我们要知道的是:迭代器在rust里是一种高层次的抽象,但是它在编译后,生成的逻辑和我们手写底层代码几乎一样的。这套机制在rust里叫做**零开销抽象**(Zero-Cost Abstraction),意味着使用抽象时不会引入额外的运行时开销。所以我们可以尽情使用类似迭代器这样高层次的抽象。

+

实际上,rust使用了很多优化,使得产出的代码非常地高效,我门可以无所畏惧地使用闭包或者迭代器,它们既能让代码在感观上保持高层次的抽象,又不会因此带来任何的运行时性能的损失。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/14_cargo_and_crates.io/01_cargo/index.html b/14_cargo_and_crates.io/01_cargo/index.html new file mode 100644 index 0000000..f04f67e --- /dev/null +++ b/14_cargo_and_crates.io/01_cargo/index.html @@ -0,0 +1,3193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 14.1   Cargo - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

Cargo

+

一、通过release profile来自定义构建

+

release profile是预定义的,也是可定义的,可使用不同的配置,对编译代码拥有更多的控制。每个profile的配置都独立于其他的profile。在cargo里主要有两种profile,如下

+
    +
  • dev profile:适用于开发,cargo build
  • +
  • release profile:适用于发布,cargo build --release
  • +
+

我们在编译的时候,如果执行的是cargo build命令,那么将使用dev profile;如果使用cargo build --release命令,将使用release profile。

+

二、自定义profile

+

针对每个profile,cargo都提供了默认的配置,如果想自定义某个xxx profie,我门可以在Cargo.toml里添加[profile.xxx]区域,在里面覆盖默认的子集。通常我门不会覆盖所有的配置,只需要覆盖想修改的配置即可。如果我门想针对devrelease的 profle进行修改,Cargo.toml文件示例内容如下

+
[package]
+name = "s43_minigrep_optmize"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[profile.dev]
+opt-level = 0
+
+[profile.release]
+opt-level = 3
+
+

opt-level参数决定是否对编译进行优化,会影响编译时间。dev profile的默认值是0,编译没有优化,这样编译速度更快,但编译产物效率较低;release profile的默认值是3,这样编译时间更长,但是得到的产物运行效率更高。

+

对于每个配置的默认值和完整选项,可以参考https://doc.rust-lang.org/stable/cargo/

+

三、发布crate到crate.io

+

之前我门通过crates.io下载了第三方库,我门也可以通过crates.io发布包来共享我们的代码。crate的注册表在https://crates.io,它会分发已注册的包的源代码,主要托管开源的代码。

+

四、文档注释

+

4.1 生成文档注释

+

rust使用一种特殊的文档注释,用于生成文档,可以生成项目的HTML文档,将显示公共API的文档注释,表明如何使用API。使用///,而且在文档中可以使用Markdown语法,文档注释放在被说明条目之前。

+

使用cargo doc生成html文档,它会运行rustdoc工具,该工具在安装rust的时候就自带了。生成的html文档放在target/doc目录下。生成文档之后,我门可以使用cargo doc --open命令会直接在浏览器打开文档。

+

4.2 常用章节

+

在文档注释中,会经常使用一些章节标注,常用章节如下

+

Example: 示例代码 +panics: 函数可能发生panic的场景 +Errors: 如果函数返回Result,描述可能的错误种类,以及可导致错误的条件 +Safety: 如果函数处于unsafe调用,就应该解释函数unsafe的原因,以及调用者应当确保使用的前提

+

4.3 文档注释作为测试

+

实际上,运行cargo test时,会把文档注释中的示例代码作为测试来运行,如下是的示例代码

+
/// Adds one to the number given.
+/// 
+/// # Examples
+///
+/// ```
+/// let args = 5;
+/// let answer = my_crate::add_one(args);
+/// 
+/// assert_eq!(6, anwser);
+/// ```dotnetcli
+pub fn add_one(x: i32) -> i32 {
+    x + 1
+}
+
+

运行cargo test之后,文档注释中的示例测试代码将会被执行。

+

4.4 为包含注释的项添加文档注释

+

符号: //!,这类注释通常用于描述crate和模块,如一个crate root(通常是src/lib.rs),另外是在一个模块内,将crate 或模块作为整体进行记录。如下的示例代码

+
//! # My Crate
+//! 
+//! `my_crate` is a collection of utilities to make performing certain calculations more convenient
+
+/// Adds one to the number given.
+/// 
+/// # Examples
+///
+/// ```
+/// let args = 5;
+/// let answer = my_crate::add_one(args);
+/// 
+/// assert_eq!(6, anwser);
+/// ```dotnetcli
+pub fn add_one(x: i32) -> i32 {
+    x + 1
+}
+
+

五、pub use

+

使用use可以到处方便使用的公共API,不过问题的在于,crate的程序结构在开发时对开发者很合理,但对于它的使用和不方便。开发者通常会把程序结构分为很多层,使用者想找到这种深层结构中的某个类型很费劲。如下的示例

+
my_crate::some_module::another_module::UsefulType;
+
+

上面的示例中存在多个层级,使用者很不方便对于用户来说,更为方便的应该是my_crate::UsefulType,但也有解决办法。我门不希要重新组织内部的代码结构,而是使用pub use,可以重新导出,创建与内部私有结构不同的对外公共结构。

+

下面我门看个示例,先创建src/lib.rs文件,写入如下的内容

+
//! # Art
+//! 
+//! A library for modeling artistic concept.
+
+pub mod kinds {
+    /// The primary colors according to the RYB color model.
+    pub enum PrimaryColor {
+        Red,
+        Yellow,
+        Blue,
+    }
+
+    /// The second colors according to RYB color model.
+    pub enum SecondaryColor {
+        Orange,
+        Green,
+        Purple,
+    }
+}
+
+pub mod utils {
+    use crate::kinds::*;
+
+    /// Combines two primary colors in equal amounts to crate
+    /// a secondary color.
+    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) ->SecondaryColor {
+        SecondaryColor::Green
+    }
+}
+
+

然后在src/main.rs调用,如下代码

+
use s44_cargo::kinds::PrimaryColor;
+use s44_cargo::utils::mix;
+
+fn main() {
+    let red = PrimaryColor::Red;
+    let yellow = PrimaryColor::Yellow;
+    mix(red, yellow);
+}
+
+

可以看到我们在引入函数的时候,层级结构比较深。我门下面做一个优化,使用pub use关键字在src/lib.rs里重新导出需要使用的内容到顶层结构,如下示例

+
//! # Art
+//! 
+//! A library for modeling artistic concept.
+
+pub use self::kinds::PrimaryColor;
+pub use self::kinds::SecondaryColor;
+pub use self::utils::mix;
+
+

这时候我们使用cargo doc --open生成并查看文档,这时候文档已经表明了导出的部分,如下图

+

44-01

+

这对于crate的用户来说,查找对应的类型和函数就非常方便了。这时候我们在使用的地方直接简化导入的层级,如下示例代码

+
// use s44_cargo::kinds::PrimaryColor;
+// use s44_cargo::utils::mix;
+use s44_cargo::PrimaryColor;
+use s44_cargo::mix;
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/14_cargo_and_crates.io/02_publish_crate/index.html b/14_cargo_and_crates.io/02_publish_crate/index.html new file mode 100644 index 0000000..d3cea2a --- /dev/null +++ b/14_cargo_and_crates.io/02_publish_crate/index.html @@ -0,0 +1,3024 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 14.2   发布crate - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

发布crate

+

一、创建账号

+

发布crate.io前,需要在创建账号并获得API Token。运行命令cargo login $API_TOKEN,将会通知cargo,把你的API token存储在本地~/.cargo/credentials。API Token可以在上进行撤销。

+

二、为新的crate添加元数据

+

在发布crate之前,需要在Cargo.toml[package]区域为crate添加一些元数据:

+
    +
  • name: crate需要指定位于的名称
  • +
  • description: 一两句话即可,会出现在crate搜索的结果里
  • +
  • license: 需要提供许可证的表示值,可以到http://spdx.org/licenses/查找,可以指定多个许可,使用OR隔开
  • +
  • version: 版本号
  • +
  • author: 作者
  • +
+

更多的原数据可以参考https://doc.rust-lang.org/cargo/reference/manifest.html,下面是一个示例的原数据

+
[package]
+name = "s44_cargo"
+version = "0.1.0"
+author = ["kotlindev"]
+edition = "2021"
+description = "A Rust tutorials"
+license = "MIT"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+

三、发布crate

+

最后,可以使用cargo pushlish进行发布。要注意的是,如果发布失败,原因可能是因为你的的账号还没绑定验证的邮箱,或者你的crate的元信息缺失,需要根据错误提示补充即可。

+

crate一旦发布,就是永久性的:该版本无法覆盖,代码无法删除。因为希望成为永久的代码托管服务器,并且依赖于该版本的项目可以继续正常工作。如果允许开发者删除已经发布的代码,那么将无法达到这个目的。

+

四、发布已存在的crate新版本

+

在修改crate的代码之后,当我们需要发布新版本时,需要先修改Cargo.toml里面的version值。可以参考语义化版本控制规范http://semver.org来定义你的语意版本。最后执行cargo publish进行发布。

+

五、使用cargo yank从crates.io撤回版本

+

我们不可以删除之前的版本,不会删除任何代码,但可以防止其他项目把某个版本最为新的依赖,使用yank可撤回一个crate版本即可。该操作防止新项目依赖于该版本,但已经存在的项目可以继续将其作为依赖并可以下载。

+

yank意味着:所有已经产生Cargo.lock的项目不会中断,同时任何将来新生成的Cargo.lock文件都不会使用被yank的版本。撤回命令如下示例

+
cargo yank --version 1.0.1
+
+

取消撤回如下命令

+
cargo yank --version 1.0.1 --undo
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/14_cargo_and_crates.io/03_cargo_workspace/index.html b/14_cargo_and_crates.io/03_cargo_workspace/index.html new file mode 100644 index 0000000..47fd911 --- /dev/null +++ b/14_cargo_and_crates.io/03_cargo_workspace/index.html @@ -0,0 +1,3039 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 14.3   Cargo工作空间 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

Cargo工作空间

+

一、概述

+

cargo工作空间可以帮助管理多个相互关联且需要协同开发的crate。实际上,cargo工作空间是一套共享同一个Cargo.lock和输出文件夹的包。

+

二、创建工作空间

+

有多种方式创建工作空间,我们下面先创建一个二进制crate,再创建2个库crate。二进制crate包含main函数,依赖于其他2个库crate,其中1个库crate提供add_one函数,另一个库crate提供add_two函数。下面是具体的步骤

+

先创建一个add目录,在该目录下创建一个Cargo.toml文件,该文件用于配置整个包工作空间,如下内容

+
[workspace]
+
+members = [
+    "adder"
+]
+
+

上面的代码中,包含wokspace节点,在该节点下的members字段表示工作空间的成员。现在我们使用cargo new adder在工作空间下创建出adder crate。

+

现在就创建出了一个工作空间,并且该空间内包含一个crate。在工作空间里使用cargo build命令将会对整个工作空间进行编译,编译后在target产生编译产物。即使我们切换到具体的crate目录执行cargo build,编译产物也依然会生成在工作空间下的target目录。通过共享一个target目录,不同的项目就可以避免不必要的重复编译过程。

+

三、工作空间内的二进制crate依赖库crate

+

现在我们在工作空间下的Cargo.toml文件添加add-one项目,该项目是个库crate,所以使用cargo new add-one --lib创建出来。我门修改add-one项目的src/lib.rs代码,修改为如下内容

+
pub fn add_one(x: i32) -> i32 {
+    x + 1
+}
+
+

接下来我们让adder项目依赖add-one这个项目,在adder项目中修改Cargo.toml文件,追加以下内容即可

+
add-one = { path = "../add-one" }
+
+

最后修改adder项目的src/main.rs源代码,如下

+
use add_one;
+
+fn main() {
+    let num = 10;
+    println!(
+        "Hello, world! {} plus one is {}!",
+        num,
+        add_one::add_one(num)
+    )
+}
+
+

这时候我们要运行adder这个项目,需要在工作空间内使用-p参数指定项目名即可,如下命令

+
cargo run -p adder
+
+

四、在工作空间中依赖外部的crate

+

工作空间只有一个Cargo.lock文件,在工作空间的顶层目录。这个文件的配置内容保证里工作空间内所有的crate使用的依赖的版本都相同。下面我们做一个测试

+

我们先在add-one项目的Cargo.toml添加以下定义

+
rand = "0.3.14"
+
+

我们在adder项目的Cargo.toml添加以下定义

+
rand = "0.3.15"
+
+

但是我们在工作空间执行cargo build之后,通过分析Cargo.lock文件,发现两个crate使用的是同一个版本,并覆盖了Cargo.toml中定义的版本。这样工作空间内所有的crate相互兼容。

+

我们再通过cargo new add-two --lib创建add-two项目,并在工作空间里引入,如果这时候我门直接在add-two项目里使用rand crate是不行的,编译会报错,需要在其Cargo.toml先声明才可以。

+

五、为工作空间添加测试

+

当我们在工作空间运行cargo test时,cargo会一次性运行工作空间里的所有crate的测试代码,包括文档注释的测试。如果只需要测试某个crate,使用-p参数指定具体crate即可,如下示例命令

+
cargo test -p add-one
+
+

对于发布,没有提供一次性发布工作空间所有crate的功能,如果需要发布,我门必须手动切换到具体的crate目录,再执行cargo publish

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/14_cargo_and_crates.io/04_install_binary_crate/index.html b/14_cargo_and_crates.io/04_install_binary_crate/index.html new file mode 100644 index 0000000..dc95567 --- /dev/null +++ b/14_cargo_and_crates.io/04_install_binary_crate/index.html @@ -0,0 +1,2971 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 14.4   安装二进制Crate - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

安装二进制Crate

+

一、概述

+

使用cargo install来安装二进制crate,安装来源来自https://crates.io

+

只能安装具有二进制目标(binary target)的crate,二进制目标binary target是一个可运行的程序,由src/main.rs或其他被指定为二进制文件的crate生成。

+

通常在对应的crate仓库中,README文件里里有关于crate的描述。

+

二、cargo install

+

cargo install安装的二进制存放在根目录的bin文件夹。

+

如果使用rustup安装的rust,没有进行任何配置,那么二进制存放目录是$HOME/.cargo/bin。为了确保rust安装的二进制程序能够运行,我门要确保该目录在PATH环境变量中,我门才能在终端上执行安装的二进制文件。

+

三、使用自定义命令扩展cargo

+

cargo被设计成可以使用子命令来扩展。例如:如果在PATH中的某个二进制是cargo-something,那么我们可以像自命令一样运行,如下

+
cargo something
+
+

类似这样的自定义命令可以通过cargo list命令列出来。这种设计的有点在于,我门可以使用cargo install来安装扩展,并想内置工具一样运行。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/14_cargo_and_crates.io/img/44-01.png b/14_cargo_and_crates.io/img/44-01.png new file mode 100644 index 0000000..eb77ccb Binary files /dev/null and b/14_cargo_and_crates.io/img/44-01.png differ diff --git a/15_smart_pointer/01_smart_pointer/index.html b/15_smart_pointer/01_smart_pointer/index.html new file mode 100644 index 0000000..9dea4a0 --- /dev/null +++ b/15_smart_pointer/01_smart_pointer/index.html @@ -0,0 +1,2997 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 15.1   智能指针概述 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

智能指针概述

+

一、概述

+

指针:一个变量在内存中包含的是一个地址(指向其他数据),该变量就是一个指针。在Rust中最常见的指针就是“引用”。引用使用&标注,它会借用它指向的值,而且没有其余的开销,它也是最常见的指针类型。

+

智能指针是这样的一些数据结构:行为和指针相似,但有额外的元数据和功能。

+

二、引用计数(reference counting)智能指针类型

+

引用计数智能指针是通过记录所有者的数量,使一份数据被多个所有者同时持有,并在没有任何所有者时自动清理数据。通过这个概念我们得知引用和智能指针有所不同,引用是借用数据,而智能指针很多时候都拥有它所指向的数据。以下是智能指针的例子

+
    +
  • String
  • +
  • Vec<T>
  • +
+

它们都拥有一片内存区域,并且允许用户对其操作。而且它们还有元数据,例如容量等。此外他们还提供额外的功能或保障,如String保障其数据都是合法的UTF8编码。

+

三、智能指针的实现

+

智能指针通常使用struct实现,和一般结构体不同的是,智能指针的结构体通常会实现DerefDrop这两个Trait。Deref trait允许智能指针struct的实例像引用一样使用,Drop trait允许你自定义当智能指针实例离开作用域时运行的代码。

+

四、智能智能的内容

+

在下面几节中,将介绍标准库中常见的智能指针

+
    +
  • Box<T>:在heap内存上分配
  • +
  • Rc<T>:启用多重所有权的引用计数类型
  • +
  • Ref<T>RefMut<T>,通过RefCell<T>访问:在运行时而不是编译时强制借用规则的类型
  • +
+

此外还将介绍以下两个知识点

+
    +
  • 内部可变模式(interior mutability pattern):不可变类型暴露出可修改其内部值的API
  • +
  • 引用循环(reference cycles):它们如何泄露内存,以及如何防止其发生
  • +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/15_smart_pointer/02_box_point_heap/index.html b/15_smart_pointer/02_box_point_heap/index.html new file mode 100644 index 0000000..562e8ae --- /dev/null +++ b/15_smart_pointer/02_box_point_heap/index.html @@ -0,0 +1,3080 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 15.2   使用Box指向Heap的数据 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

使用Box来指向Heap的数据

+

一、概述

+

Box<T>是一种最简单的智能指针,它实现了Deref trait和Drop trait。它允许你在heap上存储数据(而不是stack),Box在stack上有一个指针,指向heap上的真正数据,如下图

+

49-01

+

出了把数据存储在heap上之外,Box<T>没有其他的性能开销了,同时也没有其他额外的功能,适用于“间接”存储的场景,例如Cons List。

+

二、Box的常用场景

+

Box<T>主要有3个使用场景,如下

+
    +
  • 在编译时,某类型的大小无法确定。但使用该类型时,上下文却需要知道它的确切大小。
  • +
  • 当你有大量数据,想移交所有权,但确保这些爱操作时数据不会被复制。
  • +
  • 使用某值时,你只关心是否实现了特定的trait,尔不关心它的具体类型。
  • +
+

三、如何在heap上存储数据

+

我们先来看个示例代码

+
fn main() {
+    let b = Box::new(5);
+    println!("b = {}", b);
+}
+
+

在上面的变量b中,如果我们使用其他数据类型,那么数据将存在stack上,而显示用了Boxnew关联函数,5就存在heap上了。和其他任何拥有所有权的值一样,b在离开自己作用域的时候,会被自动释放存在statck上面的指针,已经存在heap上的数据。

+

四、使用Box赋能递归类型

+

4.1 递归数据结构

+

在编译时,Rust需要知道每一个类型所占的空间大小,递归类型的数据结构如下图

+

49-01

+

而递归类型的大小无法在编译时确定,但在递归类型中使用Box就能解决上诉问题。

+

Cons List是来自Lisp语言的一种数据结构,Cons List里的每个成员由两个元素组成,一个字段是当前项的值,另外一个字段是下一个元素。而下一个元素的数据类型也是当前元素的数据类型。Cons List 里的最后一个成员只包含一个Nil值,没有下一个元素。

+

其实Cons List就是其中链表,在Rust语言里面,像Cons List的结构并不是常用的集合。通常情况下,Vec<T>是更高的选择。

+

下面我们尝试在Rust中定义如Cons List 这样的递归数据结构,如下错误的代码

+
use crate::List::{Cons, Nil};
+
+enum List {
+    Cons(i32, List),
+    Nil,
+}
+
+fn main() {
+    let list = Cons(1, Cons(2, Cons(3, Nil)));
+}
+
+

上面的代码编译之后,将会出现报错,因为上面的list是递归的数据类型,拥有是无限制的大小,rust无法计算出存储list需要多大的空间。

+

4.2 解决存储递归数据结构的问题

+

我们继续看一个枚举的数据结构,分析Rust如何确定枚举分配的空间大小

+
enum Message {
+    Quit,
+    Move { x: i32, y:i32 },
+    Write(String),
+    ChangeColor(i32, i32, i32),
+}
+
+

rust会🏪枚举的所有变体,而一个枚举值只会有一个变体存在,所以Message枚举所需要的空间大小也就是能够存储得下最大变体的空间大小。

+

Box<T>是一个指针,指针的大小不会因为它指向的数据的大小变化而变化,Rust知道它需要多少空间。所以要在Rust中使用Cons,使用Box间接存储Cons中下一个数据即可,如下数据结构

+

49-03

+

修改代码如下

+
use crate::List::{Cons, Nil};
+
+enum List {
+    Cons(i32, Box<List>),
+    Nil,
+}
+
+fn main() {
+    let list = Cons(1, 
+        Box::new(Cons(2,
+            Box::new(Cons(3,
+                Box::new(Nil))))));
+}
+
+

现在使用新的Cons数据结构后,rust便知道使用一部分空间存储i32数据类型,意外一部分空间存储Box(相当于一个指针的大小)。优化之后数据结构仍然是一个递归,而数据不是直接存储了,使用一种间接的方式来指向和存储数据。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/15_smart_pointer/03_deref_trait/index.html b/15_smart_pointer/03_deref_trait/index.html new file mode 100644 index 0000000..dc65a68 --- /dev/null +++ b/15_smart_pointer/03_deref_trait/index.html @@ -0,0 +1,3107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 15.3   Deref Trait - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

Deref Trait

+

一、概述

+

如果一个类型实现了Deref Trait使我门可以自定义解引用*的行为。通过使用Deref,智能指针可以像常规引用一样来处理。

+

二、解引用运算符

+

常规的引用也是一种指针,我们先看一个示例

+
fn main() {
+    let x = 5;
+    let y = &x;
+
+    assert_eq!(5, x);
+    assert_eq!(5, *y);
+}
+
+

x存一个i32类型的整数,y是x的引用,所以y相当于是一个指针。第一个断言中5x是相等的,没有问题。y是个指针,这里是正数5的引用,它指向一个值: 5。如果想把它指向的值取出来,那就是在前面加一个解引用符号*,所以*y5也是相等的。

+

三、使用Box代替上例中的引用

+

我门可以原因的引用换成Box<T>,如下代码

+
fn main() {
+    let x = 5;
+    let y = Box::new(x);
+
+    assert_eq!(5, x);
+    assert_eq!(5, *y);
+}
+
+

以上代码也没有任何问题,所以说Box<T>可以像引用一样来处理。

+

四、定义自己的智能指针

+

Box<T>被定义成一个拥有元素的tuple struct,下面我们定义一个MyBox<T>,它也是一个tuple struct,即元组结构体。如下代码

+
struct MyBox<T>(T);
+
+impl<T> MyBox<T> {
+    fn new(x:T) -> MyBox<T> {
+        MyBox(x)
+    }
+}
+
+

MyBox实际上是一个有名称的元组,该元组只有一个元素。不过如果我们用于替代Box,是不能实现的,它将不能为解引用。如果需要能够解引用,我门需要实现Deref Trait。

+

五、实现Deref Trait

+

标准库中的Deref trait要求我们实现一个deref的方法,该方法会借用self,并返回一个指向内部数据的引用。如下示例代码

+
use std::ops::Deref;
+
+struct MyBox<T>(T);
+
+impl<T> MyBox<T> {
+    fn new(x:T) -> MyBox<T> {
+        MyBox(x)
+    }
+}
+
+impl<T> Deref for MyBox<T> {
+    type Target = T;
+
+    fn deref(&self) -> &T {
+        &self.0
+    }
+}
+
+fn main() {
+    let x = 5;
+    let y = MyBox::new(x);
+
+    assert_eq!(5, x);
+    assert_eq!(5, *(y.d));
+}
+
+

因为MyBox<T>实现了Deref trait,所以使用*可以进行解引用。实际上,*y就等同于*(y.deref()),因为rust编译器在编译时会将*y会隐式地展开成*(y.deref())

+

六、函数和方法的隐式解引用转化(Deref Coerion)

+

隐式解引用转化(Deref Coercion)是为函数和方法提供的一种便捷特性。假设T实现了Deref trait,Deref Coerion可以把T的引用转化为T经过Deref操作后的生成引用。

+

当把某类型的引用传递给函数或方法时,但它的类型于定义的参数不匹配,Deref Coerion就会自动发生。编译器会对deref进行一系列调用,来把它转为所需的参数类型。这个操作在编译时完成,没有额外的性能开销。我们在五中的示例代码中追加一个函数,如下

+
fn hello(name: &str) {
+    println!("Hello, {}", name);
+}
+
+

接着在main函数中调用

+
fn main() {
+    let m = MyBox::new(String::from("Rust"));
+
+    hello("Rust");
+    hello(&m);
+}
+
+

在上面的代码中,hello方法需要接收一个字符串切片。mMyBox<String>的一个引用,由于MyBox<T>实现了deref trait,所以rust可以调用deref方法把MyBox<String>的引用转化为String的引用。而String也实现了deref trait,它的实现是返回字符串切片。所以上面hello方法传入的&m经过隐式解引用转化之后,类型就匹配了。

+

如果rust没有实现隐式解引用,我们的调用方法如下

+
hello(&(*m)[..]);
+
+

充满了各种符号,难以阅读。 所以说只要这个类型实现了Deref trait,rust就会自动分析类型,并不断尝试调用deref方法,来让它与函数或是方法定义的参数类型匹配。而且这个过程是在编译的时候完成的,对程序的运行时不会额外的性能开销。

+

七、解引用与可变性

+

可使用DerefMut trait重载可变引用*运算符。在类型和trait在下列三种情况发生时,rust会执行defref coercion

+
    +
  • T实现了Deref<target=U>,允许&T转为&U
  • +
  • T实现了DerefMut<target=U>,允许&mut T转为&mut U
  • +
  • T实现了Deref<target=U>,允许&mut T转为&U
  • +
+

rust可以把一个可变引用转为不可变引用,但是反过来不行。因为将不可变引用转为可变引用,借用规则要求这个引用必须时唯一的,但这点无法保证。上面列出的情况稍微记一下就行,这里还有些知识为涉及到。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/15_smart_pointer/04_drop_trait/index.html b/15_smart_pointer/04_drop_trait/index.html new file mode 100644 index 0000000..87654d7 --- /dev/null +++ b/15_smart_pointer/04_drop_trait/index.html @@ -0,0 +1,2984 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 15.4   Drop Trait - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

Drop Trait

+

一、概述

+

当一个数据类型实现Drop Trait之后,可以让我们自定义当值离开作用域时发生的动作。通常发生的动作包括:文件、网络资源的释放等。任何类型都可以实现Drop trait,Drop Trait只要求实现drop方法,drop的参数是对self的可变引用。Drop trait在预导入模块(prelude)里。我们先看一个示例代码

+
struct CustomartPointer {
+    data: String,
+}
+
+impl Drop for CustomartPointer {
+    fn drop(&mut self) {
+        println!("Dropping CustomSmartPointer with data `{}` !", self.data)
+    }
+}
+
+fn main() {
+    let c = CustomartPointer { data: String::from("my  stuff") };
+    let d = CustomartPointer { data: String::from("other  stuff") };
+
+    println!("CusomSmartPointers created")
+}
+
+

执行程序之后,输出以下内容

+
CusomSmartPointers created
+Dropping CustomSmartPointer with data `other  stuff` !
+Dropping CustomSmartPointer with data `my  stuff` !
+
+

这表明,两个变量离开作用域的时候调用了drop方法。

+

二、使用std::mem::drop来提前drop值

+

我们很难直接自动调用drop功能,其实也没有必要,因为Drop trait的目的就是进行自动的释放逻辑处理。此外,rust也不允许手动调用Drop trait的drop方法,但可以调用标准库的std::mem::drop函数,该函数也在预导入模块中,来提前drop值,相当于提前调用了Drop trait的drop方法。如下示例代码

+
let c = CustomartPointer { data: String::from("my  stuff") };
+drop(c);
+let d = CustomartPointer { data: String::from("other  stuff") };
+
+println!("CusomSmartPointers created")
+
+

这时候,输入内容如下

+
Dropping CustomSmartPointer with data `my  stuff` !
+CusomSmartPointers created
+Dropping CustomSmartPointer with data `other  stuff` !
+
+

这时候,你可能会有疑问:提前调用drop函数,那么会不会出现重复释放(double free)的错误呢?答案是不会的,rust的设计很安全,它的所有权系统会保证引用的有效,而drop方法也只会在不再使用该值的时候只调用一次。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/15_smart_pointer/05_rc/index.html b/15_smart_pointer/05_rc/index.html new file mode 100644 index 0000000..10471d9 --- /dev/null +++ b/15_smart_pointer/05_rc/index.html @@ -0,0 +1,3033 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 15.5   Rc - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

Rc

+

一、概述

+

Rc<T>是一个引用计数智能指针。

+

所有权在大多数情况下是比较清晰的,对于一个值,我们一般能准确判定哪个变量拥有它,但在某些场景中,单个值有可能被多个所有者所持有。我们看一个图

+

52-01

+

图中是一个图的数据结构,节点6被多个数据节电引用,就可以说节点6同时被多个数据拥有。为了支持多重引用,rust提供了一种叫Rc<T>的数据类型。即reference counting(引用计数)。这个数据类型会在实例的内部维护一个用于记录值的引用次数的计数器。从而判断该值是否仍然在使用,得以追踪到所有值的引用。如果记录到某个值的引用个数为0,那么可以意味该值可以被清理掉,而且不用出发引用失效的问题。

+

二、Rc使用场景

+

需要在heap上分配数据,这些数据被程序员的多个部分读取(只读),但在编译时无法确定哪个部分最后使用完这些数据,就可以使用Rc<T>。需要注意的是,Rc<T>只能用于但线程场景。

+

Rc<T>不在域导入模块(prelude)中,Rc::clone(&a)函数用于增加引用计数,而Rc::strong_count(&a)用于获得引用计数,而Rc::weak_count函数用于弱引用计数。下面我们先看一个示例

+

两个List共享另一个List的所有权,如下图

+

52-02

+

为了实现上面的功能,我们编写如下代码

+
enum List {
+    Cons(i32, Box<List>),
+    Nil
+}
+
+use crate::List:: {Cons, Nil};
+
+fn main() {
+    let a = Cons(5,
+        Box::new(Cons(10,
+           Box::new(Nil))));
+
+    // 开头元素是3,再接上a
+    let b = Cons(3, Box::new(a));
+    // 开头元素是4,再接上a
+    let c = Cons(4, Box::new(a));
+}
+
+

不过以上的代码编译时会发生错误,因为a的已经将值移动到了b即将所有权转移到了b,如果再移动到c将会发生报错。如果我门使用Rc<T>,将解决该问题,如下示例代码

+
use crate::List::{Cons, Nil};
+use std::rc::Rc;
+
+enum List {
+    Cons(i32, Rc<List>),
+    Nil,
+}
+
+fn main() {
+    let a = Rc::new(Cons(5, Rc::new(Cons(10,Rc::new(Nil)))));
+    let b = Cons(3, Rc::clone(&a));
+    let c = Cons(3, Rc::clone(&a));
+}
+
+

在上面的代码中,当创建了ba的引用时,Rc计数器变成2,当创建ca的引用时,Rc计数器变成3,abc这3个应用共享的时一份数据。每次调用Rc::clone方法,Rc的计数器都会增加1。Rc<T>这种数据类型的引用计数器减少到0的时候,对应的数据才会被清理掉。

+

其实在上面的示例代码中,如果我想拷贝a对应数据,也可以a.clone()方法,但是该方法会执行深度拷贝,在heap上创建出另外一份数据。而Rc::clone只会进行浅拷贝,在stack上创建数据的引用。我们通常惯例调用Rc::clone而不是调用数据类型对应的clone方法。

+

下面我们修改一下main函数

+
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
+println!("counter after creating a = {}", Rc::strong_count(&a));
+
+let b = Cons(3, Rc::clone(&a));
+println!("counter after creating b = {}", Rc::strong_count(&a));
+
+{
+    let c = Cons(3, Rc::clone(&a));
+    println!("counter after creating c = {}", Rc::strong_count(&a));
+}
+
+println!(
+    "counter after c goes out of scopes = {}",
+    Rc::strong_count(&a)
+);
+
+

我们使用Rc::strong_count函数打印出强引用的计数,运行结果如下

+
counter after creating a = 1
+counter after creating b = 2
+counter after creating c = 3
+counter after c goes out of scopes = 2
+
+

可以看到,当变量离开作用域的时候,计数引用也会自动减少。

+

三、Rc::clone()和类型的clone()方法

+

Rc::clone(): 增加引用,不会执行数据的深度拷贝操作 +类型的clone(): 大多情况会执行数据的深度拷贝操作

+

Rc<T>通过不可变引用,使得我们可以在程序不同部分之间共享只读数据。但是,在开发中很多时候需要数据可变,如何允许数据变化呢?我们将在后续的章节中介绍。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/15_smart_pointer/06_ref_cell/index.html b/15_smart_pointer/06_ref_cell/index.html new file mode 100644 index 0000000..f898679 --- /dev/null +++ b/15_smart_pointer/06_ref_cell/index.html @@ -0,0 +1,3259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 15.6   RefCell - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

RefCell和内部可变性

+

一、内部可变性

+

内部可变性是Rust的设计模式之一,它允许你在只持有不可变引用的前提下进行修改。数据结构中使用了unsafe代码绕过rust正常的可变性和借用规则。

+

二、RefCell

+

2.1 Rc和RefCell

+

Rc<T>不同,RefCell<T>类型代表了其持有数据的唯一所有权。

+

我们先回忆一下借用规则

+
    +
  • 在任何给定的时间里,你要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用
  • +
  • 引用总是有效的
  • +
+

RefCell<T>Box<T>的区别如下

+ + + + + + + + + + + + + +
BoxRefCell
编译阶段强制代码遵循借用规则,否则出现错误只会在运行时检查借用规则,没有满足借用规则则触发panic
+

2.2 借用规则在不同阶段进行检查比较

+

在编译阶段检查,尽早暴露问题,没有运行时开销,对大多数场景是最佳选择,也是rust的默认行为。在运行时阶段检查,问题暴露延后,甚至影响到生产环境,因借用计数器产生些许性能损失,但实现了某些特定的内存安全场景(不可变环境中修改自身数据)。

+

针对编译器无法理解的代码,rust直接拒绝编译通过,避免产生问题。但如果开发者能够保证借用规则能满足,那么这个时候RefCell<T>就有它的勇武之处了。

+

2.3 RefCell

+

Rc<T>相似,只能用于但线程场景。

+

选择Box<T>Rc<T>RefCell<T>的依据

+
    +
  • Box: 同一个数据拥有一个所有者,可变、不可变借用在编译时检查
  • +
  • Rc: 同一个数据拥有多个所有者,不可变引用在编译时检查
  • +
  • RefCell: 同一数据拥有过一个所有者,可变、不可变借用在运行时检查
  • +
+

其中,即便RefCell<T>本身不可变,但仍能修改其中存储的值

+

2.4 内部可变性,可变的借用一个不可变的值

+

借用规则有一个这样的推论,我们无法借用一个不可变的值。我们先看一个示例

+
fn main() {
+    let x = 5;
+    let y = &mut x;
+}
+
+

上面的代码中,x是不可变的,让y借用一个x的可变引用,这样是不运行的,编译会发生报错。然而在某些情况下,我们需要这样一个值,它对外部是不可变的,但它同时能够在方法内部修改自身的值。除了这个数据的本身方法,其余的代码都不能修改这个值。

+

使用RefCell<T>就是获得这种内部可变性的一个方法,不过RefCell<T>并没有完全完全绕开借用规则,我们虽然使用内部可变性通过了编译阶段的借用检查,但借用检查仅仅是延后到了运行阶段而已,如果违反了借用规则,那么程序在运行时会出现panic。

+

下面我们来看一个示例

+
pub trait Messenger {
+    fn send(&self, msg: &str);
+}
+
+pub struct LimitTracker<'a, T: 'a + Messenger> {
+    messenger: &'a T,
+    value: usize,
+    max: usize,
+}
+
+impl<'a, T> LimitTracker<'a, T>
+where
+    T: Messenger,
+{
+    pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
+        LimitTracker {
+            messenger,
+            value: 0,
+            max,
+        }
+    }
+
+    pub fn set_value(&mut self, value: usize) {
+        self.value = value;
+
+        let percentage_of_max = self.value as f64 / self.max as f64;
+        if percentage_of_max >= 1.0 {
+            self.messenger.send("Error: You are over your quota!");
+        } else if percentage_of_max >= 0.9 {
+            self.messenger
+                .send("Urgent warning: You've used up over 90% of your quota!");
+        } else if percentage_of_max >= 0.75 {
+            self.messenger
+                .send("Warning: You've used up over 75% of your quota!");
+        }
+    }
+}
+
+

我们要测试实现了Messenger这个trait的值和一个max值来创建LimitTracker的时候,传入不同的value就能够触发Messenger发送不同的消息。而我们使用"mock object"模拟对象来创建LimitTracker实例之后,我门便可以通过调用模拟对象的set_value方法来检查模拟对象中是否存储了我们希望看到的消息。按照这个思路,我门实现一下测试代码

+
#[cfg(test)]
+mod tests {
+    use super::*;
+
+    struct MockMessenger {
+        sent_messenger: Vec<String>,
+    }
+
+    impl MockMessenger {
+        fn new() -> MockMessenger {
+            MockMessenger {
+                sent_messenger: vec![],
+            }
+        }
+    }
+
+    impl Messenger for MockMessenger {
+        fn send(&mut self, message: &str) {
+            self.sent_messenger.push(String::from(message))
+        }
+    }
+
+    #[test]
+    fn it_sends_an_over_75_percent_warning_message() {
+        // 测试超过70%的情况
+        let mock_messenger = MockMessenger::new();
+        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
+
+        limit_tracker.set_value(80);
+        assert_eq!(mock_messenger.sent_messenger.len(), 1);
+    }
+}
+
+

不过,编译上面的代码的时候,会出现错误,错误在于Messenger trait的send方法生命的时候self参数是不可变,但实现的方法,是可变的,因为我们需要修改里面的值。

+

这是时候这就是内部可变性可以使用的场景,这时候使用RefCell,最终修改的测试代码如下

+
#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::cell::RefCell;
+
+    struct MockMessenger {
+        sent_messenger: RefCell<Vec<String>>,
+    }
+
+    impl MockMessenger {
+        fn new() -> MockMessenger {
+            MockMessenger {
+                sent_messenger: RefCell::new(vec![]),
+            }
+        }
+    }
+
+    impl Messenger for MockMessenger {
+        fn send(&self, message: &str) {
+            self.sent_messenger.borrow_mut().push(String::from(message))
+        }
+    }
+
+    #[test]
+    fn it_sends_an_over_75_percent_warning_message() {
+        // 测试超过70%的情况
+        let mock_messenger = MockMessenger::new();
+        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
+
+        limit_tracker.set_value(80);
+        assert_eq!(mock_messenger.sent_messenger.borrow().len(), 1);
+    }
+}
+
+

此时代码测试通过了。我们将Vec换成RefCell<Vec>之后,然后调用对应数据的borrow_mut方法,它可以获得内部值的可变引用,最后调用了borrow方法,获得了内部值的不可变引用。

+

2.5 使用RefCell在运行时记录借用信息

+

RefCell提供了两个安全接口

+
    +
  • borrow方法:返回只能指针Ref<T>,它实现了Deref
  • +
  • borrow_mut方法:返回只能指针RefMut<T>,它实现了Deref
  • +
+

RefCell<T>会记录当前存在多少个活跃的Ref<T>RefMut<T>只能指针。每次调用borrow,不可变借用计数加1,任何一个Ref<T>的值离开作用域被释放时,不可变借用计数减1;每次调用borrow_mut,可变借用计数加1,任何一个RefMut<T>的值离开作用域被释放时,可变借用计数减1。

+

rust通过上述技术来维护借用检查规则,任何一个给定时间里,值允许拥有多个不可变借用或一个可变借用。而当我们违背这个规则时,RefCell<T>就会在运行时来触发panic。

+

2.6 将Rc和RefCell结合使用来实现多重所有权可变数据

+

RefCell<T>Rc<T>结合使用是一种很常用的做法,Rec<T>允许多个所有者持有同一个数据,但只能提供对数据的不可变访问,如果我们在Rc<T>里存储RefCell<T>,那么就可以定义出拥有多个所有者而且能够进行修改的值。如下示例代码

+
#[derive(Debug)]
+enum List {
+    Cons(Rc<RefCell<i32>>, Rc<List>),
+    Nil,
+}
+
+use crate::List::{Cons, Nil};
+use std::rc::Rc;
+use std::cell::RefCell;
+
+fn main() {
+    let value = Rc::new(RefCell::new(5));
+    // 通过Rc::clone共享value的值给a
+    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
+    // b、c共享a列表
+    let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
+    let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));
+
+    // 使用*把Rc<T>解引用为RefCell<T>,通过borrow_mut修改value的值,加10
+    *value.borrow_mut() += 10;
+
+    println!("a after = {:?}", a);
+    println!("b after = {:?}", b);
+    println!("c after = {:?}", c);
+}
+
+

最终运行结果如下

+
a after = Cons(RefCell { value: 15 }, Nil)
+b after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))
+c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))
+
+

value的值变了,符合预期。

+

三、其他可实现内部可变性的类型

+

rust还提供了其他可实现内部可变性的类型,如下示例

+
    +
  • Cell<T>: 通过复制来访问数据
  • +
  • Mutex<T>: 用于实现跨线程情况下的内部可变性模式
  • +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/15_smart_pointer/07_loop_ref_leak/index.html b/15_smart_pointer/07_loop_ref_leak/index.html new file mode 100644 index 0000000..c45401e --- /dev/null +++ b/15_smart_pointer/07_loop_ref_leak/index.html @@ -0,0 +1,3245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 15.7   循环引用导致内存泄漏 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

循环引用导致内存泄漏

+

一、内存泄漏的示例

+

rust的内存安全机制可以保证很难发生内存泄漏,但不是不可能。如使用Rc<T>RefCell<T>就可能造出循环使用,从而发生内存泄漏。因为每项的引用数量不会变成0,值也不会被处理掉。我们先看下面的示例代码

+
use std::{cell::RefCell, rc::Rc};
+use crate::List::{Cons, Nil};
+
+#[derive(Debug)]
+enum List {
+    Cons(i32, RefCell<Rc<List>>),
+    Nil,
+}
+
+impl List {
+    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
+        match self {
+            Cons(_, item) => Some(item),
+            Nil => None
+        }
+    }
+}
+
+fn main() {
+    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
+
+    // 打印a的强引用
+    println!("a initial rc count = {}", Rc::strong_count(&a));
+    // 打印a的第二个元素
+    println!("a text item = {:?}", a.tail());
+
+    // b的第一个元素是10,第二个元素共享a的数据
+    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
+    // 打印a的强引用
+    println!("a rc count after b creation = {}", Rc::strong_count(&a));
+    // 打印b的强引用
+    println!("b initial rc count = {}", Rc::strong_count(&b));
+    // 打印b的第二个元素
+    println!("b next item = {:?}", b.tail());
+
+    // 取出a的第二个元素
+    if let  Some(link) = a.tail() {
+        // 把a原来存储的Nil改为B存储的值
+        *link.borrow_mut() = Rc::clone(&b);
+    }
+
+    println!("b rc count after changing a = {}", Rc::strong_count(&b));
+    println!("a rc count after changing a = {}", Rc::strong_count(&a));
+}
+
+

该开始创建a的时候,它的引用数量是1,它的第二个元素是2,b创建之后,a的强引用个数变成2,b的引用个数变成1,b的第二个元素是a。接下来让a的第二个元素指向b,所以这个时候b有两个引用,a也有两个引用,相当于此时创建了一个循环的数据结构,如下图

+

54-01

+

a的第一个元素是5,第二个元素是b,b的第一个元素是10,第二个元素是a。这时候形成了一个循环。假如此时代码执行完毕,将先释放b,因为b是在后面创建的。程序将会把b对应的数据的引用计数减少到1,因为此时a仍然存在一个指向b对应的数据的引用。那么b对应的List在堆内存上讲不会进行释放,如果我们在main函数的最后添加以下一行代码,将会导致堆栈溢出

+
println("a next item = {:?}", a.tail());
+
+

a的第二个元素是b,而的第二个元素是a,以此循环,最终导致堆栈溢出。所以,在rust里,内存溢出并不容易,但绝非不可能。

+

二、防止内存泄漏的解决办法

+

2.1 依靠开发者来保证,不能依靠rust

+

如认证检查自己的代码逻辑,并做一些测试,防止内存泄漏

+

2.2 重组数据结构

+

简单来说把引用拆分来持有所有权和不持有所有权两种情况,如在循环引用中,让某些指向关系具有所有权,而另外一部分的指向关系不涉及所有权。这样只有持有所有权的关系才影响值的清理。

+

2.3 防止循环引用,把Rc换成Weak

+

Rc::cloneRc<T>示例的strong_count加1,Rc<T>的实例只有在strong_count为0的时候才会被清理。

+

Rc<T>实例通过调用Rc::downgrade方法可以创建值的Weak Reference(弱引用),返回值类型是Weak<T>(只能指针)。每次调用Rc::downgrade方法会为weak_count(即弱引用计数)加1,而Rc<T>使用weak_count来追踪存在多少Weak<T>weak_count不为0并不影响Rc<T>实例的清理。

+

2.4 Strong VS Weak

+
    +
  • Strong Reference(强引用)是关于如何分享Rc<T>实例的所有权
  • +
  • Weak Reference(弱引用)并不表述上述意思
  • +
+

使用Weak Reference并不会创建循环引用,因为当Strong Reference数量为0的时候,Weak Reference会自动断开。在使用Weak<T>前,需保证它指向的值仍然存在,在Weak<T>实例上调用upgrade方法,返回Option<Rc<T>>

+

2.5 Weak Reference使用示例

+

我们先看以下的示例

+
use std::rc::Rc;
+use std::cell::RefCell;
+
+#[derive(Debug)]
+// 定义树的节点
+struct Node {
+    value: i32,
+    // 使用Rc<Node>为了让所有的子节点能够共享所有权
+    children: RefCell<Vec<Rc<Node>>>
+}
+
+fn main() {
+    // 创建一个树叶
+    let leaf = Rc::new(Node {
+        value: 3,
+        children: RefCell::new(vec![]),
+    });
+
+    // 创建一个树枝
+    let branch = Rc::new(Node {
+        value: 5,
+        children: RefCell::new(vec![Rc::clone(&leaf)]),
+    });
+}
+
+

上面示例代码中,我们可以通过树枝找到叶子,但还不可以通过叶子找到树枝,因为树叶还不持有树枝的引用,它对它们两之间的父子关系一无所知。所以我门需要修改一下代码,但是双向的引用会是循环引用,这个时候我们就可以使用Weak<T>,让它避免产生循环引用。修改代码如下

+
use std::borrow::Borrow;
+use std::rc::{Rc, Weak};
+use std::cell::RefCell;
+
+#[derive(Debug)]
+// 定义树的节点
+struct Node {
+    value: i32,
+    parent: RefCell<Weak<Node>>,
+    // 使用Rc<Node>为了让所有的子节点能够共享所有权
+    children: RefCell<Vec<Rc<Node>>>
+}
+
+fn main() {
+    // 创建一个叶子
+    let leaf = Rc::new(Node {
+        value: 3,
+        parent: RefCell::new(Weak::new()),
+        children: RefCell::new(vec![]),
+    });
+
+    // 通过树叶找到树枝,通过borrow()获得不可变引用,在通过upgrade()将Weak<T>转换为Rc<T>
+    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
+
+    // 创建一个树枝
+    let branch = Rc::new(Node {
+        value: 5,
+        parent: RefCell::new(Weak::new()),
+        children: RefCell::new(vec![Rc::clone(&leaf)]),
+    });
+
+    // 让树叶的parent指向树枝
+    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
+
+    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
+}
+
+

为了观察数据强引用也弱引用的计数变化,我们修改代码如下

+
use std::borrow::Borrow;
+use std::cell::RefCell;
+use std::rc::{Rc, Weak};
+
+#[derive(Debug)]
+// 定义树的节点
+struct Node {
+    value: i32,
+    parent: RefCell<Weak<Node>>,
+    // 使用Rc<Node>为了让所有的子节点能够共享所有权
+    children: RefCell<Vec<Rc<Node>>>,
+}
+
+fn main() {
+    // 创建一个叶子
+    let leaf = Rc::new(Node {
+        value: 3,
+        parent: RefCell::new(Weak::new()),
+        children: RefCell::new(vec![]),
+    });
+    // 打印leaf
+    println!(
+        "leaf strong = {}, weak = {}",
+        Rc::strong_count(&leaf),
+        Rc::weak_count(&leaf)
+    );
+
+    // 一个作用域
+    {
+        // 创建一个树枝
+        let branch = Rc::new(Node {
+            value: 5,
+            parent: RefCell::new(Weak::new()),
+            children: RefCell::new(vec![Rc::clone(&leaf)]),
+        });
+
+        // 让树叶的parent指向树枝
+        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
+
+        // 打印branch
+        println!(
+            "branch strong = {}, weak = {}",
+            Rc::strong_count(&branch),
+            Rc::weak_count(&branch)
+        );
+
+        // 打印leaf
+        println!(
+            "leaf strong = {}, weak = {}",
+            Rc::strong_count(&leaf),
+            Rc::weak_count(&leaf)
+        );
+    }
+
+    // 使用leaf访问branch
+    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
+    // 打印leaf
+    println!(
+        "leaf strong = {}, weak = {}",
+        Rc::strong_count(&leaf),
+        Rc::weak_count(&leaf)
+    );
+
+}
+
+

运行结果如下

+
leaf strong = 1, weak = 0
+branch strong = 1, weak = 1
+leaf strong = 2, weak = 0
+leaf parent = None
+leaf strong = 1, weak = 0
+
+

根据结果,我们得出一下结论:

+
    +
  • +

    创建leaf之后,有1个强引用,0个弱引用。

    +
  • +
  • +

    然后进入一个作用域,在作用域里创建branch,再把leaf的parent连接到branchbranch有1个强引用,并且多了1个弱引用,因为leaf对其做了关联。这时候leaf有2个强引用,因为在创建branch的时候,branch中的children对其有一个强引用。

    +
  • +
  • +

    最后走出作用域,通过leaf访问它的parent,得到的结果是None。此时leaf剩下本省1个强引用,而弱引用也是0。

    +
  • +
+

所以看一看到,branch离开作用域之后,它的强引用变成0,此时相当于被丢弃了。虽然在branch的生命周期内它拥有一个弱引用,但并不影响其数据的销毁。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/15_smart_pointer/img/49-01.png b/15_smart_pointer/img/49-01.png new file mode 100644 index 0000000..9d77c08 Binary files /dev/null and b/15_smart_pointer/img/49-01.png differ diff --git a/15_smart_pointer/img/49-02.png b/15_smart_pointer/img/49-02.png new file mode 100644 index 0000000..2d5fb1a Binary files /dev/null and b/15_smart_pointer/img/49-02.png differ diff --git a/15_smart_pointer/img/49-03.png b/15_smart_pointer/img/49-03.png new file mode 100644 index 0000000..83ce627 Binary files /dev/null and b/15_smart_pointer/img/49-03.png differ diff --git a/15_smart_pointer/img/50-02.png b/15_smart_pointer/img/50-02.png new file mode 100644 index 0000000..d8bfda5 Binary files /dev/null and b/15_smart_pointer/img/50-02.png differ diff --git a/15_smart_pointer/img/52-01.png b/15_smart_pointer/img/52-01.png new file mode 100644 index 0000000..c96c839 Binary files /dev/null and b/15_smart_pointer/img/52-01.png differ diff --git a/15_smart_pointer/img/54-01.png b/15_smart_pointer/img/54-01.png new file mode 100644 index 0000000..c61c1e7 Binary files /dev/null and b/15_smart_pointer/img/54-01.png differ diff --git a/16_concurrency/01_multi_thread/index.html b/16_concurrency/01_multi_thread/index.html new file mode 100644 index 0000000..bb52379 --- /dev/null +++ b/16_concurrency/01_multi_thread/index.html @@ -0,0 +1,3156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 16.1   多线程同时运行代码 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

多线程同时运行代码

+

一、概述

+

1.1 并发

+
    +
  • Concurrent(并发): 程序不同的部分之间独立执行
  • +
  • Parallel(并行): 程序不同部分同时运行
  • +
+

Rust允许我们编写没有细微Bug的代码,并在不引入新bug的情况下易于重构。本文中的“并发”泛指concurrent和Parallel。

+

1.2 进程与线程

+

在大部分OS里,代码运行在进程(process)中,OS同时管理多个进程。

+

在你的程序里,各个独立的部分可以同时运行,运行这些独立的部分就是线程(thread)。由于多个线程是可以同时运行的,所以我们通常把程序的计算拆分为多个线程同时来运行。这样的好处在于提升程序的性能表现,但也增加了复杂性,无法保证各程序的执行顺序,因为我们无法保证各个线程之间的顺序。

+

1.3 多线程可导致的问题

+
    +
  • 竞争状态,线程以不一致的顺序访问数据或资源
  • +
  • 死锁,两个线程彼此等待对方使用完持有的资源,导致线程无法继续
  • +
  • 出现只在某些情况下发生的bug,而且很难k可靠地复现和修复
  • +
+

1.4 实现线程的方式

+

第一种,通过调用OS的API来创建线程,这就是所谓的1:1模型,即一个操作的线程对应一个编程语言的线程,优点是需要较小的运行时。

+

第二种,编程语言实现自己的线程(绿色线程),这就是所谓的M:N模型,即M个绿色线程对应N个系统线程,但是需要比较大的运行时。

+

Rust标准库仅提供1:1的线程支持,但是由于rust拥有良好的底层抽象能力,所以在社区里也涌现出了很多M:N线程模型的第三方包。

+

二、多线程的使用

+

2.5 使用spawn创建新线程

+

rust通过thread::spawn函数可以创建新线程,参数为一个闭包,在新线程将运行闭包的代码。如下示例

+
use std::thread::{self, Thread};
+use std::time::Duration;
+
+fn main() {
+    thread::spawn(||{
+        for i in 1..10 {
+            println!("hi number {} from the spawnd thread!", i);
+            thread::sleep(Duration::from_millis(1));
+        }
+    });
+
+    for i in 1..5 {
+        println!("hi number {} from the main thread!", i);
+        thread::sleep(Duration::from_millis(1));
+    }
+}
+
+

我们将在程序中看到交替打印主线程和子线程的输出内容,不过当主线运行程结束后,整个程序运行结束。但实际上我们希望子线程能完整地执行所有逻辑。

+

2.2 通过join handle来等待所有线程完成

+

thread::spawn函数的返回值类型是JoinHandleJoinHandle持有值的所有权,通过调用其join方法,可以等待对应的其他线程的完成。调用JoinHandlejoin方法会阻止当前运行线程的执行,直到handle所表示的这些线程结束。我门可以修改"2.1"中的代码如下,以保证子线程能完整地执行所有逻辑

+
use std::thread::{self, Thread};
+use std::time::Duration;
+
+fn main() {
+    let handle = thread::spawn(||{
+        for i in 1..10 {
+            println!("hi number {} from the spawnd thread!", i);
+            thread::sleep(Duration::from_millis(1));
+        }
+    });
+
+    for i in 1..5 {
+        println!("hi number {} from the main thread!", i);
+        thread::sleep(Duration::from_millis(1));
+    }
+
+    handle.join().unwrap();
+}
+
+

2.3 使用move闭包

+

move闭包通常和thread::spawn函数一起使用,它允许你使用其他线程的数据。也就是说,在创建线程的时候,把值的所有权从一个线程转移到另一个线程。我们先来看一个错误的示例代码

+
use std::thread;
+
+fn main() {
+    let v = vec![1, 2, 3];
+    let handle = thread::spawn(|| {
+        println!("Here's a vector: {:?}", v);
+    });
+
+    drop(v);
+
+    handle.join().unwrap();
+}
+
+

以上代码之所以会报错,是因为子线程借用了v,但是v的生命周期可能会比子线程短。所以rust不允许编译通过,此时我们只需要加上move关键字转移v的所有权到子线程,但是加了move关键字之后在主线程中不能再调用drop(v)。如下示例代码

+
use std::thread;
+
+fn main() {
+    let v = vec![1, 2, 3];
+    let handle = thread::spawn(move || {
+        println!("Here's a vector: {:?}", v);
+    });
+
+    handle.join().unwrap();
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/16_concurrency/02_cross_thread_message_passing/index.html b/16_concurrency/02_cross_thread_message_passing/index.html new file mode 100644 index 0000000..ff1ffb9 --- /dev/null +++ b/16_concurrency/02_cross_thread_message_passing/index.html @@ -0,0 +1,3168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 16.1   跨线程消息传递 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

跨线程消息传递

+

一、概述

+

消息传递是一种很流行且能保证安全并发的技术,在这种机制里线程(或Actor)通过彼此发送消息(数据)来进行通信。Go语言中有一句名言:“不要用共享内存来通信,要用通信来共享内存”,Go语言这种并发机制就体现了这个思想。

+

Rust也提供了一种基于消息传递的并发方式,在rust里使用标准库提供的Channel来实现。Channel包含发送端和接收端,我门可以通过调用发送端的方法来发送数据,接收端会检查和接收到达的数据。如果发送端和接收端的任意一端被丢弃了,那么Channel就关闭了。

+

二、使用Channel

+

2.1 在不同线程之间创建和接收数据

+

使用mpsc::channel函数来创建Channel,mpsc表示multiple producer, singer consumer(多个生产者、一个消费者),即有多个发送端,但只有一个接收端。调用该函数将返回一个元组,元组里的元素分别是发送端、接收端。如下示例代码

+
use std::sync::mpsc;
+use std::thread;
+
+fn main() {
+    let (tx, rx) = mpsc::channel();
+
+    thread::spawn(move || {
+        let val = String::from("hi");
+        tx.send(val).unwrap();
+    });
+
+    let received = rx.recv().unwrap();
+    println!("Got: {}", received);
+}
+
+

消费者的recv方法一直会阻塞当前线程,直到接收到消息为止。

+

2.2 发送端的send方法

+

该方法的参数为想要发送的数据,返回值为Result<T, E>,如果有问题(例如接收端已经被丢弃),将返回一个错误。

+

2.3 接收端的方法

+

recv方法阻止当前线程执行,直到Channel中有值被送来。一旦收到值,就会返回Result<T>,所有这个管道的所有发送端都关闭了,就会收到一个错误。

+

try_recv方法不会阻塞当前的线程,如果有数据到达,返回OK,里面包含着数据,否则返回错误。我们通常会使用循环来检查try_recv的结果,如果消息还没有来,我们也可以执行其他的操作。

+

2.4 channel和所有权转移

+

所有权先消息传递中非常重要,能帮你补全编写安全、并发的代码。我们先看以下的示例代码

+
use std::sync::mpsc;
+use std::thread;
+
+fn main() {
+    let (tx, rx) = mpsc::channel();
+
+    thread::spawn(move || {
+        let val = String::from("hi");
+        tx.send(val).unwrap();
+
+        // 下面一行代码将会报错,因为所有权已经被转移
+        println("val is {}", val);
+    });
+
+    let received = rx.recv().unwrap();
+    println!("Got: {}", received);
+}
+
+

在上面的示例代码中,借用了已移动的值,因此会发生编译错误。所以所有权机制会帮我我们编写编写安全、并发的代码。

+

2.5 发送多个值

+

我们通过发送多个值,就可以看到接收者在等待的过程。如下示例代码

+
use std::sync::mpsc;
+use std::{thread, vec};
+use std::time::Duration;
+
+fn main() {
+    let (tx, rx) = mpsc::channel();
+
+    thread::spawn(move || {
+        let vals = vec![
+            String::from("hi"),
+            String::from("from"),
+            String::from("the"),
+            String::from("thread"),
+        ];
+
+        // 循环分别发送四个字符串
+        for val in vals {
+            tx.send(val).unwrap();
+            thread::sleep(Duration::from_millis(1000));
+        }
+    });
+
+    // 我们把接收端当作迭代器来使用,这样就不需要显式调用recv方法
+    for received in rx {
+        println!("Got: {}", received);
+    }
+}
+
+

运行以上的代码,我们将看到接收端在等待消息的过程。

+

2.6 通过克隆创建多个发送者

+

通过调用mpsc::Sender::clone函数可以克隆发送者,如下示例代码

+
use std::sync::mpsc;
+use std::{thread, vec};
+use std::time::Duration;
+
+fn main() {
+    let (tx, rx) = mpsc::channel();
+
+    let tx1 = mpsc::Sender::clone(&tx);
+
+    thread::spawn(move || {
+        let vals = vec![
+            String::from("1: hi"),
+            String::from("1: from"),
+            String::from("1: the"),
+            String::from("1: thread"),
+        ];
+
+        // 循环分别发送四个字符串
+        for val in vals {
+            tx1.send(val).unwrap();
+            thread::sleep(Duration::from_millis(200));
+        }
+    });
+
+    thread::spawn(move || {
+        let vals = vec![
+            String::from("hi"),
+            String::from("from"),
+            String::from("the"),
+            String::from("thread"),
+        ];
+
+        // 循环分别发送四个字符串
+        for val in vals {
+            tx.send(val).unwrap();
+            thread::sleep(Duration::from_millis(200));
+        }
+    });
+
+    // 我们把接收端当作迭代器来使用,这样就不需要显式调用recv方法
+    for received in rx {
+        println!("Got: {}", received);
+    }
+}
+
+

在以上的示例代码中,我们通过两个子线程由两个发送者来发数据。并在主线程中使用接收者接收数据,可以通过程序运行结果看到由两个发送者发送的数据被交替输出。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/16_concurrency/03_shared_state_concurrency/index.html b/16_concurrency/03_shared_state_concurrency/index.html new file mode 100644 index 0000000..5d176d3 --- /dev/null +++ b/16_concurrency/03_shared_state_concurrency/index.html @@ -0,0 +1,3050 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 16.2   共享状态的并发 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+ +
+
+ + + +
+
+ + + + + + + + + + + + +

共享状态的并发

+

一、概述

+

“不要用共享的内存来通信,要用通信来共享内存”。实际上,在上节中我们就是使用通信的方式来实现并发的,在本节我们要使用共享内存的方式来实现并发。

+

rust支持通过共享状态来实现并发。channel类似于单所有权,一旦将值的所有权转移至channel,就无法使用它了。而共享内存的并发方式类似多所有权,多个线程可以同时访问一块内存。

+

在rust里使用Mutex来每次只允许一个线程来访问数据,是mutual exclusion(互斥锁)的简写。在同一时刻,Mutex只允许一个线程来访问某些数据。想要访问数据,线程必须首先获取互斥锁(lock)。lock数据结构是mutex的一部分,它能跟踪谁对数据拥有独占访问权。mutex通常被描述为:通过锁定系统来保护它锁持有的数据。

+

二、Mutext的两条规则

+
    +
  • 在使用数据之前,必须尝试获取锁(lock)。
  • +
  • 使用完mutex所保护的数据,必须对数据进行解锁,以便其他线程可以获取锁。
  • +
+

三、Mutex的API

+

通过Mutex::new(数据)来创建Mutex<T>Mutex<T>是一个只能指针,在访问数据之前,通过lock方法来获取锁。这个方法会阻塞当前线程的执行,返回的是MutexGuard(智能指针,实现了Deref和Drop),但lock方法可能会失败。如下示例代码

+
use std::sync::Mutex;
+
+fn main() {
+    let m = Mutex::new(5);
+
+    {
+        // 获取数据的可变引用
+        let mut num = m.lock().unwrap();
+        // 变更数据
+        *num = 6;
+    }
+    // 由于MutexGuard实现了 Drop trait,当作用域走完,m被自动解锁
+
+    println("m = {"?"}", m);
+}
+
+

四、多线程共享Mutex

+

在rust里使用Arc<T>来进行原子引用计数,Arc<T>Rc<T>类似,它可以用于并发场景。A是atomic的简称,即原子的。这时候你可以有一个问题,为什么所有的基础类型都不是原子的,为什么标准库类型不默认使用Arc<T>?因为需要付出性能代价。Arc<T>Rc<T>的API是相同的。如下示例代码

+
use std::rc::Rc;
+use std::sync::{Arc, Mutex};
+use std::thread;
+
+fn main() {
+    // 创建一个数据
+    let counter = Arc::new(Mutex::new(0));
+    let mut handles = vec![];
+
+    // 创建10个线程去修改数据,再把返回的10个JoinHandle放到handles中
+    for _ in 0..10 {
+        let counter = Arc::clone(&counter);
+
+        let handle = thread::spawn(move || {
+            let mut num = counter.lock().unwrap();
+
+            *num += 1;
+        });
+
+        handles.push(handle);
+    }
+
+    // 等待所有线程运行完成
+    for handle in handles {
+        handle.join().unwrap();
+    }
+
+    println!("Result: {}", *counter.lock().unwrap());
+}
+
+

最终运行结果为10

+

五、RefCell/Rc和Mutex/Arc

+

Mutext<T>提供了内部可变性,和Cell家族一样。我们可以使用RefCell<T>改变Rc<T>里面的内容,同样可以使用Mutex<T>来改变Arc<T>里面的内容。

+

但要注意的是,使用Rc<T>可能会造成循环引用,造成内存泄漏的风险;而使用Mutex<T>也有死锁的风险。所谓的死锁,就是当某个操作需要同时锁住两个资源,两个线程分别持有其中一个锁,并相互请求另外一个锁的时候,这两个线程就会陷入无穷无尽的等待。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/16_concurrency/04_sync_and_send/index.html b/16_concurrency/04_sync_and_send/index.html new file mode 100644 index 0000000..d84b02b --- /dev/null +++ b/16_concurrency/04_sync_and_send/index.html @@ -0,0 +1,2981 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 16.3   Sync和Send扩展并发 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

Sync和Send扩展并发

+

一、概述

+

rust语言本省的并发特性较少,目前将的并发特性都是来自标准库(而不是语言本身)。其实我们无需仅限于标准库的并发,可以自己实现并发。但在rust语言中有两个并发概念:std::marker::Syncstd::marker::Send这两个trait。

+

二、Send

+

Send:允许线程间转移所有权。实现Send trait的类型可以在线程间转移所有权,Rust中几乎所有的类型都实现了Send,但Rc<T>没有实现Send,它只用于单线程的场景。任何完全由Send组成的类型也被标记为Send,实际上除了原始指针之外,几乎所有的基础类型都实现了Send。

+

三、Sync

+

Sync:允许从多线程同时访问。实现Sync的类型可以安全的被多个线程引用,也就是说:如果T实现了Sync,那么&T(T的引用)就是实现了Send,表示T的引用可以被安全地送往另一个线程。基础类型都实现了Sync,完全由Sync类型组成的类型也是Sync,但是Rc<T>不是Sync,RefCell<T>Cell<T>家族也不是Sync,而Mutex<T>实现了Sync。

+

四、手动实现Send和Sync很难保证安全

+

手动来实现Send和Sync是很难保证安全的,因为手动实现这些trait的时候涉及到使用特殊不安全的Rust代码,你需要非常谨慎地确保设计,才能满足线程间的安全性要求。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/17_oop/01_oop_language_features/index.html b/17_oop/01_oop_language_features/index.html new file mode 100644 index 0000000..37416e2 --- /dev/null +++ b/17_oop/01_oop_language_features/index.html @@ -0,0 +1,2974 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 17.1   面向对象语言特性 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

面向对象语言特性

+

一、rust是面向对象语言吗?

+

Rust在设计的时候受到各种编程范式的影响,包括面向对象。面向对象通常包含以下特性:命名对象、封装、继承。

+

我们所说的对象包含数据和行为,“设计模式四人帮”在《设计模式》中给出面向对象的定义如下

+
    +
  • 面向对象的程序由对象组成
  • +
  • 对象包装了数据和操作这些数据的过程,这些过程通常被称为方法或操作
  • +
+

基于以上定义,rust是面向对象的。struct、enum包含数据,impl块为之提供了方法,但在rust里,带有方法的struct、enum并没有被称为对象。

+

二、封装

+

封装指的是:调用对象外部的代码无法直接访问对象内部的实现细节,唯一可以与对象进行交互的方法就是通过它公开的API。在rust里我们使用pub关键字来决定那些函数或方法是公开的,而默认情况下都是私有的。

+

三、继承

+

继承指的是:使对象可以沿用另外一个对象的数据和行为,且无需重复定义相关代码。

+

使用继承的其中一个原因通常是希望代码能够复用。不过rust没有继承,但在rust里我们使用默认的trait方法来实现代码的共享。因为在trait中,如果某个方法有默认实现(即默认的trait方法),那么任何实现了这个trait的类型就会自动拥有这个方法。此外,在实现trait的时候,选择覆盖trait里方法的默认实现,这就是面向对象中的子类覆盖父类方法类似。

+

使用继承的另一个原因是使用多态。多态是同一个行为具有多个不同表现形式或形态的能力,多态就是同一个接口,使用不同的实例而执行不同操作。换句话说就是如果一些对象拥有共同的特征,那么这个对象就可以在运行时相互替换。在rust中我们可以使用泛型来构建不同类型的抽象,并使用trait约束来决定类型必须提供的某些具体特性,这个技术有时候可以叫做限定参数多态化(bounded parametric)。

+

不过,现在很多编程语言都不使用继承作为内置的程序设计方案了。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/17_oop/02_trait_objects/index.html b/17_oop/02_trait_objects/index.html new file mode 100644 index 0000000..f648d37 --- /dev/null +++ b/17_oop/02_trait_objects/index.html @@ -0,0 +1,3001 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 17.2   使用trait存储不同类型值 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

使用trait存储不同类型值

+

一、概述

+

假设我们有这样的需求:创建一个GUI工具,它会遍历某个元素(指的是GUI原属)的列表,依次调用元素的draw方法进行绘制。例如Button、TextField等元素。

+

对于以上的需求,在面向对象的语言里,我们通常先定义一个Component父类,里面定义了draw方法,接下来定义各个元素的类(Button、TextField等),它们都继承于Component类,再覆盖draw方法。

+

而在rust里是没有继承功能的,所以说如果想用rust来构建这个GUI工具,我们就得使用其他的方法,即为共有的行为定义一个trait。

+

二、为共有行为定义一个trait

+

在rust里,我们避免将struct或enum称为对象,虽然它们是持有数据,但是它们的方法实现是在impl块里,而struct和enum和impl块是分开的。而trait对象有些类似于其他语言中的对象,因为trait对象在某种程度上实际上是组合了数据于行为。trait对象于传统的对象不同的地方在于,我们无法为trait对象添加数据。trait对象被专门用于抽象共有行为的,它没有其他语言中的对象那么通用。

+

我们将实现的示例代码存在src/s60_trait_save_different_val中。

+

三、trait对象执行的是动态派发

+

将trait约束作用于泛型时,rust编译器会执行单态化。编译器会为我们用来替换泛型类型参数的每一个具体类型生成对应函数和方法的非泛型实现。

+

通过单态化生成的代码会执行静态派发(static dispatch),在编译过程中确定调用的具体方法。

+

所谓的动态派发(dynamic dispatch),它无法在编译过程中确定你调用的究竟是哪个方法,编译时会产生额外的代码以便在运行时找出希望调用的方法。如果使用trait对象,就会执行动态派发,那么将会导致产生运行时开销,并且阻止编译器内联方法代码,使得部分优化无法进行。

+

三、Trait对象必须保证对象安全

+

只能把满足对象安全(object-safe)的trait转化为trait对象。rust采用了一系列规则来判定某个对象是否安全,我们只需要记住两条规则

+
    +
  • 方法的返回类型不是Self
  • +
  • 方法中不包含任何泛型类型参数
  • +
+

我们来看一个示例,在标准库中,Clone trait就是不符合对象安全的例子,Clone trait的clone方法如下

+
pub trait Clone {
+    fn clone(&self) -> Self;
+}
+
+

如果我们在“二”中的示例代码使用Clone trait作为 Screen 结构体 conponents 的对象,如下代码

+
pub struct Screen {
+    pub commponents: Vec<Box<dyn Clone>>
+}
+
+

程序将会报错,因为Clone trait的clone方法返回Self,换句话说,它不是对象安全的。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/17_oop/03_oop_design_patterns/index.html b/17_oop/03_oop_design_patterns/index.html new file mode 100644 index 0000000..e3cbc2d --- /dev/null +++ b/17_oop/03_oop_design_patterns/index.html @@ -0,0 +1,2934 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 17.3   实现面向对象的设计模式 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

实现面向对象的设计模式

+

一、状态模式

+

状态模式(state pattern)是一种面向对象的设计模式,一个值拥有的内部状态由数个状态对象(state object)表达而成,而值的行为则随着内部状态的改变而改变。

+

使用状态模式意味着:业务需求不断变化时,不需要修改持有状态的值的代码,或者使用这个值的代码。只需要更新状态对象内部的代码,以改变其规则。或者增加一些新的状态对象。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/18_pattern_matching/01_how_to_use_pattern_matching/index.html b/18_pattern_matching/01_how_to_use_pattern_matching/index.html new file mode 100644 index 0000000..55c0b20 --- /dev/null +++ b/18_pattern_matching/01_how_to_use_pattern_matching/index.html @@ -0,0 +1,3082 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 18.1   用到模式匹配的地方 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

用到模式匹配的地方

+

一、概述

+

模式匹配是rust的一种特殊语法,用于匹配复杂和简单类型的结构,将模式与匹配表达式和其他结构结合使用,可以更好地控制程序的控制流。模式由以下元素(的一些组合)组成

+
    +
  • 字面值
  • +
  • 解析的数组、enum、struct和tuple
  • +
  • 变量
  • +
  • 通配符
  • +
  • 占位符
  • +
+

想要使用模式,需要将其与某个值进行比较,如果模式匹配,就可以在代码中使用这个值的相应部分。下文将介绍rust中使用到模式匹配的地方。

+

二、match的Arm(分支)

+
match VALUE {
+    PATTERN => EXPRESSION,
+    PATTERN => EXPRESSION,
+    PATTERN => EXPRESSION,
+}
+
+

match表达式的要求是详尽,即包含所有的可能性。一个特殊的模式是_(下划线),它会匹配到任何东西,不会绑定到变量,所以通常用户match的最后一个分支,或者用于忽略某些值。

+

三、条件if let表达式

+

if let主要是作为一种简短的方式来等价的代替只有一个匹配项的match。if let可选可以拥有else,包括else ifelse if let,但是if let不会检查穷尽性。

+

四、while let条件循环

+

只要模式匹配继续满足匹配的条件,那它允许while循环一直运行。如下示例

+
fn main() {
+    let mut stack = Vec::new();
+
+    statck.push(1);
+    statck.push(2);
+    statck.push(3);
+
+    while let Some(top) = stack.pop() {
+        println("{}", top);
+    }
+}
+
+

五、for循环

+

for循环是rust中最常见的循环,for循环中,模式就是紧随for关键字后的值。如下示例代码

+
fn main() {
+    let v = vec!['a', 'b', 'c'];
+
+    for (index, value) in v.iter().enumerate() {
+        println("{} is at index {}", value, index);
+    }
+}
+
+

六、let语句

+

let语句也是模式,官方写法为let PATTERN = EXPRESSION;,如下示例代码

+
fn main() {
+    let a = 5;
+
+    let (x, y, z) = (1, 2, 3);
+}
+
+

七、函数参数

+

函数参数也是模式,如下示例代码

+
fn foo(x: i32) {
+    // code goes here
+}
+
+fn print_coordinates(&(x, y): &(i32, i32)) {
+    println("Current location: ({}, {})", x, y);
+}
+
+fn main() {
+    let point = (3, 5);
+    print_coordinates(&point);
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/18_pattern_matching/02_pattern_matching_failure/index.html b/18_pattern_matching/02_pattern_matching_failure/index.html new file mode 100644 index 0000000..982e679 --- /dev/null +++ b/18_pattern_matching/02_pattern_matching_failure/index.html @@ -0,0 +1,2904 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 18.2   模式匹配可失败性 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

模式匹配可失败性

+

模式的两种形式包括:可辩驳的(可反驳的或可失败的)、无可辩驳的(不可反驳的或不可失败的)。

+

能够匹配任何可能传递的值的模式是无可辩驳的,即不能失败,怎么匹配都会成功。例如let x = 5不可能失败,它能匹配所有表达式右侧的值。

+

对于某些可能的值,无法进行匹配的模式,就是可辩驳的。例如if let Some(x) = a_value,如果右边的值是null,这时候就会发生不匹配的情况,这就是可辩驳的,或者可失败的。

+

其中,函数参数、let语句、for循环只接受无可辩驳的模式。if letwhile let接受可辩驳和无可辩驳的模式。在接受无可辩驳模式的时候编译器可能会发生警告,因为存在可能的失败,如下例子

+
fn main() {
+    let a: Option<i32> = Some(5);
+    // Some是个可辩驳的
+    let Some(x) = a;
+}
+
+

a是Option类型,而Some是一个可辩驳的,以上代码将会编译错误,可以修改为如下代码

+
fn main() {
+    let a: Option<i32> = Some(5);
+    if let Some(x) = a {
+
+    }
+}
+
+

但是如果我们改为不可辩驳的模式,如下

+
fn main() {
+    let a: Option<i32> = Some(5);
+    if let x = 5 {}
+}
+
+

代码不会报错,但是x = 5总是匹配成功的,所以使用一个可辩驳的模式没有任何意义。结合上面的示例,我们可以想到match表达除了最后一个分支的所有分支必须是可辩驳的(即可失败的),而最后一个分支因为是不可辩驳的,因为它需要匹配所有剩余的情况。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/18_pattern_matching/03_match_grammer/index.html b/18_pattern_matching/03_match_grammer/index.html new file mode 100644 index 0000000..60a6fcd --- /dev/null +++ b/18_pattern_matching/03_match_grammer/index.html @@ -0,0 +1,3454 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 18.3   模式匹配语法 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

模式匹配的语法

+

一、匹配字面值

+

模式可以直接匹配字面值,如下例子

+
fn main() {
+    let x = 1;
+
+    match x {
+        1 => println("one"),
+        2 => println("two"),
+        3 => println("three"),
+        _ => println("anything"),
+    }
+}
+
+

二、匹配命名变量

+

命名的变量是可匹配任何值的无可辩驳模式,如下示例代码

+
fn main() {
+    let x = Some(5);
+    let y = 10;
+
+    match x {
+        Some(50) => println("Got 50"),
+        Some(y) => println("Matched, y = {:?}", y),
+        _ => println("Default case, x = {:?}", x),
+    }
+
+    println("at the end: x = {:?}, y = {:?}", x, y);
+}
+
+

三、多重模式

+

在match表达式中,使用|语法(就是或的意思),可以匹配多种模式,如下例子

+
fn main() {
+    let x = 1;
+
+    match x {
+        1 | 2 => println("one or two"),
+        3 => println("three"),
+        _ => println("anything"),
+    }
+}
+
+

四、使用..=来匹配某个范围的值

+

如下示例

+
fn main() {
+    let x = 5;
+    match x {
+        1..=5 => println("one through five"),
+        _ => println("something else"),
+    }
+
+    let x = 'c';
+    match x {
+        'a'..='j' => println("early ASCII letter"),
+        'k'..='z' => println("late ASCII letter"),
+        _ => println("something else"),
+    }
+}
+
+

五、结构以分解值

+

可以使用模式来解构struct、enum、tuple,从而引用这些类型值的不同部分。

+
struct Point {
+    x: i32,
+    y: i32,
+}
+
+fn main() {
+    let p = Point { x: 0, y: 7 };
+
+    let Point { x: a, y: b } = p;
+    assert_eq!(0, a);
+    assert_eq!(0, b);
+}
+
+

不过,以上在main的代码可以简写成如下

+
fn main() {
+    let p = Point { x: 0, y: 7 };
+
+    let Point { x, y } = p;
+    assert_eq!(0, x);
+    assert_eq!(0, y);
+}
+
+

我们还可以灵活地使用解构,如下代码

+
match p {
+    // 要求y的值是0,x随意
+    Point { x, y: 0 } => println("On the x axis at {}", x),
+    // 要求x的值是0,y随意
+    Point { x: 0, y } => println("On the y axis at {}", y),
+    // 如果前两个模式都无法匹配的话,将匹配最后一个
+    Point { x, y } => println("On neither axis: ({}, {})", x, y),
+}
+
+

六、解构枚举

+
enum Message {
+    Quit,
+    Move { x: i32, y: i32 },
+    Write(String),
+    ChangeColor(i32, i32, i32),
+}
+
+fn main() {
+    let msg = Message::ChangeColor(0, 160, 255);
+
+    match msg {
+        Message::Quit => {
+            println!("The Quit variant has no data to destructure.")
+        }
+        Message::Move { x, y } => {
+            println!("Move in the x direction {} and in the y direction {}.", x, y)
+        }
+        Message::Write(text) => println!("Text message: {}", text),
+        Message::ChangeColor(r, g, b) => {
+            println!("change the color to red {}, green {}, and blue {}", r, g, b)
+        }
+    }
+}
+
+

七、解构嵌套的struct和enum

+

示例代码如下

+
enum Color {
+    Rgb(i32, i32, i32),
+    Hsv(i32, i32, i32),
+}
+
+enum Message {
+    Quit,
+    Move { x: i32, y: i32 },
+    Write(String),
+    ChangeColor(Color),
+}
+
+fn main() {
+    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
+
+    match msg {
+        Message::ChangeColor(Color::Rgb(r, g, b)) => {
+            println!("change the color to red {}, green {}, and blue {}", r, g, b)
+        }
+        Message::ChangeColor(Color::Hsv(h, s, v)) => {
+            println!("change the color to hue {}, saturation {}, and value {}", h, s, v)
+        }
+        _ => (),
+    }
+}
+
+

八、解构struct和tuple

+
struct Point {
+    x: i32,
+    y: i32,
+}
+
+fn main() {
+    let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
+}
+
+

九、在模式中忽略值

+

有几种方式可以在模式中忽略整个值或部分值,如下

+
    +
  • _忽略整个值
  • +
  • _配合其他模式可以忽略部分值
  • +
  • _开头的名称
  • +
  • ..忽略值的剩余部分
  • +
+

9.1 使用“_”来忽略整个值

+

函数有两个参数,但是函数体中只用到第二个参数,如下示例代码

+
fn foo(_: i32, y: i32 ) {
+    prinltln!("This code only uses the y parameter: {}", y);
+}
+
+fn main() {
+    foo(3, 4);
+}
+
+

9.2 使用嵌套的“_”来忽略值的一部分

+

如下例子

+
fn main() {
+    let mut setting_value = Some(5);
+    let new_setting_value = Some(10);
+
+    match (setting_value, new_setting_value) {
+        // 内配的值:只要两个值都是Some即可
+        (Some(_), Some(_)) => {
+            println!("Can't overwrite an existing customized value");
+        }
+        _ => {
+            setting_value = new_setting_value 
+        }
+    }
+
+    println("setting is {:?}", setting_value);
+
+    let numbers = (2, 4, 6, 16, 32);
+
+    match numbers {
+        // 只需要元组的某部分值
+        (first, _, third, _, fifth) => {
+            println!("Some numbers: {}, {}, {}", first, third, fifth);
+        }
+    }
+}
+
+

9.3 通过使用“_”开头命名来忽略未使用的变量

+

正常情况下,创建一个变量,但是没有使用,rust编译器就会发出警告。有时候我们想要创建一个临时变量,但有不想让编译器发出警告,就可以使用下划线开头的方式。如下例子

+
fn main() {
+    let _x = 5;
+    ley y = 10;
+}
+
+

以上代码编译之后,x不会发生警告,而y会发出警告。我们再来看一个示例

+
fn mian() {
+    let s = Some(String::from("Hello!"));
+
+    if let Some(_s) = s {
+        println!("found a string");
+    }
+
+    println!("{:?}", s);
+}
+
+

以上代码编译将会发生报错,因为在嗲用最后的println!之前,s已经将所有权移动到了_s,如果将代码修改为如下内容得以解决问题

+
fn mian() {
+    let s = Some(String::from("Hello!"));
+
+    if let Some(_) = s {
+        println!("found a string");
+    }
+
+    println!("{:?}", s);
+}
+
+

9.4 使用..来忽略值的剩余部分

+

如下例子

+
struct Point {
+    x: i32,
+    y: i32,
+    z: i32,
+}
+
+fn main() {
+    let origin = Point { x: 0, y: 0, z: 0 };
+    match origin {
+        // 只需要用到struct的x字段
+        Point { x, .. } => println!("x is {}", x),
+    }
+
+    let numbers = (2, 4, 8, 16, 32);
+    match numbers {
+        // 只需要用到元组中的第一个数和最后一个数
+        (first, .., last) => {
+            println!("Some numbers: {}, {}", first, last);
+        }
+    }
+}
+
+

9.5 使用match守卫来提供额外的条件

+

match守卫就是match arm模式后额外的if条件,想要匹配该条件也必须满足,macth守卫适用于闭单独的模式更复杂的场景。如下示例

+
fn main() {
+    let num = Some(4);
+
+    match num {
+        Some(x) if x < 5 => println!("less than five: {}", x);
+        Some(x) => println("{}", x),
+        None => (),
+    }
+}
+
+

再有另外一个示例,如下代码

+
fn main() {
+    let x = Some(5);
+    let y = 10;
+
+    match y {
+        Some(50) => println!("Got 50");
+        Some(n) if n == y => println("Matchd, n = {}", n),
+        _ => println!("Default case, x = {:?}", x),
+    }
+
+    println!("at the end: x = {:?}, y = {:?}", x, y);
+}
+
+

我们还可以在多重模式中使用match守卫,如下示例

+
fn main() {
+    let x = 4;
+    let y = false;
+
+    match x {
+        4 | 5 | 6 if y => println("yes"),
+        _ => println!("no"),
+    }
+}
+
+

9.6 @绑定

+

@符号让我们可以创建一个变量,该变量可以在测试某个值是否与模式匹配的同时保存该值。如下示例代码

+
enum Message {
+    Hello { id: i32 },
+}
+
+fn main() {
+    let msg {
+        Message::Hello {
+            id: id_variable @ 3..=7,
+        } => {
+            println!("Found an id in range: {}", id_variable);
+        }
+        Message::Hello {
+            id: 10..=12,
+        } => {
+            println!("Found an id in another range");
+        }
+        Message::Hello { id } => {
+            println!("Found an other id: {}", id);
+        }
+    }
+}
+
+

最后输出结果为5,即变量id_variable存储了匹配的值。

+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/19_advanced_features/01_unsafe_rust/index.html b/19_advanced_features/01_unsafe_rust/index.html new file mode 100644 index 0000000..cc4a19d --- /dev/null +++ b/19_advanced_features/01_unsafe_rust/index.html @@ -0,0 +1,3243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 19.1   不安全Rust - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

不安全Rust

+

一、概述

+

rust隐藏着第二个语言,它没有强制内存安全保证,这就是 unsafe rust(不安全的rust)。实际上和普通的rust一样,但提供了额外的“超能力”。Unsafe Rust存在的原因如下

+

第一个原因是,静态分析是保守的,编译器在判断一段是否拥有安全保证时,它宁可错杀一些合法的程序,也不会接受仅仅是可能存在非法的代码,金尽管这些代码是安全的。而使用 Unsafe Rust,我们就是告诉编译器,我知道自己在做什么,我愿意自己承担这个风险。

+

第二个原因是,计算机硬件本身就是不安全的,Rust 需要能够进行底层系统编程,如果不允许 Unsafe Rust,那么这些工作就无法完成了。

+

二、Unsafe超能力

+

我们可以使用unsafe关键字来切换代码到 Unsafe Rust,开启一个代码块,里面存放着 unsafe 代码。Unsafe Rust 里可以执行四类动作(unsafe 超能力),如下

+
    +
  • 解引用原始指针
  • +
  • 调用unsafe函数或方法
  • +
  • 访问或修改可变的静态变量
  • +
  • 实现unsafe trait
  • +
+

需要注意的是,unsafe 并没有关闭借用检查或停用其他安全检查。任何内存安全相关的错误,我们必须留在unsafe块里。同时,我们应该尽可能隔离 unsafe 代码,最好将其封装在安全的抽象里,提供安全的API。

+

2.1 解引用原始指针

+

在不安全的rust里,有两种类似于引用的新型指针,它们就叫做原始指针或者裸指针。原始指针的写法如下

+
    +
  • 可变的:*mut T
  • +
  • 不可变的:*const T。意味着指针在解引用后不能直接对其进行赋值。
  • +
+

需要注意的是,这里的*不是解引用符号,它是类型名的一部分。与引用不同,原始指针允许通过同时具有不可变和可变指针或多个指向同一位置的可变指针来忽略借用规则。原始指针无法保证指向合理的内存,原始指针允许为null,原始指针也不实现任何的自动清理。

+

在放弃保证的安全,rust换取了更好的性能,以及与其他语言或硬件接口的能力。下面我们来看一下创建原始指针的例子

+
fn main() {
+    let mut num = 5;
+
+    // 以下两个指针来自有效的引用,所以以下两个指针也是有效的
+    // 不可变的原始指针
+    let r1 = &num as *const i32;
+    // 可变的原始指针
+    let r2 = &mut num as *const i32;
+
+    // 创建一个无法确定其有效性的指针
+    let address = 0x012345usize;// 内存地址中可能有数据,可能没有数据
+    let r = address as *const i32;// 创建未确定有效性的内存的原始指针
+}
+
+

我们可以在不安全代码之外创建原始指针,但是只能在不安全代码中对其进行解引用。如下示例代码

+
fn main() {
+    let mut num = 5;
+
+    // 以下两个指针来自有效的引用,所以以下两个指针也是有效的
+    // 不可变的原始指针
+    let r1 = &num as *const i32;
+    // 可变的原始指针
+    let r2 = &mut num as *const i32;
+
+    unsafe {
+        println!("r1: {}", *r1);
+        println!("r2: {}", *r2);
+    }
+
+    // 创建一个无法确定其有效性的指针
+    let address = 0x012345usize;// 内存地址中可能有数据,可能没有数据
+    let r = address as *const i32;// 创建未确定有效性的内存的原始指针
+    unsafe {
+        println!("r: {}", *r);
+    }
+}
+
+

在以上代码中,程序运行到第二个unsafe代码块时,发生了错误。因为我们指定的地址是有可能不存在的,如果出现错误,也有由我们自己来负责。

+

为什么要使用原始指针?目的是与C语言进行接口交互,另外可以构建借用检查器无法理解的安全抽象。

+

2.2 调用unsafe函数或方法

+

unsafe函数或方法指的是在函数或方法前加上了unsafe关键字,除此之外和其他函数或者方法没什么区别。但是在调用这种函数或者方法之前,需要手动满足一些条件,因为rust无法对这些条件进行验证。另外,想要调用unsafe的函数或者方法,必须在unsafe代码块里进行调用。如下示例

+
unsafe fn dangerous() {}
+
+fn main() {
+    unsafe {
+        dangerous();
+    }
+}
+
+

2.3 创建unsafe代码的安全抽象

+

函数包含unsafe代码并不需要将整个函数标记为unsafe,将unsafe代码包裹在安全函数中是一个常见的抽象。如下示例代码

+
use std::{slice, vec};
+
+// 不安全代码的安全抽象
+fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
+    let len = slice.len();
+    let ptr = slice.as_mut_ptr();
+
+    assert!(mid <= len);
+
+    unsafe {
+        (
+            slice::from_raw_parts_mut(ptr, mid),
+            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
+        )
+    }
+}
+
+fn main() {
+    let mut v = vec![1, 2, 3, 4, 5, 6];
+
+    let r = &mut v[..];
+    let (a, b) = r.split_at_mut(3);
+    assert_eq!(a, &mut [1, 2, 3]);
+    assert_eq!(b, &mut [4, 5, 6]);
+}
+
+

2.4 使用extern函数调用外部代码

+

extern关键字:简化创建和使用外部函数接口(FFI)的过程。外部函数接口(FFI,Foreign Function Interface),它允许一种编程语言定义函数,并让其他编程语言能调用这些函数。任何在extern块里声明的函数都是不安全的,因为其他语言并不会强制执行rust遵守的规则,而rust又无法对它们进行检查。所以在调用外部函数的过程中,如何保证程序安全的责任也落在了开发者身上。如下示例代码

+
// C 指明了外部函数使用的应用二进制接口(Application Binary Intreface),即ABI
+// ABI 是用于定义函数在汇编层面的调用方式
+extern "C" {
+    // 想要调用的外部函数的名称和签名
+    fn abs(input: i32) -> i32;
+}
+
+fn main() {
+    unsafe {
+        println!("Absolute value of -3 according to C: {}", abs(-3));
+    }
+}
+
+

"C" ABI是最常见的ABI,它遵循C语言的ABI。

+

2.5 从其他语言调用rust函数

+

可以使用extern创建接口,其他语言通过它们可以调用rust函数。在fn前添加extern关键字,并制定对应的ABI。还需要添加#[no_mangle]注解,目的是避免rust在编译时改变它们的名称。如下示例

+
#[no_mangle]
+pub extern "C" fn call_from_c() {
+    println!("Just called a Rust function from C");
+}
+
+fn main() {}
+
+

call_from_c函数在编译和链接之后就可以被C语言访问。

+

2.6 访问或修改一个可变静态变量

+

rust支持全局变量,但因为所有权机制可能产生某些问题,例如数据竞争。在rust里,全局变量叫做静态(static)变量,如下示例代码

+
static HELLO_WORLD: &str = "Hello, world!";
+
+fn main() {
+    println!("name is: {}", HELLO_WORLD);
+}
+
+

静态变量与常量类似,命名规范示例如:SCREAMING_SNAKE_CASE。声明的时候必须标注类型,静态变量只能存储'static声明周期的引用,无需显式标注。访问不可变的静态变量是安全的。常量与静态变量的区别如下

+
    +
  • 静态变量:有固定的内存地址,使用它的值总会访问同样的数据
  • +
  • 常量:允许使用它们的时候对数据进行赋值
  • +
+

静态变量是可变的,访问和修改静态可变变量是不安全(unsafe)的操作。如下示例代码

+
static mut COUNTER: u32 = 0;
+
+fn add_to_count(inc: u32) {
+    // 修改静态变量
+    unsafe {
+        COUNTER += inc;
+    }
+}
+
+fn main() {
+    add_to_count(3);
+
+    // 访问静态变量
+    unsafe {
+        println("COUNTER: {}", COUNTER);
+    }
+}
+
+

2.7 实现不安全的trait

+

当某个trait中存在至少一个方法拥有编译器无法校验的安全因素时,就称这个trait时不安全的,声明unsafe trait,需定义前加unsafe关键字。该trait只能在unsafe代码块中实现。如下示例代码

+
unsafe trait Foo {
+    // methods go here
+}
+
+unsafe impl Foo for i32 {
+    // method implements go here
+}
+
+fn main() {}
+
+

三、何时使用unsafe

+

使用unsafe代码的时候要考虑以下问题

+
    +
  • 使用unsafe代码时编译器无法保证内存安全,让开发者保证unsafe代码正确性并不简单。但有充足的理由使用unsafe代码时,就可以这样做
  • +
  • 通过显示标记unsafe,可以在出现问题时轻松定位
  • +
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/19_advanced_features/02_advanced_trait/index.html b/19_advanced_features/02_advanced_trait/index.html new file mode 100644 index 0000000..89789b6 --- /dev/null +++ b/19_advanced_features/02_advanced_trait/index.html @@ -0,0 +1,3258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 19.2   高级Trait - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

高级Trait

+

一、在rust中使用关联类型来制定占位类型

+

关联类型(assciated type)是trait中类型占位符,它可以用于trait的方法签名中。具体地说可以定义出包含某些类型的trait,而在实现前无需知道这些类型是什么。如下例子

+
pub trait Iterator {
+    type Item;
+
+    fn next(&mut self) -> Option<Self::Item>;
+}
+
+fn main() {
+    println!("Hello, world");
+}
+
+

二、关联类型与泛型的区别

+

每次实现泛型trait的时候必须标注具体的类型,而且可以为一个类型多次实现某个Trait(使用不同的泛型参数);而实现关联类型trait的时候,我们无需标注类型,但是要在里面指明具体的关联类型,而且我门无法为单个类型多次实现某个Trait。如下示例代码

+
// 关联类型Trait
+pub trait Iterator {
+    type Item;
+
+    fn next(&mut self) -> Option<Self::Item>;
+}
+
+// 泛型类型Trait
+pub trait Iterator2<T> {
+    fn next(&mut self) -> Option<T>;
+}
+
+// struct
+struct Counter {}
+
+// 实现关联类型的Trait
+impl Iterator for Counter {
+    type Item = u32;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        None
+    }
+}
+
+// 实现泛型类型的trait
+impl Iterator2<String> for Counter {
+
+    fn next(&mut self) -> Option<String> {
+        None
+    }
+}
+
+// 再次实现泛型类型的trait
+impl Iterator2<u32> for Counter {
+
+    fn next(&mut self) -> Option<u32> {
+        None
+    }
+}
+
+fn main() {
+    println!("Hello, world!");
+}
+
+

三、默认泛型参数和运算符重载

+

可以在使用泛型参数时为泛型指定一个默认的具体类型,语法为:<PlaceholderType=ConcreteType>,这种技术常用于运算符重载(operator overloading)。虽然Rust不允许创建自己的运算符以及重载任意的运算符,但可以通过实现std::ops中列出的那些trait来重载一部分相应的运算符。如下示例

+
use std::ops::Add;
+
+#[derive(Debug, PartiaEq)]
+struct Point {
+    x: i32,
+    y: i32,
+}
+
+impl Add for Point {
+    type Output = Point;
+
+    fn add(self, other: Point) -> Point {
+        Point {
+            x: self.x + other.x,
+            y: self.y + other.y,
+        }
+    }
+}
+
+fn main() {
+    assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
+              Point { x: 3, y: 3 });
+}
+
+

在上面的示例代码中,std::ops::Add trait 使用默认的泛型参数类型为 <Rhs = Self>,也就是说,在我们实现std::ops::Add trait的过程中,如果没有为Rhs指定一个具体的参数类型,那么Rhs类型就默认为Self,所以上面的代码中Rhs就是SelfPoint

+

下面我们做一个毫米和米相加的例子,就需要指定泛型参数类型,如下示例代码

+
use std::ops::Add;
+
+struct Millimeters(u32);
+struct Meters(u32);
+
+impl Add(Meters) for Millimeters {
+    type Output = Millimeters;
+
+    fn add(self, other: Point) -> Point {
+        Millimeters(self.0 + (other.o * 1000))
+    }
+}
+
+fn main() {
+
+}
+
+

四、默认泛型参数的主要应用场景

+

主要有两个应用场景,如下

+
    +
  • 扩展一个类型而不破坏现有的代码
  • +
  • 允许在大部分用户都不需要的特定场景下进行自定义
  • +
+

五、完全限定语法(Fully Qualified Syntax)与如何调用同名方法

+

我们看如下例子

+
trait Pilot {
+    fn fly(&self);
+}
+
+trait Wizard {
+    fn fly(&self);
+}
+
+struct Human;
+
+impl Pilot for Human {
+    fn fly(&self) {
+        println!("This is your caption speaking.")
+    }
+}
+
+impl Wizard for Human {
+    fn fly(&self) {
+        println!("Up!");
+    }
+}
+
+impl Human {
+    fn fly(&self) {
+        println!("*waving arms furiously*");
+    }
+}
+
+fn mian() {
+    let person = Human;
+    person.fly();
+}
+
+

在上面示例代码中,两个struct分别实现了Humantrait,实现了fly方法,并且Human trait有自己的fly方法。最后在main函数中调用了fly方法,那么会调用到哪个方法呢?在上面的示例代码中,将调用本身的fly方法。此时又有一个问题,那么如何调用实现了两个trait的方法,如下示例代码

+
fn mian() {
+    let person = Human;
+    // 调用本身的方法
+    person.fly();
+    // 调用Pilot的方法
+    Pilot::fly(&person);
+    // 调用Wizard的方法
+    Wizard::fly(&person);
+}
+
+

需要注意的是,Pilot可以被多个struct实现,那么如何知道为什么调的是Human的方法,是因为有self参数,传入的&person参数中它就是Human类型。我们再来看一个不一样的示例

+
trait Animal {
+    fn baby_name() -> String;
+}
+
+struct Dog;
+
+impl Dog {
+    fn baby_name() -> String {
+        String::from("Spot");
+    }
+}
+
+impl Animal for Dog {
+    fn baby_name() -> String {
+        String::from("puppy");
+    }
+}
+
+fn main() {
+    println!("A baby dog is called a {}", Dog::baby_name());
+}
+
+

在上面的示例代码中,Dog本身有baby_name关联方法,它输出的是“Spot”,那么如何调用Animal的方法呢?这时候我们可以使用完全限定语法。

+

完全限定语法的格式为:<Type as Trait>::function(receiver_if_method, next_arg, ...),这种语法可以在任何调用函数或方法的地方使用,并且它允许忽略那些从上下文能推导出来的部分。但要记住的是,当rust无法区分我们调用哪个具体实现的时候,才需使用这种语法,因为这种语法写起来比较麻烦,不该轻易使用。如下示例代码

+
// ...
+
+fn main() {
+    // 调用本身的方法
+    println!("A baby dog is called a {}", Dog::baby_name());
+    // 调用实现Animal的方法
+    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
+}
+
+

五、使用supertrait来要求trait附带其他trait的功能

+

其实相当于一个trait继承另外一个trait。有时候我们需要再一个trait中使用其他trait的功能,也就是说需要间接被依赖的trait也被实现,那个被间接依赖的trait就是当前trait的supertrait。加入我们希望一个trait有打印功能的方法,并希望打印的方法能够调用to_string方法,实现如下示例代码

+
use std::fmt;
+
+// 依赖于fmt::Display
+trait OutlinePrint: fmt::Display {
+    fn outline_print(&self) {
+        let output = self.to_string();
+        let len = output.len();
+        println!("{}", "*".repeat(len + 4));
+        println!("*{}*", "*".repeat(len + 2));
+        println!("* {} *", output);
+        println!("*{}*", " ".repeat(len + 2));
+        println!("{}", "*".repeat(len + 4));
+    }
+}
+
+struct Point {
+    x: i32,
+    y: i32,
+}
+
+// 为Point实现OutlinePrint
+impl OutlinePrint for Point {}
+
+// Point实现了OutlinePrint就必须实现fmt::Display
+impl fmt::Display for Point {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "({}, {})", self.x, self.y)
+    }
+}
+
+fn main() {}
+
+

六、使用newtype模式在外部类型上实现外部trait

+

孤儿规则:只有当trait或类型定义在本地包时,才能为该类型实现这个trait。我们可以使用newtype模式来绕过这规则,即使用tuple struct(元组结构体)创建一个新的类型放在本地。如我们想为Vec实现fmt::Display trait,而Vecfmt::Display都在定义在外部包中,所以我们无法直接为Vec实现fmt::Display。实现方案如下

+
use std::fmt;
+
+// 定义Wrapper元组结构体,包住Vec
+struct Wrapper(Vec<String>);
+
+// 为Wrapper实现Display
+impl fmt::Display for Wrapper {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        // 使用selef.0即取出vec
+        write!("[{}]", selef.0.join(", "));
+    }
+}
+
+fn main() {
+    let w = Wrapper(vec![String::from("hello"), String::from("world")]);
+    println!("w = {}", w);
+}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/19_advanced_features/03_advanced_type/index.html b/19_advanced_features/03_advanced_type/index.html new file mode 100644 index 0000000..4998608 --- /dev/null +++ b/19_advanced_features/03_advanced_type/index.html @@ -0,0 +1,3039 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 19.3   高级类型 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

高级类型

+

一、使用newtype模式实现类型安全和抽象

+

newtype模式有以下作用

+
    +
  • 用来静态的保证各种值之间不会混淆并表明值的单位
  • +
  • 为类型的某些细节提供抽象能力
  • +
  • 通过轻量级的封装来隐藏内部的实现细节
  • +
+

二、使用类型的别名创建类型同义词

+

Rust提供了类型别名的功能,为现有类型产生另外的名称(同义词),类型的别名并不是一个独立的类型,使用type关键字创建类型别名。主要用途是减少代码字符的重复。如下示例

+
type Kilometers = i32;
+
+fn main() {
+    let x: i32 = 5;
+    let y: Kilometers = 5;
+    println!("x + y = {}", x + y);
+}
+
+

三、Never类型

+

有一个名为!的特殊类型,它没有任何值,行话称之为空类型(empty type)。我们倾向于叫never类型,因为它在不返回的函数中充当返回类型。其中,不返回值的函数也叫发散函数(diverging function)。

+

四、动态大小和sized trait

+

rust需要在编译时确定为一个特定类型的值分配多少空间。在rust中存在动态大小的类型(Dynamically Sized Types, DST)的概念,他可以使我们在编写代码的时候使用只有在运行时才能确定大小的值。str是动态大小的类型(注意不是&str),特点是只有在运行时才能确定字符串的长度。如下列代码将无法正常工作

+
let s1:str = "Hello there!";
+let s2:str = "How's it going?";
+
+

rust在编译的时候需要确定某个类型究竟会占多少内存,二同一类型所有的值必须使用等量的内存,所以以上代码无法正常工作。所以需要使用字符串切片(&str)来解决,因为字符串切片存储的是str的地址以及str的长度,所以过去我们在程序中存储字符串的时候都是用&str

+

在rust中,存储动态大小类型的数据时,总会附带一些额外的元数据来存储动态信息的大小。即使用动态大小类型的时候总会把它的值存放在某种指针后面,如字符串切片。

+

五、另外一种动态大小的类型:trait

+

其实每一个trait都是一个动态大小的类型,可以通过名称对其引用。如为了将trait用作trait对象,必须将它放置在某种指针之后,例如&dyn TraitBox<dyn Trait>Rc<dyn trait>之后。

+

六、sized trait

+

为了处理动态大小的类型,rust提供一个Sized trait来确定类型的大小在编译时是否已知,在编译时可计算出大小的类型会自动实现这trait,rust还会为每一个泛型函数隐式的添加Sized约束。如下

+
fn generic<T>(t: T) {}
+
+

上面的函数会被隐式转为如下函数

+
fn generic<T: Sized>(t: T) {}
+
+

默认情况下,泛型函数只能被应用于编译时已经知道大小的类型,可以通过特殊语法解除这一限制,即?Sized trait约束。表示的是类型的不确定性,可能是Sized类型,可能不是Sized类型,此语法只能用在Sized上,不能用于其他trait,如下示例

+
fn generic<T: ?Sized>(t: &T) {}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/19_advanced_features/04_advanced_function/index.html b/19_advanced_features/04_advanced_function/index.html new file mode 100644 index 0000000..b5406cf --- /dev/null +++ b/19_advanced_features/04_advanced_function/index.html @@ -0,0 +1,3018 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 19.4   高级函数和闭包 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + +

高级函数和闭包

+

一、函数指针

+

在rust中,闭包可以传递给函数,还可以将函数传递给其他函数。在传递的过程中会被强制转换成fn类型,fn类型就是“函数指针”(function pointer)。如下

+
fn add_one(x: i32) -> i32 {
+    x + 1
+}
+
+fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
+    f(arg) + f(arg)
+}
+
+fn main() {
+    let answer = do_twice(add_one, 5);
+
+    println!("The answer is: {}", answer);
+}
+
+

二、函数指针与闭包的不同

+

闭包实现了三种闭包trait,而函数指针即fn是一个类型,不是一个trait。我们可以直接制定fn为参数类型,不用声明一个以内Fn trait为约束的泛型参数。

+

函数指针实现了3种闭包trait(Fn, FnMut, FnOnce),所以总是可以把函数指针用作参数传递给一个接收闭包的函数。也正是因为这个原因,我们更倾向于搭配闭包trait的泛型来编写函数,这样这个函数可以同时接收闭包和普通函数作为参数。

+

某些情景下,我们只想接收fn而不接收闭包,如与外部不支持闭包的代码交互:C函数。我们先看一个示例,将vec的每一个元素转换为String,如下代码

+
fn main() {
+    // 使用闭包
+    let list_of_numbers = vec![1, 2, 3];
+    let list_of_strings: Vec<String> = list_of_numbers
+    .iter()
+    .map(|i| i.to_string())
+    .collect();
+
+    // 使用函数
+    let list_of_numbers = vec![1, 2, 3];
+    let list_of_strings: Vec<String> = list_of_numbers
+    .iter()
+    .map(ToString::to_string)
+    .collect();
+}
+
+

我们再看一个示例,在创建一些枚举数据时,需传入一个数据,并返回一个值。我们可以把这种构造器也作为实现了闭包trait的函数指针来进行使用,如下示例代码

+
fn main() {
+    enum Status {
+        Value(u32),
+        Stop,
+    }
+
+    let v = Status::Value(2);
+
+    let list_of_statuses: Vec<Status> = 
+    (0u32..20)
+    .map(Status::Value)
+    .collect();
+}
+
+

三、返回闭包

+

闭包使用trait进行表达,无法在函数中直接返回一个闭包,因为rust无法推断出需要多少空间来存储一个返回的闭包,但是可以将一个实现了该trait的具体类型作为返回值。如下示例

+
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
+    Box::new(|x| x + 1)
+}
+
+fn main() {}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/19_advanced_features/04_macro/index.html b/19_advanced_features/04_macro/index.html new file mode 100644 index 0000000..8f7ea05 --- /dev/null +++ b/19_advanced_features/04_macro/index.html @@ -0,0 +1,3115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 19.5   宏 - Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 跳转至 + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + +

+

一、概述

+

宏在rust里指的是一组相关特性的集合称谓,包括使用macro_rules!构建的声明宏(declarative macro),以及3中过程宏,3种过程宏如下

+
    +
  • 自定义#[derive]宏,用于rust或enum,可以为其指定随derive属性添加的代码
  • +
  • 类似属性的宏,可以在任意条目上添加自定义属性
  • +
  • 类似函数的宏,看起来像函数调用,对其制定为参数的token进行操作
  • +
+

二、函数和宏的差别

+

本质上,宏是用来编写可以生成其他代码的代码(元编程,metaprogramming)。函数在定义签名时,必须声明参数的个数和类型,而宏可以处理可变的参数。编译器会在解释代码前把展开宏。宏的定义比函数的定义复杂得多,宏时难以阅读、理解、维护。在某个文件调用宏的时候必须提前定义宏或将宏引入当前作用域,而函数可以在任何位置定义并在任何位置使用。

+

三、macro_rules!声明宏

+

rust中最常见的宏形式:声明宏,类似于match的匹配模式,在定义声明宏时,需要使用macro_rules!。如下示例,是一个简化的vec!

+
// 以下标注意味着它所处的包被引入作用域后才可以使用,缺少下面标注的宏不能引入作用域
+#[macro_export]
+// 使用macro_rules!声明宏,宏的名称叫vec
+macro_rules! vec {
+// 大括号里是宏的定义体
+// 以下代码类似于match表达式,但是match表达式匹配的是值,这里匹配的是rust的代码结构
+
+// $( $x:expr ) 匹配任何的rust表达式,并命名为$x。
+// 后面紧跟的逗号匹配可能出现的逗号分隔符(,)出现在捕获的代码的后面
+// 星号(*)代表匹配0个或者多个星号之前的内容
+( $( $x:expr ), * ) => {
+        {
+            // 每次匹配,将会生成类似如下代码
+            let mut temp_vec = Vec::new();
+            $(
+                temp_vec.push($x);
+            )*
+            temp_vec
+        }
+    };
+}
+
+

那么对于let v: Vec<u32> = vec![1, 2, 3];这行代码,将会生成类似如下的代码

+
let mut temp_vec = Vec::new();
+temp_vec.push(1);
+temp_vec.push(2);
+temp_vec.push(3);
+
+

其实,macro_rules!存在一些奇怪的技术细节,所以rust团队正在致力于推出macro关键字的第二种声明宏,它和现在宏的工作方式有些类似,但是修复了某些可能的极端情况,而在更新之后,macro_rules!就会被标记为弃用。

+

其实针对宏,大多数程序员只是用,通常不会去编写宏,所以这块我们不再深入研究。

+

四、基于属性来生成代码的过程宏

+

这种形式的宏更像函数(或者某种形式的过程),它会接收并操作输入的rust代码,再生成另一些rust代码作为结果。一共有三种过程宏,如下

+
    +
  • 自定义派生
  • +
  • 属性宏
  • +
  • 函数宏
  • +
+

在创建过程宏时,宏定义必须单独放在它们自己的包中,并使用特殊的包类型。如下示例

+
use proc_macro;
+
+// some_attribute是一个用来制定过程宏的占位符 
+#[some_attribute]
+// 定义过程宏的函数,接收一个TokenStream(TokenStream属于proc_macro包下)
+// 产生一个TokenStream作为输出
+pub fn some_name(input: TokenStream) -> TokenStream {
+
+}
+
+

函数附带的属性决定了我们创建的是哪种过程宏,同一个包中可以拥有不同的过程宏。

+

4.1 自定义derive宏

+

我们通过一个示例简单介绍自定义宏,需求为:创建一个hello_macro包,定义一个拥有关联函数hello_macroHelloMacro trait,我们需要提供一个自动实现trait的过程宏。

+

这就使得用户在它们的类型上标注#[derive(HelloMacro)],进而得到hello_macro的默认实现。

+

实现过程比较复杂,代码实现过程暂时留空......

+

4.2 类似属性的宏

+

类似属性的宏可以叫属性宏,属性宏与自定义 derive 宏类似,它允许创建新的属性,但不是为 derive 属性生成代码。属性宏更加灵活,derive 只能用于struct和enum,而属性宏可以用于任意条目,例如函数。

+

下面是一个不完整的示例代码

+
// 在MVC的项目中,Controller通常存在这样的属性,用来做路由
+// 而route使用一个共同宏来定义
+#[route(GET, "/")]
+fn index() {}
+
+#[proc_macro_attribute]
+// 宏定义的函数签名如下,有两个TokenStream作为参数
+// 前面的attr: TokenStream对应“GET, "/"”,后面的item: TokenStream对应index函数体
+// 最后返回一个TokenStream
+pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {}
+
+

4.3 类似函数的宏

+

类似函数的宏又叫函数宏,函数宏的定义类似于函数调用的宏,但比普通的函数更加灵活。函数宏可以接受TokenStream作为参数,与另外两种宏的过程一样,在定义中使用rust代码来操作TokenStream。

+

下面是一个不完整的示例代码

+
// 我们定义一个能够解析SQL语句的宏
+let sql = sql!(SELECT * FROM posts WHERE id = 1);
+
+#[proc_macro]
+// 函数的签名如下,接受一个TokenStream,再返回相应功能的TokenStream
+pub fn sql(input: TokenStream) -> TokenStream {}
+
+ + + + + + +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/404.html b/404.html new file mode 100644 index 0000000..a120cd7 --- /dev/null +++ b/404.html @@ -0,0 +1,2789 @@ + + + + + + + + + + + + + + + + + + + + + + + Rust中文Wiki + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000..1cf13b9 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/img/favicon.ico b/assets/img/favicon.ico new file mode 100644 index 0000000..9e570f7 Binary files /dev/null and b/assets/img/favicon.ico differ diff --git a/assets/javascripts/bundle.4e0fa4ba.min.js b/assets/javascripts/bundle.4e0fa4ba.min.js new file mode 100644 index 0000000..328d22a --- /dev/null +++ b/assets/javascripts/bundle.4e0fa4ba.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var gi=Object.create;var dr=Object.defineProperty;var xi=Object.getOwnPropertyDescriptor;var yi=Object.getOwnPropertyNames,Ht=Object.getOwnPropertySymbols,Ei=Object.getPrototypeOf,hr=Object.prototype.hasOwnProperty,Xr=Object.prototype.propertyIsEnumerable;var Jr=(e,t,r)=>t in e?dr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,I=(e,t)=>{for(var r in t||(t={}))hr.call(t,r)&&Jr(e,r,t[r]);if(Ht)for(var r of Ht(t))Xr.call(t,r)&&Jr(e,r,t[r]);return e};var Zr=(e,t)=>{var r={};for(var o in e)hr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Ht)for(var o of Ht(e))t.indexOf(o)<0&&Xr.call(e,o)&&(r[o]=e[o]);return r};var br=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var wi=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of yi(t))!hr.call(e,n)&&n!==r&&dr(e,n,{get:()=>t[n],enumerable:!(o=xi(t,n))||o.enumerable});return e};var $t=(e,t,r)=>(r=e!=null?gi(Ei(e)):{},wi(t||!e||!e.__esModule?dr(r,"default",{value:e,enumerable:!0}):r,e));var to=br((vr,eo)=>{(function(e,t){typeof vr=="object"&&typeof eo!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(vr,function(){"use strict";function e(r){var o=!0,n=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(A){return!!(A&&A!==document&&A.nodeName!=="HTML"&&A.nodeName!=="BODY"&&"classList"in A&&"contains"in A.classList)}function c(A){var it=A.type,Ne=A.tagName;return!!(Ne==="INPUT"&&s[it]&&!A.readOnly||Ne==="TEXTAREA"&&!A.readOnly||A.isContentEditable)}function p(A){A.classList.contains("focus-visible")||(A.classList.add("focus-visible"),A.setAttribute("data-focus-visible-added",""))}function m(A){A.hasAttribute("data-focus-visible-added")&&(A.classList.remove("focus-visible"),A.removeAttribute("data-focus-visible-added"))}function f(A){A.metaKey||A.altKey||A.ctrlKey||(a(r.activeElement)&&p(r.activeElement),o=!0)}function u(A){o=!1}function d(A){a(A.target)&&(o||c(A.target))&&p(A.target)}function b(A){a(A.target)&&(A.target.classList.contains("focus-visible")||A.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),m(A.target))}function _(A){document.visibilityState==="hidden"&&(n&&(o=!0),re())}function re(){document.addEventListener("mousemove",Y),document.addEventListener("mousedown",Y),document.addEventListener("mouseup",Y),document.addEventListener("pointermove",Y),document.addEventListener("pointerdown",Y),document.addEventListener("pointerup",Y),document.addEventListener("touchmove",Y),document.addEventListener("touchstart",Y),document.addEventListener("touchend",Y)}function Z(){document.removeEventListener("mousemove",Y),document.removeEventListener("mousedown",Y),document.removeEventListener("mouseup",Y),document.removeEventListener("pointermove",Y),document.removeEventListener("pointerdown",Y),document.removeEventListener("pointerup",Y),document.removeEventListener("touchmove",Y),document.removeEventListener("touchstart",Y),document.removeEventListener("touchend",Y)}function Y(A){A.target.nodeName&&A.target.nodeName.toLowerCase()==="html"||(o=!1,Z())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",_,!0),re(),r.addEventListener("focus",d,!0),r.addEventListener("blur",b,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var Vr=br((Mt,Dr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Mt=="object"&&typeof Dr=="object"?Dr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Mt=="object"?Mt.ClipboardJS=r():t.ClipboardJS=r()})(Mt,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return vi}});var s=i(279),a=i.n(s),c=i(370),p=i.n(c),m=i(817),f=i.n(m);function u(F){try{return document.execCommand(F)}catch(S){return!1}}var d=function(S){var y=f()(S);return u("cut"),y},b=d;function _(F){var S=document.documentElement.getAttribute("dir")==="rtl",y=document.createElement("textarea");y.style.fontSize="12pt",y.style.border="0",y.style.padding="0",y.style.margin="0",y.style.position="absolute",y.style[S?"right":"left"]="-9999px";var R=window.pageYOffset||document.documentElement.scrollTop;return y.style.top="".concat(R,"px"),y.setAttribute("readonly",""),y.value=F,y}var re=function(S,y){var R=_(S);y.container.appendChild(R);var P=f()(R);return u("copy"),R.remove(),P},Z=function(S){var y=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},R="";return typeof S=="string"?R=re(S,y):S instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(S==null?void 0:S.type)?R=re(S.value,y):(R=f()(S),u("copy")),R},Y=Z;function A(F){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?A=function(y){return typeof y}:A=function(y){return y&&typeof Symbol=="function"&&y.constructor===Symbol&&y!==Symbol.prototype?"symbol":typeof y},A(F)}var it=function(){var S=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},y=S.action,R=y===void 0?"copy":y,P=S.container,q=S.target,Me=S.text;if(R!=="copy"&&R!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&A(q)==="object"&&q.nodeType===1){if(R==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(R==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Me)return Y(Me,{container:P});if(q)return R==="cut"?b(q):Y(q,{container:P})},Ne=it;function Ie(F){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Ie=function(y){return typeof y}:Ie=function(y){return y&&typeof Symbol=="function"&&y.constructor===Symbol&&y!==Symbol.prototype?"symbol":typeof y},Ie(F)}function pi(F,S){if(!(F instanceof S))throw new TypeError("Cannot call a class as a function")}function Gr(F,S){for(var y=0;y0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof P.action=="function"?P.action:this.defaultAction,this.target=typeof P.target=="function"?P.target:this.defaultTarget,this.text=typeof P.text=="function"?P.text:this.defaultText,this.container=Ie(P.container)==="object"?P.container:document.body}},{key:"listenClick",value:function(P){var q=this;this.listener=p()(P,"click",function(Me){return q.onClick(Me)})}},{key:"onClick",value:function(P){var q=P.delegateTarget||P.currentTarget,Me=this.action(q)||"copy",kt=Ne({action:Me,container:this.container,target:this.target(q),text:this.text(q)});this.emit(kt?"success":"error",{action:Me,text:kt,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(P){return ur("action",P)}},{key:"defaultTarget",value:function(P){var q=ur("target",P);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(P){return ur("text",P)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(P){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return Y(P,q)}},{key:"cut",value:function(P){return b(P)}},{key:"isSupported",value:function(){var P=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof P=="string"?[P]:P,Me=!!document.queryCommandSupported;return q.forEach(function(kt){Me=Me&&!!document.queryCommandSupported(kt)}),Me}}]),y}(a()),vi=bi},828:function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,c){for(;a&&a.nodeType!==n;){if(typeof a.matches=="function"&&a.matches(c))return a;a=a.parentNode}}o.exports=s},438:function(o,n,i){var s=i(828);function a(m,f,u,d,b){var _=p.apply(this,arguments);return m.addEventListener(u,_,b),{destroy:function(){m.removeEventListener(u,_,b)}}}function c(m,f,u,d,b){return typeof m.addEventListener=="function"?a.apply(null,arguments):typeof u=="function"?a.bind(null,document).apply(null,arguments):(typeof m=="string"&&(m=document.querySelectorAll(m)),Array.prototype.map.call(m,function(_){return a(_,f,u,d,b)}))}function p(m,f,u,d){return function(b){b.delegateTarget=s(b.target,f),b.delegateTarget&&d.call(m,b)}}o.exports=c},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}},370:function(o,n,i){var s=i(879),a=i(438);function c(u,d,b){if(!u&&!d&&!b)throw new Error("Missing required arguments");if(!s.string(d))throw new TypeError("Second argument must be a String");if(!s.fn(b))throw new TypeError("Third argument must be a Function");if(s.node(u))return p(u,d,b);if(s.nodeList(u))return m(u,d,b);if(s.string(u))return f(u,d,b);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function p(u,d,b){return u.addEventListener(d,b),{destroy:function(){u.removeEventListener(d,b)}}}function m(u,d,b){return Array.prototype.forEach.call(u,function(_){_.addEventListener(d,b)}),{destroy:function(){Array.prototype.forEach.call(u,function(_){_.removeEventListener(d,b)})}}}function f(u,d,b){return a(document.body,u,d,b)}o.exports=c},817:function(o){function n(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),p=document.createRange();p.selectNodeContents(i),c.removeAllRanges(),c.addRange(p),s=c.toString()}return s}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,s,a){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var c=this;function p(){c.off(i,p),s.apply(a,arguments)}return p._=s,this.on(i,p,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),c=0,p=a.length;for(c;c{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var _a=/["'&<>]/;Pn.exports=Aa;function Aa(e){var t=""+e,r=_a.exec(t);if(!r)return t;var o,n="",i=0,s=0;for(i=r.index;i0&&i[i.length-1])&&(p[0]===6||p[0]===2)){r=0;continue}if(p[0]===3&&(!i||p[1]>i[0]&&p[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function U(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],s;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(a){s={error:a}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(s)throw s.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||a(u,d)})})}function a(u,d){try{c(o[u](d))}catch(b){f(i[0][3],b)}}function c(u){u.value instanceof Ze?Promise.resolve(u.value.v).then(p,m):f(i[0][2],u)}function p(u){a("next",u)}function m(u){a("throw",u)}function f(u,d){u(d),i.shift(),i.length&&a(i[0][0],i[0][1])}}function no(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof Ee=="function"?Ee(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(s){return new Promise(function(a,c){s=e[i](s),n(a,c,s.done,s.value)})}}function n(i,s,a,c){Promise.resolve(c).then(function(p){i({value:p,done:a})},s)}}function C(e){return typeof e=="function"}function at(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var It=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function De(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Pe=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=Ee(s),c=a.next();!c.done;c=a.next()){var p=c.value;p.remove(this)}}catch(_){t={error:_}}finally{try{c&&!c.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var m=this.initialTeardown;if(C(m))try{m()}catch(_){i=_ instanceof It?_.errors:[_]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=Ee(f),d=u.next();!d.done;d=u.next()){var b=d.value;try{io(b)}catch(_){i=i!=null?i:[],_ instanceof It?i=D(D([],U(i)),U(_.errors)):i.push(_)}}}catch(_){o={error:_}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new It(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)io(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&De(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&De(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var xr=Pe.EMPTY;function Pt(e){return e instanceof Pe||e&&"closed"in e&&C(e.remove)&&C(e.add)&&C(e.unsubscribe)}function io(e){C(e)?e():e.unsubscribe()}var Le={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,s=n.isStopped,a=n.observers;return i||s?xr:(this.currentObservers=null,a.push(r),new Pe(function(){o.currentObservers=null,De(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,s=o.isStopped;n?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new j;return r.source=this,r},t.create=function(r,o){return new uo(r,o)},t}(j);var uo=function(e){ie(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:xr},t}(x);var yt={now:function(){return(yt.delegate||Date).now()},delegate:void 0};var Et=function(e){ie(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=yt);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,s=o._infiniteTimeWindow,a=o._timestampProvider,c=o._windowTime;n||(i.push(r),!s&&i.push(a.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,s=n._buffer,a=s.slice(),c=0;c0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=mt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var s=r.actions;o!=null&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==o&&(mt.cancelAnimationFrame(o),r._scheduled=void 0)},t}(Wt);var vo=function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o=this._scheduled;this._scheduled=void 0;var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t}(Ut);var Te=new vo(bo);var T=new j(function(e){return e.complete()});function Nt(e){return e&&C(e.schedule)}function Mr(e){return e[e.length-1]}function Qe(e){return C(Mr(e))?e.pop():void 0}function Oe(e){return Nt(Mr(e))?e.pop():void 0}function Dt(e,t){return typeof Mr(e)=="number"?e.pop():t}var lt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Vt(e){return C(e==null?void 0:e.then)}function zt(e){return C(e[pt])}function qt(e){return Symbol.asyncIterator&&C(e==null?void 0:e[Symbol.asyncIterator])}function Kt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function ki(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Qt=ki();function Yt(e){return C(e==null?void 0:e[Qt])}function Bt(e){return oo(this,arguments,function(){var r,o,n,i;return Rt(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,Ze(r.read())];case 3:return o=s.sent(),n=o.value,i=o.done,i?[4,Ze(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,Ze(n)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Gt(e){return C(e==null?void 0:e.getReader)}function W(e){if(e instanceof j)return e;if(e!=null){if(zt(e))return Hi(e);if(lt(e))return $i(e);if(Vt(e))return Ri(e);if(qt(e))return go(e);if(Yt(e))return Ii(e);if(Gt(e))return Pi(e)}throw Kt(e)}function Hi(e){return new j(function(t){var r=e[pt]();if(C(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function $i(e){return new j(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?L(function(n,i){return e(n,i,o)}):de,ge(1),r?He(t):Io(function(){return new Xt}))}}function Po(){for(var e=[],t=0;t=2,!0))}function le(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new x}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,c=a===void 0?!0:a;return function(p){var m,f,u,d=0,b=!1,_=!1,re=function(){f==null||f.unsubscribe(),f=void 0},Z=function(){re(),m=u=void 0,b=_=!1},Y=function(){var A=m;Z(),A==null||A.unsubscribe()};return g(function(A,it){d++,!_&&!b&&re();var Ne=u=u!=null?u:r();it.add(function(){d--,d===0&&!_&&!b&&(f=kr(Y,c))}),Ne.subscribe(it),!m&&d>0&&(m=new tt({next:function(Ie){return Ne.next(Ie)},error:function(Ie){_=!0,re(),f=kr(Z,n,Ie),Ne.error(Ie)},complete:function(){b=!0,re(),f=kr(Z,s),Ne.complete()}}),W(A).subscribe(m))})(p)}}function kr(e,t){for(var r=[],o=2;oe.next(document)),e}function z(e,t=document){return Array.from(t.querySelectorAll(e))}function N(e,t=document){let r=ce(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ce(e,t=document){return t.querySelector(e)||void 0}function Re(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}var ea=M(h(document.body,"focusin"),h(document.body,"focusout")).pipe(ke(1),V(void 0),l(()=>Re()||document.body),B(1));function er(e){return ea.pipe(l(t=>e.contains(t)),G())}function Je(e){return{x:e.offsetLeft,y:e.offsetTop}}function Uo(e){return M(h(window,"load"),h(window,"resize")).pipe(Ae(0,Te),l(()=>Je(e)),V(Je(e)))}function tr(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return M(h(e,"scroll"),h(window,"resize")).pipe(Ae(0,Te),l(()=>tr(e)),V(tr(e)))}function No(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)No(e,r)}function O(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)No(o,n);return o}function rr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function ht(e){let t=O("script",{src:e});return $(()=>(document.head.appendChild(t),M(h(t,"load"),h(t,"error").pipe(v(()=>St(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),k(()=>document.head.removeChild(t)),ge(1))))}var Do=new x,ta=$(()=>typeof ResizeObserver=="undefined"?ht("https://unpkg.com/resize-observer-polyfill"):H(void 0)).pipe(l(()=>new ResizeObserver(e=>{for(let t of e)Do.next(t)})),v(e=>M(Ve,H(e)).pipe(k(()=>e.disconnect()))),B(1));function he(e){return{width:e.offsetWidth,height:e.offsetHeight}}function xe(e){return ta.pipe(w(t=>t.observe(e)),v(t=>Do.pipe(L(({target:r})=>r===e),k(()=>t.unobserve(e)),l(()=>he(e)))),V(he(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function or(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var Vo=new x,ra=$(()=>H(new IntersectionObserver(e=>{for(let t of e)Vo.next(t)},{threshold:0}))).pipe(v(e=>M(Ve,H(e)).pipe(k(()=>e.disconnect()))),B(1));function nr(e){return ra.pipe(w(t=>t.observe(e)),v(t=>Vo.pipe(L(({target:r})=>r===e),k(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function zo(e,t=16){return dt(e).pipe(l(({y:r})=>{let o=he(e),n=bt(e);return r>=n.height-o.height-t}),G())}var ir={drawer:N("[data-md-toggle=drawer]"),search:N("[data-md-toggle=search]")};function qo(e){return ir[e].checked}function Ke(e,t){ir[e].checked!==t&&ir[e].click()}function We(e){let t=ir[e];return h(t,"change").pipe(l(()=>t.checked),V(t.checked))}function oa(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function na(){return M(h(window,"compositionstart").pipe(l(()=>!0)),h(window,"compositionend").pipe(l(()=>!1))).pipe(V(!1))}function Ko(){let e=h(window,"keydown").pipe(L(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:qo("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),L(({mode:t,type:r})=>{if(t==="global"){let o=Re();if(typeof o!="undefined")return!oa(o,r)}return!0}),le());return na().pipe(v(t=>t?T:e))}function fe(){return new URL(location.href)}function ot(e){location.href=e.href}function Qo(){return new x}function Yo(){return location.hash.slice(1)}function Pr(e){let t=O("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function ia(e){return M(h(window,"hashchange"),e).pipe(l(Yo),V(Yo()),L(t=>t.length>0),B(1))}function Bo(e){return ia(e).pipe(l(t=>ce(`[id="${t}"]`)),L(t=>typeof t!="undefined"))}function Fr(e){let t=matchMedia(e);return Zt(r=>t.addListener(()=>r(t.matches))).pipe(V(t.matches))}function Go(){let e=matchMedia("print");return M(h(window,"beforeprint").pipe(l(()=>!0)),h(window,"afterprint").pipe(l(()=>!1))).pipe(V(e.matches))}function jr(e,t){return e.pipe(v(r=>r?t():T))}function ar(e,t={credentials:"same-origin"}){return me(fetch(`${e}`,t)).pipe(pe(()=>T),v(r=>r.status!==200?St(()=>new Error(r.statusText)):H(r)))}function Ue(e,t){return ar(e,t).pipe(v(r=>r.json()),B(1))}function Jo(e,t){let r=new DOMParser;return ar(e,t).pipe(v(o=>o.text()),l(o=>r.parseFromString(o,"text/xml")),B(1))}function Xo(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function Zo(){return M(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(l(Xo),V(Xo()))}function en(){return{width:innerWidth,height:innerHeight}}function tn(){return h(window,"resize",{passive:!0}).pipe(l(en),V(en()))}function rn(){return Q([Zo(),tn()]).pipe(l(([e,t])=>({offset:e,size:t})),B(1))}function sr(e,{viewport$:t,header$:r}){let o=t.pipe(X("size")),n=Q([o,r]).pipe(l(()=>Je(e)));return Q([r,t,n]).pipe(l(([{height:i},{offset:s,size:a},{x:c,y:p}])=>({offset:{x:s.x-c,y:s.y-p+i},size:a})))}function aa(e){return h(e,"message",t=>t.data)}function sa(e){let t=new x;return t.subscribe(r=>e.postMessage(r)),t}function on(e,t=new Worker(e)){let r=aa(t),o=sa(t),n=new x;n.subscribe(o);let i=o.pipe(J(),ee(!0));return n.pipe(J(),qe(r.pipe(K(i))),le())}var ca=N("#__config"),vt=JSON.parse(ca.textContent);vt.base=`${new URL(vt.base,fe())}`;function ue(){return vt}function te(e){return vt.features.includes(e)}function be(e,t){return typeof t!="undefined"?vt.translations[e].replace("#",t.toString()):vt.translations[e]}function ye(e,t=document){return N(`[data-md-component=${e}]`,t)}function ne(e,t=document){return z(`[data-md-component=${e}]`,t)}function pa(e){let t=N(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(l(()=>N(".md-typeset",e)),l(r=>({hash:__md_hash(r.innerHTML)})))}function nn(e){if(!te("announce.dismiss")||!e.childElementCount)return T;if(!e.hidden){let t=N(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return $(()=>{let t=new x;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),pa(e).pipe(w(r=>t.next(r)),k(()=>t.complete()),l(r=>I({ref:e},r)))})}function ma(e,{target$:t}){return t.pipe(l(r=>({hidden:r!==e})))}function an(e,t){let r=new x;return r.subscribe(({hidden:o})=>{e.hidden=o}),ma(e,t).pipe(w(o=>r.next(o)),k(()=>r.complete()),l(o=>I({ref:e},o)))}function la(e,t){let r=$(()=>Q([Uo(e),dt(t)])).pipe(l(([{x:o,y:n},i])=>{let{width:s,height:a}=he(e);return{x:o-i.x+s/2,y:n-i.y+a/2}}));return er(e).pipe(v(o=>r.pipe(l(n=>({active:o,offset:n})),ge(+!o||1/0))))}function sn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return $(()=>{let i=new x,s=i.pipe(J(),ee(!0));return i.subscribe({next({offset:a}){e.style.setProperty("--md-tooltip-x",`${a.x}px`),e.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),nr(e).pipe(K(s)).subscribe(a=>{e.toggleAttribute("data-md-visible",a)}),M(i.pipe(L(({active:a})=>a)),i.pipe(ke(250),L(({active:a})=>!a))).subscribe({next({active:a}){a?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Ae(16,Te)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(Rr(125,Te),L(()=>!!e.offsetParent),l(()=>e.offsetParent.getBoundingClientRect()),l(({x:a})=>a)).subscribe({next(a){a?e.style.setProperty("--md-tooltip-0",`${-a}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(K(s),L(a=>!(a.metaKey||a.ctrlKey))).subscribe(a=>{a.stopPropagation(),a.preventDefault()}),h(n,"mousedown").pipe(K(s),oe(i)).subscribe(([a,{active:c}])=>{var p;if(a.button!==0||a.metaKey||a.ctrlKey)a.preventDefault();else if(c){a.preventDefault();let m=e.parentElement.closest(".md-annotation");m instanceof HTMLElement?m.focus():(p=Re())==null||p.blur()}}),r.pipe(K(s),L(a=>a===o),ze(125)).subscribe(()=>e.focus()),la(e,t).pipe(w(a=>i.next(a)),k(()=>i.complete()),l(a=>I({ref:e},a)))})}function Wr(e){return O("div",{class:"md-tooltip",id:e},O("div",{class:"md-tooltip__inner md-typeset"}))}function cn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return O("aside",{class:"md-annotation",tabIndex:0},Wr(t),O("a",{href:r,class:"md-annotation__index",tabIndex:-1},O("span",{"data-md-annotation-id":e})))}else return O("aside",{class:"md-annotation",tabIndex:0},Wr(t),O("span",{class:"md-annotation__index",tabIndex:-1},O("span",{"data-md-annotation-id":e})))}function pn(e){return O("button",{class:"md-clipboard md-icon",title:be("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}function Ur(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(c=>!e.terms[c]).reduce((c,p)=>[...c,O("del",null,p)," "],[]).slice(0,-1),i=ue(),s=new URL(e.location,i.base);te("search.highlight")&&s.searchParams.set("h",Object.entries(e.terms).filter(([,c])=>c).reduce((c,[p])=>`${c} ${p}`.trim(),""));let{tags:a}=ue();return O("a",{href:`${s}`,class:"md-search-result__link",tabIndex:-1},O("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&O("div",{class:"md-search-result__icon md-icon"}),r>0&&O("h1",null,e.title),r<=0&&O("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&e.tags.map(c=>{let p=a?c in a?`md-tag-icon md-tag--${a[c]}`:"md-tag-icon":"";return O("span",{class:`md-tag ${p}`},c)}),o>0&&n.length>0&&O("p",{class:"md-search-result__terms"},be("search.result.term.missing"),": ",...n)))}function mn(e){let t=e[0].score,r=[...e],o=ue(),n=r.findIndex(m=>!`${new URL(m.location,o.base)}`.includes("#")),[i]=r.splice(n,1),s=r.findIndex(m=>m.scoreUr(m,1)),...c.length?[O("details",{class:"md-search-result__more"},O("summary",{tabIndex:-1},O("div",null,c.length>0&&c.length===1?be("search.result.more.one"):be("search.result.more.other",c.length))),...c.map(m=>Ur(m,1)))]:[]];return O("li",{class:"md-search-result__item"},p)}function ln(e){return O("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>O("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?rr(r):r)))}function Nr(e){let t=`tabbed-control tabbed-control--${e}`;return O("div",{class:t,hidden:!0},O("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function fn(e){return O("div",{class:"md-typeset__scrollwrap"},O("div",{class:"md-typeset__table"},e))}function fa(e){let t=ue(),r=new URL(`../${e.version}/`,t.base);return O("li",{class:"md-version__item"},O("a",{href:`${r}`,class:"md-version__link"},e.title))}function un(e,t){return O("div",{class:"md-version"},O("button",{class:"md-version__current","aria-label":be("select.version")},t.title),O("ul",{class:"md-version__list"},e.map(fa)))}function ua(e){return e.tagName==="CODE"?z(".c, .c1, .cm",e):[e]}function da(e){let t=[];for(let r of ua(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let s;for(;s=/(\(\d+\))(!)?/.exec(i.textContent);){let[,a,c]=s;if(typeof c=="undefined"){let p=i.splitText(s.index);i=p.splitText(a.length),t.push(p)}else{i.textContent=a,t.push(i);break}}}}return t}function dn(e,t){t.append(...Array.from(e.childNodes))}function cr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,s=new Map;for(let a of da(t)){let[,c]=a.textContent.match(/\((\d+)\)/);ce(`:scope > li:nth-child(${c})`,e)&&(s.set(c,cn(c,i)),a.replaceWith(s.get(c)))}return s.size===0?T:$(()=>{let a=new x,c=a.pipe(J(),ee(!0)),p=[];for(let[m,f]of s)p.push([N(".md-typeset",f),N(`:scope > li:nth-child(${m})`,e)]);return o.pipe(K(c)).subscribe(m=>{e.hidden=!m,e.classList.toggle("md-annotation-list",m);for(let[f,u]of p)m?dn(f,u):dn(u,f)}),M(...[...s].map(([,m])=>sn(m,t,{target$:r}))).pipe(k(()=>a.complete()),le())})}function hn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return hn(t)}}function bn(e,t){return $(()=>{let r=hn(e);return typeof r!="undefined"?cr(r,e,t):T})}var gn=$t(Vr());var ha=0;function xn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return xn(t)}}function vn(e){return xe(e).pipe(l(({width:t})=>({scrollable:bt(e).width>t})),X("scrollable"))}function yn(e,t){let{matches:r}=matchMedia("(hover)"),o=$(()=>{let n=new x;if(n.subscribe(({scrollable:s})=>{s&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")}),gn.default.isSupported()&&(e.closest(".copy")||te("content.code.copy")&&!e.closest(".no-copy"))){let s=e.closest("pre");s.id=`__code_${ha++}`,s.insertBefore(pn(s.id),e)}let i=e.closest(".highlight");if(i instanceof HTMLElement){let s=xn(i);if(typeof s!="undefined"&&(i.classList.contains("annotate")||te("content.code.annotate"))){let a=cr(s,e,t);return vn(e).pipe(w(c=>n.next(c)),k(()=>n.complete()),l(c=>I({ref:e},c)),qe(xe(i).pipe(l(({width:c,height:p})=>c&&p),G(),v(c=>c?a:T))))}}return vn(e).pipe(w(s=>n.next(s)),k(()=>n.complete()),l(s=>I({ref:e},s)))});return te("content.lazy")?nr(e).pipe(L(n=>n),ge(1),v(()=>o)):o}function ba(e,{target$:t,print$:r}){let o=!0;return M(t.pipe(l(n=>n.closest("details:not([open])")),L(n=>e===n),l(()=>({action:"open",reveal:!0}))),r.pipe(L(n=>n||!o),w(()=>o=e.open),l(n=>({action:n?"open":"close"}))))}function En(e,t){return $(()=>{let r=new x;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),ba(e,t).pipe(w(o=>r.next(o)),k(()=>r.complete()),l(o=>I({ref:e},o)))})}var wn=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel rect,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel rect{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color);stroke-width:.05rem}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}.attributeBoxEven,.attributeBoxOdd{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityBox{fill:var(--md-mermaid-label-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityLabel{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.relationshipLabelBox{fill:var(--md-mermaid-label-bg-color);fill-opacity:1;background-color:var(--md-mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--md-mermaid-label-fg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--md-mermaid-edge-color)!important}defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--md-mermaid-label-bg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var zr,ga=0;function xa(){return typeof mermaid=="undefined"||mermaid instanceof Element?ht("https://unpkg.com/mermaid@9.4.3/dist/mermaid.min.js"):H(void 0)}function Sn(e){return e.classList.remove("mermaid"),zr||(zr=xa().pipe(w(()=>mermaid.initialize({startOnLoad:!1,themeCSS:wn,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),l(()=>{}),B(1))),zr.subscribe(()=>{e.classList.add("mermaid");let t=`__mermaid_${ga++}`,r=O("div",{class:"mermaid"}),o=e.textContent;mermaid.mermaidAPI.render(t,o,(n,i)=>{let s=r.attachShadow({mode:"closed"});s.innerHTML=n,e.replaceWith(r),i==null||i(s)})}),zr.pipe(l(()=>({ref:e})))}var Tn=O("table");function On(e){return e.replaceWith(Tn),Tn.replaceWith(fn(e)),H({ref:e})}function ya(e){let t=z(":scope > input",e),r=t.find(o=>o.checked)||t[0];return M(...t.map(o=>h(o,"change").pipe(l(()=>N(`label[for="${o.id}"]`))))).pipe(V(N(`label[for="${r.id}"]`)),l(o=>({active:o})))}function Mn(e,{viewport$:t}){let r=Nr("prev");e.append(r);let o=Nr("next");e.append(o);let n=N(".tabbed-labels",e);return $(()=>{let i=new x,s=i.pipe(J(),ee(!0));return Q([i,xe(e)]).pipe(Ae(1,Te),K(s)).subscribe({next([{active:a},c]){let p=Je(a),{width:m}=he(a);e.style.setProperty("--md-indicator-x",`${p.x}px`),e.style.setProperty("--md-indicator-width",`${m}px`);let f=tr(n);(p.xf.x+c.width)&&n.scrollTo({left:Math.max(0,p.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),Q([dt(n),xe(n)]).pipe(K(s)).subscribe(([a,c])=>{let p=bt(n);r.hidden=a.x<16,o.hidden=a.x>p.width-c.width-16}),M(h(r,"click").pipe(l(()=>-1)),h(o,"click").pipe(l(()=>1))).pipe(K(s)).subscribe(a=>{let{width:c}=he(n);n.scrollBy({left:c*a,behavior:"smooth"})}),te("content.tabs.link")&&i.pipe(je(1),oe(t)).subscribe(([{active:a},{offset:c}])=>{let p=a.innerText.trim();if(a.hasAttribute("data-md-switching"))a.removeAttribute("data-md-switching");else{let m=e.offsetTop-c.y;for(let u of z("[data-tabs]"))for(let d of z(":scope > input",u)){let b=N(`label[for="${d.id}"]`);if(b!==a&&b.innerText.trim()===p){b.setAttribute("data-md-switching",""),d.click();break}}window.scrollTo({top:e.offsetTop-m});let f=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([p,...f])])}}),i.pipe(K(s)).subscribe(()=>{for(let a of z("audio, video",e))a.pause()}),ya(e).pipe(w(a=>i.next(a)),k(()=>i.complete()),l(a=>I({ref:e},a)))}).pipe(rt(ae))}function Ln(e,{viewport$:t,target$:r,print$:o}){return M(...z(".annotate:not(.highlight)",e).map(n=>bn(n,{target$:r,print$:o})),...z("pre:not(.mermaid) > code",e).map(n=>yn(n,{target$:r,print$:o})),...z("pre.mermaid",e).map(n=>Sn(n)),...z("table:not([class])",e).map(n=>On(n)),...z("details",e).map(n=>En(n,{target$:r,print$:o})),...z("[data-tabs]",e).map(n=>Mn(n,{viewport$:t})))}function Ea(e,{alert$:t}){return t.pipe(v(r=>M(H(!0),H(!1).pipe(ze(2e3))).pipe(l(o=>({message:r,active:o})))))}function _n(e,t){let r=N(".md-typeset",e);return $(()=>{let o=new x;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Ea(e,t).pipe(w(n=>o.next(n)),k(()=>o.complete()),l(n=>I({ref:e},n)))})}function wa({viewport$:e}){if(!te("header.autohide"))return H(!1);let t=e.pipe(l(({offset:{y:n}})=>n),Ce(2,1),l(([n,i])=>[nMath.abs(i-n.y)>100),l(([,[n]])=>n),G()),o=We("search");return Q([e,o]).pipe(l(([{offset:n},i])=>n.y>400&&!i),G(),v(n=>n?r:H(!1)),V(!1))}function An(e,t){return $(()=>Q([xe(e),wa(t)])).pipe(l(([{height:r},o])=>({height:r,hidden:o})),G((r,o)=>r.height===o.height&&r.hidden===o.hidden),B(1))}function Cn(e,{header$:t,main$:r}){return $(()=>{let o=new x,n=o.pipe(J(),ee(!0));return o.pipe(X("active"),Ge(t)).subscribe(([{active:i},{hidden:s}])=>{e.classList.toggle("md-header--shadow",i&&!s),e.hidden=s}),r.subscribe(o),t.pipe(K(n),l(i=>I({ref:e},i)))})}function Sa(e,{viewport$:t,header$:r}){return sr(e,{viewport$:t,header$:r}).pipe(l(({offset:{y:o}})=>{let{height:n}=he(e);return{active:o>=n}}),X("active"))}function kn(e,t){return $(()=>{let r=new x;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=ce(".md-content h1");return typeof o=="undefined"?T:Sa(o,t).pipe(w(n=>r.next(n)),k(()=>r.complete()),l(n=>I({ref:e},n)))})}function Hn(e,{viewport$:t,header$:r}){let o=r.pipe(l(({height:i})=>i),G()),n=o.pipe(v(()=>xe(e).pipe(l(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),X("bottom"))));return Q([o,n,t]).pipe(l(([i,{top:s,bottom:a},{offset:{y:c},size:{height:p}}])=>(p=Math.max(0,p-Math.max(0,s-c,i)-Math.max(0,p+c-a)),{offset:s-i,height:p,active:s-i<=c})),G((i,s)=>i.offset===s.offset&&i.height===s.height&&i.active===s.active))}function Ta(e){let t=__md_get("__palette")||{index:e.findIndex(r=>matchMedia(r.getAttribute("data-md-color-media")).matches)};return H(...e).pipe(se(r=>h(r,"change").pipe(l(()=>r))),V(e[Math.max(0,t.index)]),l(r=>({index:e.indexOf(r),color:{scheme:r.getAttribute("data-md-color-scheme"),primary:r.getAttribute("data-md-color-primary"),accent:r.getAttribute("data-md-color-accent")}})),B(1))}function $n(e){let t=O("meta",{name:"theme-color"});document.head.appendChild(t);let r=O("meta",{name:"color-scheme"});return document.head.appendChild(r),$(()=>{let o=new x;o.subscribe(i=>{document.body.setAttribute("data-md-color-switching","");for(let[s,a]of Object.entries(i.color))document.body.setAttribute(`data-md-color-${s}`,a);for(let s=0;s{let i=ye("header"),s=window.getComputedStyle(i);return r.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(a=>(+a).toString(16).padStart(2,"0")).join("")})).subscribe(i=>t.content=`#${i}`),o.pipe(_e(ae)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")});let n=z("input",e);return Ta(n).pipe(w(i=>o.next(i)),k(()=>o.complete()),l(i=>I({ref:e},i)))})}var qr=$t(Vr());function Oa(e){e.setAttribute("data-md-copying","");let t=e.innerText;return e.removeAttribute("data-md-copying"),t}function Rn({alert$:e}){qr.default.isSupported()&&new j(t=>{new qr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||Oa(N(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(w(t=>{t.trigger.focus()}),l(()=>be("clipboard.copied"))).subscribe(e)}function Ma(e){if(e.length<2)return[""];let[t,r]=[...e].sort((n,i)=>n.length-i.length).map(n=>n.replace(/[^/]+$/,"")),o=0;if(t===r)o=t.length;else for(;t.charCodeAt(o)===r.charCodeAt(o);)o++;return e.map(n=>n.replace(t.slice(0,o),""))}function pr(e){let t=__md_get("__sitemap",sessionStorage,e);if(t)return H(t);{let r=ue();return Jo(new URL("sitemap.xml",e||r.base)).pipe(l(o=>Ma(z("loc",o).map(n=>n.textContent))),pe(()=>T),He([]),w(o=>__md_set("__sitemap",o,sessionStorage,e)))}}function In({location$:e,viewport$:t}){let r=ue();if(location.protocol==="file:")return T;let o=pr().pipe(l(p=>p.map(m=>`${new URL(m,r.base)}`))),n=h(document.body,"click").pipe(oe(o),v(([p,m])=>{if(!(p.target instanceof Element))return T;let f=p.target.closest("a");if(f===null)return T;if(f.target||p.metaKey||p.ctrlKey)return T;let u=new URL(f.href);return u.search=u.hash="",m.includes(`${u}`)?(p.preventDefault(),H(new URL(f.href))):T}),le());n.pipe(ge(1)).subscribe(()=>{let p=ce("link[rel=icon]");typeof p!="undefined"&&(p.href=p.href)}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),n.pipe(oe(t)).subscribe(([p,{offset:m}])=>{history.scrollRestoration="manual",history.replaceState(m,""),history.pushState(null,"",p)}),n.subscribe(e);let i=e.pipe(V(fe()),X("pathname"),je(1),v(p=>ar(p).pipe(pe(()=>(ot(p),T))))),s=new DOMParser,a=i.pipe(v(p=>p.text()),v(p=>{let m=s.parseFromString(p,"text/html");for(let u of["title","link[rel=canonical]","meta[name=author]","meta[name=description]","[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...te("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let d=ce(u),b=ce(u,m);typeof d!="undefined"&&typeof b!="undefined"&&d.replaceWith(b)}let f=ye("container");return Fe(z("script",f)).pipe(v(u=>{let d=m.createElement("script");if(u.src){for(let b of u.getAttributeNames())d.setAttribute(b,u.getAttribute(b));return u.replaceWith(d),new j(b=>{d.onload=()=>b.complete()})}else return d.textContent=u.textContent,u.replaceWith(d),T}),J(),ee(m))}),le());return h(window,"popstate").pipe(l(fe)).subscribe(e),e.pipe(V(fe()),Ce(2,1),v(([p,m])=>p.pathname===m.pathname&&p.hash!==m.hash?H(m):T)).subscribe(p=>{var m,f;history.state!==null||!p.hash?window.scrollTo(0,(f=(m=history.state)==null?void 0:m.y)!=null?f:0):(history.scrollRestoration="auto",Pr(p.hash),history.scrollRestoration="manual")}),a.pipe(oe(e)).subscribe(([,p])=>{var m,f;history.state!==null||!p.hash?window.scrollTo(0,(f=(m=history.state)==null?void 0:m.y)!=null?f:0):Pr(p.hash)}),a.pipe(v(()=>t),X("offset"),ke(100)).subscribe(({offset:p})=>{history.replaceState(p,"")}),a}var jn=$t(Fn());function Wn(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,s)=>`${i}${s}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return s=>(0,jn.default)(s).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function Lt(e){return e.type===1}function mr(e){return e.type===3}function Un(e,t){let r=on(e);return M(H(location.protocol!=="file:"),We("search")).pipe($e(o=>o),v(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:te("search.suggest")}}})),r}function Nn({document$:e}){let t=ue(),r=Ue(new URL("../versions.json",t.base)).pipe(pe(()=>T)),o=r.pipe(l(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:s,aliases:a})=>s===i||a.includes(i))||n[0]}));r.pipe(l(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),v(n=>h(document.body,"click").pipe(L(i=>!i.metaKey&&!i.ctrlKey),oe(o),v(([i,s])=>{if(i.target instanceof Element){let a=i.target.closest("a");if(a&&!a.target&&n.has(a.href)){let c=a.href;return!i.target.closest(".md-version")&&n.get(c)===s?T:(i.preventDefault(),H(c))}}return T}),v(i=>{let{version:s}=n.get(i);return pr(new URL(i)).pipe(l(a=>{let p=fe().href.replace(t.base,"");return a.includes(p.split("#")[0])?new URL(`../${s}/${p}`,t.base):new URL(i)}))})))).subscribe(n=>ot(n)),Q([r,o]).subscribe(([n,i])=>{N(".md-header__topic").appendChild(un(n,i))}),e.pipe(v(()=>o)).subscribe(n=>{var s;let i=__md_get("__outdated",sessionStorage);if(i===null){i=!0;let a=((s=t.version)==null?void 0:s.default)||"latest";Array.isArray(a)||(a=[a]);e:for(let c of a)for(let p of n.aliases)if(new RegExp(c,"i").test(p)){i=!1;break e}__md_set("__outdated",i,sessionStorage)}if(i)for(let a of ne("outdated"))a.hidden=!1})}function ka(e,{worker$:t}){let{searchParams:r}=fe();r.has("q")&&(Ke("search",!0),e.value=r.get("q"),e.focus(),We("search").pipe($e(i=>!i)).subscribe(()=>{let i=new URL(location.href);i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=er(e),n=M(t.pipe($e(Lt)),h(e,"keyup"),o).pipe(l(()=>e.value),G());return Q([n,o]).pipe(l(([i,s])=>({value:i,focus:s})),B(1))}function Dn(e,{worker$:t}){let r=new x,o=r.pipe(J(),ee(!0));Q([t.pipe($e(Lt)),r],(i,s)=>s).pipe(X("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(X("focus")).subscribe(({focus:i})=>{i&&Ke("search",i)}),h(e.form,"reset").pipe(K(o)).subscribe(()=>e.focus());let n=N("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ka(e,{worker$:t}).pipe(w(i=>r.next(i)),k(()=>r.complete()),l(i=>I({ref:e},i)),B(1))}function Vn(e,{worker$:t,query$:r}){let o=new x,n=zo(e.parentElement).pipe(L(Boolean)),i=e.parentElement,s=N(":scope > :first-child",e),a=N(":scope > :last-child",e);We("search").subscribe(m=>a.setAttribute("role",m?"list":"presentation")),o.pipe(oe(r),Hr(t.pipe($e(Lt)))).subscribe(([{items:m},{value:f}])=>{switch(m.length){case 0:s.textContent=f.length?be("search.result.none"):be("search.result.placeholder");break;case 1:s.textContent=be("search.result.one");break;default:let u=rr(m.length);s.textContent=be("search.result.other",u)}});let c=o.pipe(w(()=>a.innerHTML=""),v(({items:m})=>M(H(...m.slice(0,10)),H(...m.slice(10)).pipe(Ce(4),Ir(n),v(([f])=>f)))),l(mn),le());return c.subscribe(m=>a.appendChild(m)),c.pipe(se(m=>{let f=ce("details",m);return typeof f=="undefined"?T:h(f,"toggle").pipe(K(o),l(()=>f))})).subscribe(m=>{m.open===!1&&m.offsetTop<=i.scrollTop&&i.scrollTo({top:m.offsetTop})}),t.pipe(L(mr),l(({data:m})=>m)).pipe(w(m=>o.next(m)),k(()=>o.complete()),l(m=>I({ref:e},m)))}function Ha(e,{query$:t}){return t.pipe(l(({value:r})=>{let o=fe();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function zn(e,t){let r=new x,o=r.pipe(J(),ee(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(K(o)).subscribe(n=>n.preventDefault()),Ha(e,t).pipe(w(n=>r.next(n)),k(()=>r.complete()),l(n=>I({ref:e},n)))}function qn(e,{worker$:t,keyboard$:r}){let o=new x,n=ye("search-query"),i=M(h(n,"keydown"),h(n,"focus")).pipe(_e(ae),l(()=>n.value),G());return o.pipe(Ge(i),l(([{suggest:a},c])=>{let p=c.split(/([\s-]+)/);if(a!=null&&a.length&&p[p.length-1]){let m=a[a.length-1];m.startsWith(p[p.length-1])&&(p[p.length-1]=m)}else p.length=0;return p})).subscribe(a=>e.innerHTML=a.join("").replace(/\s/g," ")),r.pipe(L(({mode:a})=>a==="search")).subscribe(a=>{switch(a.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(L(mr),l(({data:a})=>a)).pipe(w(a=>o.next(a)),k(()=>o.complete()),l(()=>({ref:e})))}function Kn(e,{index$:t,keyboard$:r}){let o=ue();try{let n=Un(o.search,t),i=ye("search-query",e),s=ye("search-result",e);h(e,"click").pipe(L(({target:c})=>c instanceof Element&&!!c.closest("a"))).subscribe(()=>Ke("search",!1)),r.pipe(L(({mode:c})=>c==="search")).subscribe(c=>{let p=Re();switch(c.type){case"Enter":if(p===i){let m=new Map;for(let f of z(":first-child [href]",s)){let u=f.firstElementChild;m.set(f,parseFloat(u.getAttribute("data-md-score")))}if(m.size){let[[f]]=[...m].sort(([,u],[,d])=>d-u);f.click()}c.claim()}break;case"Escape":case"Tab":Ke("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof p=="undefined")i.focus();else{let m=[i,...z(":not(details) > [href], summary, details[open] [href]",s)],f=Math.max(0,(Math.max(0,m.indexOf(p))+m.length+(c.type==="ArrowUp"?-1:1))%m.length);m[f].focus()}c.claim();break;default:i!==Re()&&i.focus()}}),r.pipe(L(({mode:c})=>c==="global")).subscribe(c=>{switch(c.type){case"f":case"s":case"/":i.focus(),i.select(),c.claim();break}});let a=Dn(i,{worker$:n});return M(a,Vn(s,{worker$:n,query$:a})).pipe(qe(...ne("search-share",e).map(c=>zn(c,{query$:a})),...ne("search-suggest",e).map(c=>qn(c,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,Ve}}function Qn(e,{index$:t,location$:r}){return Q([t,r.pipe(V(fe()),L(o=>!!o.searchParams.get("h")))]).pipe(l(([o,n])=>Wn(o.config)(n.searchParams.get("h"))),l(o=>{var s;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let a=i.nextNode();a;a=i.nextNode())if((s=a.parentElement)!=null&&s.offsetHeight){let c=a.textContent,p=o(c);p.length>c.length&&n.set(a,p)}for(let[a,c]of n){let{childNodes:p}=O("span",null,c);a.replaceWith(...Array.from(p))}return{ref:e,nodes:n}}))}function $a(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return Q([r,t]).pipe(l(([{offset:i,height:s},{offset:{y:a}}])=>(s=s+Math.min(n,Math.max(0,a-i))-n,{height:s,locked:a>=i+n})),G((i,s)=>i.height===s.height&&i.locked===s.locked))}function Kr(e,o){var n=o,{header$:t}=n,r=Zr(n,["header$"]);let i=N(".md-sidebar__scrollwrap",e),{y:s}=Je(i);return $(()=>{let a=new x,c=a.pipe(J(),ee(!0)),p=a.pipe(Ae(0,Te));return p.pipe(oe(t)).subscribe({next([{height:m},{height:f}]){i.style.height=`${m-2*s}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),p.pipe($e()).subscribe(()=>{for(let m of z(".md-nav__link--active[href]",e)){let f=or(m);if(typeof f!="undefined"){let u=m.offsetTop-f.offsetTop,{height:d}=he(f);f.scrollTo({top:u-d/2})}}}),me(z("label[tabindex]",e)).pipe(se(m=>h(m,"click").pipe(l(()=>m),K(c)))).subscribe(m=>{let f=N(`[id="${m.htmlFor}"]`);N(`[aria-labelledby="${m.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),$a(e,r).pipe(w(m=>a.next(m)),k(()=>a.complete()),l(m=>I({ref:e},m)))})}function Yn(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return Tt(Ue(`${r}/releases/latest`).pipe(pe(()=>T),l(o=>({version:o.tag_name})),He({})),Ue(r).pipe(pe(()=>T),l(o=>({stars:o.stargazers_count,forks:o.forks_count})),He({}))).pipe(l(([o,n])=>I(I({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return Ue(r).pipe(l(o=>({repositories:o.public_repos})),He({}))}}function Bn(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return Ue(r).pipe(pe(()=>T),l(({star_count:o,forks_count:n})=>({stars:o,forks:n})),He({}))}function Gn(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return Yn(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return Bn(r,o)}return T}var Ra;function Ia(e){return Ra||(Ra=$(()=>{let t=__md_get("__source",sessionStorage);if(t)return H(t);if(ne("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return T}return Gn(e.href).pipe(w(o=>__md_set("__source",o,sessionStorage)))}).pipe(pe(()=>T),L(t=>Object.keys(t).length>0),l(t=>({facts:t})),B(1)))}function Jn(e){let t=N(":scope > :last-child",e);return $(()=>{let r=new x;return r.subscribe(({facts:o})=>{t.appendChild(ln(o)),t.classList.add("md-source__repository--active")}),Ia(e).pipe(w(o=>r.next(o)),k(()=>r.complete()),l(o=>I({ref:e},o)))})}function Pa(e,{viewport$:t,header$:r}){return xe(document.body).pipe(v(()=>sr(e,{header$:r,viewport$:t})),l(({offset:{y:o}})=>({hidden:o>=10})),X("hidden"))}function Xn(e,t){return $(()=>{let r=new x;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(te("navigation.tabs.sticky")?H({hidden:!1}):Pa(e,t)).pipe(w(o=>r.next(o)),k(()=>r.complete()),l(o=>I({ref:e},o)))})}function Fa(e,{viewport$:t,header$:r}){let o=new Map,n=z("[href^=\\#]",e);for(let a of n){let c=decodeURIComponent(a.hash.substring(1)),p=ce(`[id="${c}"]`);typeof p!="undefined"&&o.set(a,p)}let i=r.pipe(X("height"),l(({height:a})=>{let c=ye("main"),p=N(":scope > :first-child",c);return a+.8*(p.offsetTop-c.offsetTop)}),le());return xe(document.body).pipe(X("height"),v(a=>$(()=>{let c=[];return H([...o].reduce((p,[m,f])=>{for(;c.length&&o.get(c[c.length-1]).tagName>=f.tagName;)c.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return p.set([...c=[...c,m]].reverse(),u)},new Map))}).pipe(l(c=>new Map([...c].sort(([,p],[,m])=>p-m))),Ge(i),v(([c,p])=>t.pipe(Cr(([m,f],{offset:{y:u},size:d})=>{let b=u+d.height>=Math.floor(a.height);for(;f.length;){let[,_]=f[0];if(_-p=u&&!b)f=[m.pop(),...f];else break}return[m,f]},[[],[...c]]),G((m,f)=>m[0]===f[0]&&m[1]===f[1])))))).pipe(l(([a,c])=>({prev:a.map(([p])=>p),next:c.map(([p])=>p)})),V({prev:[],next:[]}),Ce(2,1),l(([a,c])=>a.prev.length{let i=new x,s=i.pipe(J(),ee(!0));if(i.subscribe(({prev:a,next:c})=>{for(let[p]of c)p.classList.remove("md-nav__link--passed"),p.classList.remove("md-nav__link--active");for(let[p,[m]]of a.entries())m.classList.add("md-nav__link--passed"),m.classList.toggle("md-nav__link--active",p===a.length-1)}),te("toc.follow")){let a=M(t.pipe(ke(1),l(()=>{})),t.pipe(ke(250),l(()=>"smooth")));i.pipe(L(({prev:c})=>c.length>0),Ge(o.pipe(_e(ae))),oe(a)).subscribe(([[{prev:c}],p])=>{let[m]=c[c.length-1];if(m.offsetHeight){let f=or(m);if(typeof f!="undefined"){let u=m.offsetTop-f.offsetTop,{height:d}=he(f);f.scrollTo({top:u-d/2,behavior:p})}}})}return te("navigation.tracking")&&t.pipe(K(s),X("offset"),ke(250),je(1),K(n.pipe(je(1))),Ot({delay:250}),oe(i)).subscribe(([,{prev:a}])=>{let c=fe(),p=a[a.length-1];if(p&&p.length){let[m]=p,{hash:f}=new URL(m.href);c.hash!==f&&(c.hash=f,history.replaceState({},"",`${c}`))}else c.hash="",history.replaceState({},"",`${c}`)}),Fa(e,{viewport$:t,header$:r}).pipe(w(a=>i.next(a)),k(()=>i.complete()),l(a=>I({ref:e},a)))})}function ja(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(l(({offset:{y:s}})=>s),Ce(2,1),l(([s,a])=>s>a&&a>0),G()),i=r.pipe(l(({active:s})=>s));return Q([i,n]).pipe(l(([s,a])=>!(s&&a)),G(),K(o.pipe(je(1))),ee(!0),Ot({delay:250}),l(s=>({hidden:s})))}function ei(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new x,s=i.pipe(J(),ee(!0));return i.subscribe({next({hidden:a}){e.hidden=a,a?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(K(s),X("height")).subscribe(({height:a})=>{e.style.top=`${a+16}px`}),h(e,"click").subscribe(a=>{a.preventDefault(),window.scrollTo({top:0})}),ja(e,{viewport$:t,main$:o,target$:n}).pipe(w(a=>i.next(a)),k(()=>i.complete()),l(a=>I({ref:e},a)))}function ti({document$:e,tablet$:t}){e.pipe(v(()=>z(".md-toggle--indeterminate")),w(r=>{r.indeterminate=!0,r.checked=!1}),se(r=>h(r,"change").pipe($r(()=>r.classList.contains("md-toggle--indeterminate")),l(()=>r))),oe(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function Wa(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function ri({document$:e}){e.pipe(v(()=>z("[data-md-scrollfix]")),w(t=>t.removeAttribute("data-md-scrollfix")),L(Wa),se(t=>h(t,"touchstart").pipe(l(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function oi({viewport$:e,tablet$:t}){Q([We("search"),t]).pipe(l(([r,o])=>r&&!o),v(r=>H(r).pipe(ze(r?400:100))),oe(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function Ua(){return location.protocol==="file:"?ht(`${new URL("search/search_index.js",Qr.base)}`).pipe(l(()=>__index),B(1)):Ue(new URL("search/search_index.json",Qr.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var nt=Wo(),At=Qo(),gt=Bo(At),Yr=Ko(),Se=rn(),lr=Fr("(min-width: 960px)"),ii=Fr("(min-width: 1220px)"),ai=Go(),Qr=ue(),si=document.forms.namedItem("search")?Ua():Ve,Br=new x;Rn({alert$:Br});te("navigation.instant")&&In({location$:At,viewport$:Se}).subscribe(nt);var ni;((ni=Qr.version)==null?void 0:ni.provider)==="mike"&&Nn({document$:nt});M(At,gt).pipe(ze(125)).subscribe(()=>{Ke("drawer",!1),Ke("search",!1)});Yr.pipe(L(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=ce("link[rel=prev]");typeof t!="undefined"&&ot(t);break;case"n":case".":let r=ce("link[rel=next]");typeof r!="undefined"&&ot(r);break;case"Enter":let o=Re();o instanceof HTMLLabelElement&&o.click()}});ti({document$:nt,tablet$:lr});ri({document$:nt});oi({viewport$:Se,tablet$:lr});var Xe=An(ye("header"),{viewport$:Se}),_t=nt.pipe(l(()=>ye("main")),v(e=>Hn(e,{viewport$:Se,header$:Xe})),B(1)),Na=M(...ne("consent").map(e=>an(e,{target$:gt})),...ne("dialog").map(e=>_n(e,{alert$:Br})),...ne("header").map(e=>Cn(e,{viewport$:Se,header$:Xe,main$:_t})),...ne("palette").map(e=>$n(e)),...ne("search").map(e=>Kn(e,{index$:si,keyboard$:Yr})),...ne("source").map(e=>Jn(e))),Da=$(()=>M(...ne("announce").map(e=>nn(e)),...ne("content").map(e=>Ln(e,{viewport$:Se,target$:gt,print$:ai})),...ne("content").map(e=>te("search.highlight")?Qn(e,{index$:si,location$:At}):T),...ne("header-title").map(e=>kn(e,{viewport$:Se,header$:Xe})),...ne("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?jr(ii,()=>Kr(e,{viewport$:Se,header$:Xe,main$:_t})):jr(lr,()=>Kr(e,{viewport$:Se,header$:Xe,main$:_t}))),...ne("tabs").map(e=>Xn(e,{viewport$:Se,header$:Xe})),...ne("toc").map(e=>Zn(e,{viewport$:Se,header$:Xe,main$:_t,target$:gt})),...ne("top").map(e=>ei(e,{viewport$:Se,header$:Xe,main$:_t,target$:gt})))),ci=nt.pipe(v(()=>Da),qe(Na),B(1));ci.subscribe();window.document$=nt;window.location$=At;window.target$=gt;window.keyboard$=Yr;window.viewport$=Se;window.tablet$=lr;window.screen$=ii;window.print$=ai;window.alert$=Br;window.component$=ci;})(); +//# sourceMappingURL=bundle.4e0fa4ba.min.js.map + diff --git a/assets/javascripts/bundle.4e0fa4ba.min.js.map b/assets/javascripts/bundle.4e0fa4ba.min.js.map new file mode 100644 index 0000000..b3e2c4e --- /dev/null +++ b/assets/javascripts/bundle.4e0fa4ba.min.js.map @@ -0,0 +1,8 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/clipboard/dist/clipboard.js", "node_modules/escape-html/index.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/rxjs/node_modules/tslib/tslib.es6.js", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourceRoot": "../../../..", + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*\n * Copyright (c) 2016-2023 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantLoading,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up instant loading, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantLoading({ location$, viewport$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.component$ = component$ /* Component observable */\n", "/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n *\n * @class Subscription\n */\nexport class Subscription implements SubscriptionLike {\n /** @nocollapse */\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n * @return {void}\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n *\n * @class Subscriber\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @nocollapse\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param {T} [value] The `next` value.\n * @return {void}\n */\n next(value?: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param {any} [err] The `error` exception.\n * @return {void}\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n * @return {void}\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as (((value: T) => void) | undefined),\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent\n * @param subscriber The stopped subscriber\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n *\n * @class Observable\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @constructor\n * @param {Function} subscribe the function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @owner Observable\n * @method create\n * @param {Function} subscribe? the subscriber function to be passed to the Observable constructor\n * @return {Observable} a new observable\n * @nocollapse\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @method lift\n * @param operator the operator defining the operation to take on the observable\n * @return a new observable with the Operator applied\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param {Observer|Function} observerOrNext (optional) Either an observer with methods to be called,\n * or the first of three possible handlers, which is the handler for each value emitted from the subscribed\n * Observable.\n * @param {Function} error (optional) A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param {Function} complete (optional) A handler for a terminal event resulting from successful completion.\n * @return {Subscription} a subscription reference to the registered handlers\n * @method subscribe\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next a handler for each value emitted by the observable\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @method Symbol.observable\n * @return {Observable} this instance of the observable\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n * @method pipe\n * @return {Observable} the Observable result of all of the operators having\n * been called in the order they were passed in.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @method toPromise\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @nocollapse\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return {Observable} Observable that the Subject casts to\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\n/**\n * @class AnonymousSubject\n */\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param bufferSize The size of the buffer to replay on subscription\n * @param windowTime The amount of time the buffered items will stay buffered\n * @param timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n *\n * @class Action\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler.\n * @return {void}\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n * @return {any}\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @class Scheduler\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return {number} A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param {function(state: ?T): ?Subscription} work A function representing a\n * task, or some unit of work to be executed by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler itself.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @return {Subscription} A subscription in order to be able to unsubscribe\n * the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @type {boolean}\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @type {any}\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n const flushId = this._scheduled;\n this._scheduled = undefined;\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an