$$\text{只有分享知识,才能延续生命}$$
前言
Rust语言
Rust是一门可靠高效、保证内存安全、支持安全并发和似C语言同级速度的计算机编程语言。
rustup版本管理工具
rustup是Rust语言官方的版本管理工具,负责安装Rust语言及其版本管理。通过rustup工具实现了Rust语言安装工作。
Cargo包管理器
Cargo是一款Rust语言官方的包管理器和开发工具,负责Rust软件篋的依赖管理,并且能够下载、开发、编译、生成和分发的软件篋。一旦安装了Rust语言,该工具也自动完成了安装。
Crate.io
Crate.io是Rust语言软件篋的官方仓库,负责软件篋登记、托管和存储等功能服务。
Rust语言编程方法
Rust语言是一门多编程(multi-programming)方法的或者说多范式(multi-paradigm)系统编程语言,它包括了:
- 面向对象式编程
- 命令式编程
- 函数式编程
- 声明式编程
- 响应式编程
- ...
关于本书内容
以开发软件篋过程,学习和理解Rust语言的全新设计思路及其重要概念。
关于本书读者
本书面向的读者是,致在了解Rust语言概念和设计思想,同时也学习具体程序开发的方法。
关于本书使用系统说明
本书程序代码使用苹果电脑系统开发的。大部分终端命令也应用适用于其它操作系统。
Rust语言重要网站
Rust语言入门在线图书
- 官方网站: The Rust Programming Language
- 官方网站: Rust by Example
- Learning Rust With Entirely Too Many Linked Lists
Rust语言在线图书
Rust语言开发参考图书
重要工具网站
- Tables Generator
- markdown-it demo
- Liste der Unicode-Zeichen der Kategorie „Sonstiges Symbol“
- Sketchpad - Draw, Create, Share!
- Quality metadata badges
- Continuous Integration and Deployment service for Windows and Linux | AppVeyor
- Nu Shell
- Markdown: Syntax
- category_slugs from crates.io
- Compress That Address
- The Rust Playground Codes
- stable error-index
- markdown basics
- Lib.rs — home for Rust crates
本书项目结构
篇目
作业区和篋
Cargo项目是Cargo工具所生成的目录和文件内容,称之为软件篋,或者简称为篋。
软件篋项目是由若干个Cargo项目或者说软件篋组成,在本书里,软件篋项目也简称为作业区(workspace)、整体项目或者项目。所有Cargo项目都是在作业区目录之下,且这些项目在相同目录层上。所有项目名称也是目录名称。每一章最顶层目录是作业区目录。
共享篋和应用程序
每一个软件篋源代码存在于一个Cargo项目里。
把Cargo项目软件篋(library package)发布到网站crates.io的共享软件篋,简称共享篋。
除了这个共享软件篋的Cargo项目外,还有一个使用本地共享篋的可执行应用程序Cargo项目(binary package),称为可执行的本地程序,或者简称为本地程序。其目的是在发布共享篋之前,作为完全独立的Cargo项目,来测试和应用该共享软件篋。
另外,一个使用仓库crates.io里共享篋的可执行应用程序Cargo项目(binary package),称为可执行的本地程序,或者简称为仓库程序。其目的是在发布共享软件篋之后,作为完全独立的Cargo项目,来测试和应用该共享软件篋。
作业区命名法则
作业区目录名称的命名法则,可以是使用短横线命名(kebab-case),也可以使用小蛇式命名(lower snake case)。
篋命名法则
按照Rust语言命名法则,共享软件篋名称使用小蛇式命名(lower snake case)。
可执行程序。其目录名称的命名法则,可以是使用短横线命名(kebab-case),也可以使用小蛇式命名(lower snake case)。
实例:项目类型清单
项目类型 | 项目名称 | 相对路径 |
---|---|---|
作业区 | hello-world | ./hello-world |
共享篋 | lib-hello | ./hello-world/lib-hello |
本地程序 | bin-local-hello | ./hello-world/bin-local-hello |
仓库程序 | bin-hello | ./hello-world/bin-hello |
实例:篋类型清单
篋类型 | 篋名称 | 相对路径 |
---|---|---|
共享软件篋 | hello_exercism | ./hello-world/lib-hello |
可执行程序 | bin-local-hello | ./hello-world/bin-local-hello |
可执行程序 | bin-hello | ./hello-world/bin-hello |
实例:作业区所有目录文件清单
── hello-world
├── Cargo.lock
├── Cargo.toml
├── README.md
├── bin-hello
│ ├── Cargo.lock
│ ├── Cargo.toml
│ └── src
│ └── main.rs
├── bin-local-hello
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── src
│ │ └── main.rs
│ └── tests
│ └── hello.rs
└── lib-hello
├── Cargo.lock
├── Cargo.toml
├── Cargo.txt
├── README.md
├── examples
│ ├── i_hello.rs
│ ├── main.rs
│ └── u_hello.rs
├── src
│ └── lib.rs
└── tests
├── i_hello.rs
└── u_hello.r
本书使用符号说明
符号 | 英文单词 | 说明 | 要求 | 实例 |
---|---|---|---|---|
Ⓓ | default | 叙述Rust语言默认情况 | 必须记住 | Ⓓ 所有模块和函数默认情况下都是私有的。 |
ⓡ | regulation | 阐述Rust语言规则 | 必须记住 | ⓡ 所有字符串文字类型都是引用,且具有静态生命周期。 |
Ⓒ | consensus | 解释约定而非强制方法 | 最好记住 | Ⓒ 大多数单元测试都带有注解'#[cfg(test)]'的测试模块。 |
Ⓘ | idea | 必须记住 | 计算机技术和Rust语言基本概念 |
关于作业区和软件篋(Crate)实例
软件篋(Crate)是其他语言的库(library)或包(package)的同义词。软件篋可以生成这里称之为应用程序的可执行文件或共享库,
实例:创建作业区空间
# 创建一个工作空间
mkdir workpsaces && cd workpsaces
实例:创建作业区
# 创建一个作业区hello-world
mkdir hello-world && cd hello-world
# 创建一个作业区配置文件
touch Cargo.toml
# 作业区存在四个软件篋
# 注意:下面两行代码是一行命令
echo '[workspace]
members = ["lib-hello", "bin-hello", "bin-local-hello", "lib-extern"]' >> Cargo.toml
实例:创建默认共享软件篋程序命令
# 进入作业区根目录
# 命令说明:
# mkdir <crate-project-name>
# 命令实例,如创建名称为lib-hello的共享篋程序项目目录
mkdir lib-hello
# 进入软件篋程序根目录
cd lib-hello
# 命令说明:
# cargo init --name <crate_name> --lib
# 命令实例,如创建名称为hello_exercism的软件篋程序
cargo init --name hello_exercism --lib
实例:创建默认可执行的应用程序命令
# 进入作业区根目录
# 命令说明:
# mkdir <app-project-name>
# 命令实例,如创建名称为bin-hello的应用程序项目目录
mkdir bin-hello
# 进入应用程序根目录
cd bin-hello
# 命令说明
# cargo init --name <app-name> --bin
# 命令实例,如创建名称为bin-hello的应用程序
cargo init --name bin-hello --bin
实例:说明共享软件篋结构
使用软件工具Cargo,在默认Cargo项目基础上,这里除了增加了默认说明文件README.md外,还有增加了两个Cargo默认目录:tests和examples,同时在两个目录下增加了两个rust程序文件,其结果如下:
── lib-hello
├── Cargo.lock
├── Cargo.toml
├── README.md
├── examples
│ └── hello.rs
├── src
│ └── lib.rs
└── tests
└── hello.rs
在上面的结构里,除了两个文件hello.rs之外,其他都是Cargo项目的默认目录和文件。这些目录和文件都是与Cargo工具默认命令相关的。Cargo项目还有其他默认目录和文件。目录src下的默认文件lib.rs是共享篋的入口文件。
参考资料
关于Cargo工具基础命令
安装Rust语言软件篋存在两个行为,其目的和作用是不同的。使用软件工具rustup安装的软件篋是支持版本管理工具,而使用软件工具Cargo安装的软件篋是支持项目级开发环境。
编写规范格式代码工具 Rustfmt
安装Rustfmt命令
rustup self update
rustup component add rustfmt
使用Rustfmt命令
# 进来Cargo项目根目录
cargo fmt
编写有效代码工具 Clippy
安装Clippy命令
rustup self update
rustup component add clippy
使用Clippy命令
# 进来Cargo项目根目录
cargo clippy
说明Cargo软件篋开发命令
除了上面两个开发工具命令之外,Cargo项目还有自身命令:
测试代码运行命令
# -- 适用于所有Cargo项目和作业区 --
# 进来Cargo项目根目录
# 默认测试命令
# 说明:测试在目录tests下的所有测试文件
cargo test
运行应用程序命令
# -- 适用于所有Cargo项目 --
# 进来Cargo项目根目录
# 命令说明:
# cargo run --example <程序文件名称>
# 命令实例,如运行在目录examples下文件名称为hello.rs的应用程序
cargo run --example hello
运行应用程序命令
# -- 仅适用于Cargo应用程序项目 --
# 进来Cargo项目根目录
# 默认运行命令
# 说明:运行目录src下默认入口文件main.rs
cargo run
说明Cargo作业区开发命令
测试代码运行命令
# -- 适用于Cargo作业区和所有Cargo项目 --
# 进来作业区根目录
# 命令说明:
# cargo test --package <篋名称>
# 或者
# cargo test -p <篋名称>
# 命令实例,如运行在Cargo项目lib-hello下共享篋名称为hello_exercism
cargo test -p hello_exercism
运行应用程序命令
# -- 适用于Cargo作业区内Cargo应用程序项目 --
# 进来作业区根目录
# 命令说明:
# cargo run --package <篋名称>
# 或者
# cargo run -p <软件篋名称>
# 命令实例,如运行在Cargo项目bin-hello下篋名称为bin-hello
cargo run -p bin-hello
后续还将介绍上面Cargo工具的其他实用命令。
Rust语言新思维和新概念
变量生命期
不可变量(Immutable)和可变量(Mutable)
表达式(Expression)和语句(statements)
方法(method)和函数(function)
类和对象
全局软件篋和项目软件篋
题外话
工具cargo-play
- https://journal.zeyi.fan/2019/08/20/announce-cargo-play.html
关于软件篋项目hello-world
项目目标
学习和理解如何开发Rust语言的软件篋基本思路和方法,同时了解该项目的其他Cargo项目开发过程和方法。
主题内容
- 了解和学习Cargo工具作业区概念
- 学习和理解共享软件篋整个开发过程
- 了解和学习单元测试和集成测试基本概念
关键词内容
说明 | 关键词 | 链接 |
---|---|---|
实例定义 | let | |
公共修饰 | pub | |
函数和方法 | fn | |
模块定义 | mod | |
生命周期修饰 | ' | |
静态修饰 | static | |
模块关联 | super | |
无名氏 | _ |
类型内容
归类 | 数据类型 | 说明 | 链接 |
---|---|---|---|
基本数据类型 | &str | 字符串文字 |
宏内容
归类 | 宏名 | 说明 | 链接 |
---|---|---|---|
标准库宏 | println! | 打印输出 | |
标准库宏 | assert_eq! | 相同值测试 |
注释内容
归类 | 注释名 | 说明 | 链接 |
---|---|---|---|
标准库注释 | #[test] | 注释测试函数 | |
标准库注释 | #[cfg(test)] | 注释有条件运行测试函数 | |
标准库注释 | #[path="."] | 注释模块路径 |
命名规范
命名对象 | 命名规范 | 实例 | 链接 |
---|---|---|---|
共享软件箧 | 小蛇式命名 | hello_exercism | |
程序软件箧 | 小蛇式命名或短横线命名 | bin-hello | |
函数和方法 | 小蛇式命名 | it_works_at_private() | |
模块 | 小蛇式命名 | owned_hello | |
变量 | 小蛇式命名 | _ |
关于软件篋项目hello-world
学习内容
- 了解项目名称和目录
- 了解项目目录和文件结构
篇目
项目名称清单
项目类型 | 项目名称 | 相对路径 | 项目说明 |
---|---|---|---|
作业区 | hello-world | ./hello-world | 开发共享软件篋工作区 |
共享篋 | lib-hello | ./hello-world/lib-hello | 开发共享软件篋实例 |
本地程序 | bin-local-hello | ./hello-world/bin-local-hello | 使用在本地的共享篋 |
仓库程序 | bin-hello | ./hello-world/bin-hello | 使用在crates.io上共享篋 |
共享篋 | lib-extern | ./hello-world/lib-extern | 作为第三方共享篋实例使用 |
软件篋类型清单
篋类型 | 篋名称 | 相对路径 |
---|---|---|
共享软件篋 | hello_exercism | ./hello-world/lib-hello |
可执行程序 | bin-local-hello | ./hello-world/bin-local-hello |
可执行程序 | bin-hello | ./hello-world/bin-hello |
共享软件篋 | hello_extern | ./hello-world/lib-extern |
项目目录清单
目录名称 | 根目录说明 | 生成方式 |
---|---|---|
src | 篋源代码目录 | Cargo命令 |
src/integration_tests | 篋源代码集成测试目录 | 用户手动命令 |
src/private_tests | 篋源代码私有代码测试目录 | 用户手动命令 |
tests | 篋测试源代码目录 | 用户手动命令 |
examples | 篋实例源代码目录 | 用户手动命令 |
target | 篋构建目录 | Cargo命令 |
debug | 篋调试构建目录 | Cargo命令 |
release | 篋版本构建目录 | Cargo命令 |
项目文件清单
名称 | 说明 | 内容属性 | 名称属性 |
---|---|---|---|
README.md | 项目说明文件 | 可修改 | 不可修改 |
Cargo.lock | 项目配置锁定文件 | 不可修改 | 不可修改 |
Cargo.toml | 项目配置锁定文件 | 可修改 | 不可修改 |
main.rs | 可执行软件篋的入口文件 | 可修改 | 不可修改 |
lib.rs | 共享软件篋的入口文件 | 可修改 | 不可修改 |
mod.rs | 篋模块的入口文件 | 可修改 | 不可修改 |
i_hello.rs | 集成测试或者实例文件 | 可修改 | 可修改 |
u_hello.rs | 单元测试或者实例文件 | 可修改 | 可修改 |
owned_hello.rs | 私有代码测试文件 | 可修改 | 可修改 |
项目结构树
── hello-world
├── Cargo.lock
├── Cargo.toml
├── README.md
├── bin-hello
│ ├── Cargo.lock
│ ├── Cargo.toml
│ └── src
│ └── main.rs
├── bin-local-hello
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── src
│ │ └── main.rs
│ └── tests
│ └── i_hello.rs
├── lib-extern
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ └── lib.rs
│ └── tests
│ └── u_hello.rs
└── lib-hello
├── Cargo.toml
├── README.md
├── examples
│ ├── i_hello.rs
│ ├── main.rs
│ └── u_hello.rs
├── src
│ ├── integration_tests
│ │ ├── i_hello.rs
│ │ └── mod.rs
│ ├── lib.rs
│ └── private_tests
│ ├── mod.rs
│ └── owned_hello.rs
└── tests
├── i_hello.rs
└── u_hello.rs
题外话:目录与命令
目录名称 | 生成命令 | 删除命令 |
---|---|---|
src | cargo new <project_name> cargo new <project_name> --lib cargo new <project_name> --bin cargo init --name <project_name> cargo init --name <project_name> --bin cargo init --name <project_name> --lib | 用户手动命令 |
tests | 用户手动命令 | 用户手动命令 |
examples | 用户手动命令 | 用户手动命令 |
target | 随下面命令自动生成 | cargo clean |
debug | cargo build 或者 cargo run | cargo clean --target-dir target/debug |
release | cargo build --release cargo run --release | cargo clean --release cargo clean --target-dir target/release |
共享篋:开发命令
学习内容
- 熟悉和使用Cargo工具命令
- 熟悉和使用共享篋项目开发命令
篇目
- 创建共享篋项目命令
- 开发共享篋和单元测试代码
- 开发共享篋的单元实例代码
- 开发共享篋的单元实例代码
- 开发共享篋和集成测试代码
- 执行共享篋和测试代码
- 执行共享篋的实例代码
- 开发共享篋文档
- 发布共享篋准备工作
- 发布共享篋
创建共享篋项目命令
# 创建共享篋项目命令
# 先进入作业区根目录,且创建项目目录,然后进入共享篋根目录
mkdir lib-hello && cd lib-hello
# 创建名称为hello_exercism的共享篋
cargo init --name hello_exercism --lib
开发共享篋和单元测试代码
# 开发共享篋和单元测试代码
vi Cargo.toml
vi src/lib.rs
mkdir tests
touch tests/u_hello.rs
vi tests/u_hello.rs
touch tests/i_hello.rs
vi tests/i_hello.rs
开发共享篋的单元实例代码
# 开发共享篋的单元实例代码
mkdir examples
touch examples/u_hello.rs
vi examples/u_hello.rs
touch examples/i_hello.rs
vi examples/i_hello.rs
开发共享篋的单元实例代码
# 开发共享篋的单元实例代码
mkdir examples
touch examples/u_hello.rs
vi examples/u_hello.rs
开发共享篋和集成测试代码
# 开发共享篋和集成测试代码
echo 'i_crate = { version = "0.1.1", package = "hello_extern"}' >> Cargo.toml
touch tests/i_hello.rs
vi tests/i_hello.rs
touch examples/i_hello.rs
vi examples/i_hello.rs
执行共享篋和测试代码
# 执行共享篋和测试代码
# 这些命令需要重复运行
cargo fmt
cargo clippy
cargo test
执行共享篋的实例代码
# 执行共享篋的实例代码
# 这些命令需要重复运行
cargo fmt
cargo clippy
cargo run --example u_hello
cargo run --example i_hello
开发共享篋文档
# 开发共享篋文档
mkdir -p ../../docs/hello-world
cargo doc
cp -rf ../target/doc/. ../../docs/hello-world/.
发布共享篋准备工作
# 发布共享篋准备工作
# 注册网站crates.io帐号
# 登录网站crates.io
# 从网站crates.io获取token,如下所示
# 在本地电脑运行下面命令
cargo login <token>
发布共享篋
## 提交代码
cargo package
cargo publish
共享软件篋hello_exercism:程序代码解释
共享软件篋本身只能提供给其他共享篋和应用程序使用。Cargo工具实现了项目内所有目录和文件有机联系在一起。当运行测试代码或者实例代码时,这些代码都知道应该怎么样连接到正在开发的共享篋。
学习内容
- 阐述共享篋文件功能
- 理解项目共享篋程序代码
篇目
- 项目配置文件Cargo.toml
- 程序文件lib.rs的核心代码
- 静态生命周期的字符串文字类型
- 关键词mod与关键词pub
- 实例目录的单元实例文件
- 实例目录的集成实例文件
- 题外话
- Rust语言类型与关键词
- 浅谈软件篋的模块
- 关键词let
- 表达式、语句和模块
- 项目配置文件
- 参考资料
项目配置文件Cargo.toml
文件Cargo.toml是由用户编写的描述项目共享篋依赖关系。而文件Cargo.lock包含有关共享软件篋的依赖项的确切信息。它是由Cargo工具自动生成和维护的,不应手动对其进行编辑。
# Crate Configuration File: ./Cargo.toml
[package]
name = "hello_exercism"
version = "0.5.5"
authors = ["cnruby <gudao.luo@gmail.com>"]
edition = "2018"
readme = "README.md"
license = "MIT OR Apache-2.0"
repository = "https://github.com/cnruby/learn-rust-by-crates/tree/master/hello-world"
homepage = "https://crates.io/crates/hello_exercism"
documentation = "https://cnruby.github.io/learn-rust-by-crates/hello-world/hello_exercism/"
categories = ["development-tools::testing"]
description = "how to create an own crate"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
#hello_extern = { version = "0.1.1"}
i_crate = { version = "0.1.1", package = "hello_extern"}
在文件Cargo.toml里,最重要的一项是共享软件篋名称:name。这是使用该共享篋的入口名称。这里默认模块名称为hello_exercism。
最常用的一节是共享篋依赖关系:[dependencies]。这里有一行依赖关系代码,说明共享篋有赖于外部共享篋。这里使用了软件篋hello_exercism的已经发布的版本,只是为了解释问题和说明方便,实际上可以依赖所有需要的共享篋。
程序文件lib.rs的核心代码
程序文件lib.rs是由三部分代码。第一部分是共享篋核心代码:两个函数hello()和hallo()。第二部分是私有代码测试函数。第三部分是集成测试函数。这里仅仅说明该程序两个函数的功能,将在后面章节说明其它两个部分的测试代码。
程序文件lib.rs是共享篋的入口文件。它有两个函数hello()和hallo(),其功能都是返回一个字符串文字。hello()返回英文问候,而hallo()返回德文问候。它们返回的类型也都是&'static str的字符串,这一类型是静态生命周期的字符串文字。
// Rust File: src/lib.rs pub fn hello() -> &'static str { println!("{}", hallo()); "Hello, World!" } fn hallo() -> &'static str { "Hallo, Welt!" } // BEGIN: unit tests for private code // code 1 #[cfg(test)] #[path = "./private_tests/owned_hello.rs"] mod owned_hello; // code 2 #[cfg(test)] #[path = "./private_tests/mod.rs"] mod private_tests; // code 3 #[cfg(test)] mod private_tests_with_use { use super::*; //use super::hallo; #[test] fn it_works_at_private() { assert_eq!("Hallo, Welt!", hallo()); } } // code 4 #[cfg(test)] mod private_tests_without_use { #[test] fn it_works_at_private() { assert_eq!("Hallo, Welt!", super::hallo()); } } // END unit tests for private code // BEGIN: integration tests #[cfg(test)] #[path = "./integration_tests/i_hello.rs"] mod i_hello; #[cfg(test)] #[path = "./integration_tests/mod.rs"] mod integration_tests; // END: integration tests
静态生命周期的字符串文字类型
程序文件lib.rs里,两个函数的返回类型都是:静态生命周期的字符串文字类型。
ⓡ 所有字符串文字类型都是引用,且具有静态生命周期的功能。
↳ 这里的函数返回值是包含一个引用字符串文字类型值,所以函数返回类型也要此类型&str
。因为在整个程序过程中需要该类型是有效的,所以此类型还要增加静态生命周期'static
修饰关键词。
关键词mod与关键词pub
Ⓓ 在默认情况下,无论使用关键词mod定义的模块,还使用关键词fn定义的函数,它们都是私有的。要使得它们可公开访问的话,就需要使用修饰词关键词'pub'。
↳ 程序文件lib.rs里,hello()是公共可访问的函数,而hallo()只是模块hello_exercism内可访问的私有函数。该软件篋的公共接口只有:函数hello()。
Ⓓ 共享篋模块默认是公开的。
↳ 这里共享篋模块名称是hello_exercism,尽管既没有关键词mod也没有关键词pub出现,但是Rust语言已经给了该模块名称这两个关键词。
实例目录的单元实例文件
单元实例仅仅测试此软件篋自身的功能。
ⓡ 凡是存在main()函数的Rust程序都是可执行的。
这个程序的功能是调用了该项目的软件篋hello_exercism程序的函数hello(),且打印调用函数的结果。这也是该共享篋的功能。
Ⓓ 在运行实例目录下可执行文件时,Cargo工具自动会调用程序lib.rs。
// Rust File: examples/u_hello.rs fn main() { println!("{}", hello_exercism::hello()); }
在共享篋里的实例目录下的可执行的Rust程序,使用下面命令执行,且得到执行结果如下:
# 从该共享篋项目的根目录执行下面命令;
$ cargo run --example u_hello -q
Hallo, Welt!
Hello, World!
实例目录的集成实例文件
集成实例测试此共享篋与外部其它共享篋的功能。
这个程序的功能是调用了外部软件篋hello_exercism程序的函数hello(),且打印调用函数的结果。这也是该共享篋的功能。此外,还比较了正在开发的软件篋与外部软件篋的函数返回值。
// Rust File: examples/i_hello.rs fn main() { println!("{}", i_crate::hello()); assert_eq!(hello_exercism::hello(), i_crate::hello()); }
在共享篋里的实例目录下的可执行的Rust程序,使用下面命令执行,且得到执行结果如下:
# 从该共享篋项目的根目录执行下面命令;
$ cargo run --example i_hello -q
Hello, World!
Hallo, Welt!
题外话
Rust语言类型与关键词
对于修饰词关键词'static',Rust语言以与常量类似的方式提供了类似“全局变量”的功能。对每一个值只有一个实例,并且位于内存中的固定位置。
在Rust语言里,存在一类关键词是修饰词关键词,如关键词"pub"和“static”。
类型 | 关键词 | 类型说明 |
---|---|---|
&str | 字符串文字 | |
static | 静态修饰词关键词 | |
' | 生命周期关键词 | |
'static | 静态生命周期关键词 | |
&'static str | 静态生命周期字符串文字 |
浅谈软件篋的模块
每一个共享篋都有自己的入口模块名称,这里是hello_exercism,使用共享篋都要从这个名称开始,这里模块hello_exercism有自己的函数hello()。在程序文件lib.rs内还,可以使用关键词mod再定义模块名称,但是它们都是hello_exercism的子模块。
关键词let
从作用意义上,共享篋的函数hello()类似于使用关键词let语句,即可把它看作为一个类型为&str的字符串文字与一个变量绑定,如下所示:
// Rust File: examples/main.rs fn main() { let _: &'static str = "Hello, World!"; let _: &str = "Hello, World!"; }
表达式、语句和模块
在Rust语言里,表达式和语句都可以作为一行代码。要是一行代码,最后没有分号就是表达式,而有分号就是语句。表达式只有作为函数的返回值。
表达式和语句可以汇聚成一个由{}内的代码块和由关键词fn开始的函数与方法。函数和方法可以组成一个由关键词mod开始的模块。若干个模块可以形成由关键词mod开始的父模块。
项目配置文件
除了项目配置文件Cargo.toml之外,还可以有其它功能的配置文件,如工具rustfmt的配置文件。
参考资料
共享篋hello_exercism:目录tests的测试代码解释
学习内容
- 理解开发共享篋的测试代码
- 了解公共接口的单元测试方法
- 了解目录tests的集成测试方法
篇目
公共接口的单元测试文件u_hello.rs
在下面的单元测试程序里,只有一个测试函数,其功能是判断函数hello()返回值与字符串文字“Hello, World!”是否完全一致。
// Rust File: tests/u_hello.rs #[cfg(test)] mod tests { #[test] fn it_works_at_uint() { assert_eq!("Hello, World!", hello_exercism::hello()); } }
单元测试与公共接口的单元测试
Ⓘ 单元测试(Unit tests)是单个模块或者说单个软件篋的单独测试。
一般情况下,单元测试很小且可以测试私有代码。它们的目的是软件篋每一个功能能否正常工作。Rust语言将单元测试分成两类:这里探讨公共接口的单元测试和下面将要说明的私有代码的单元测试。
ⓡ 从模块范围之外只能访问模块的公共接口,而不能访问模块的私有内容。
Ⓒ 大多数单元测试都带有注解'#[cfg(test)]'的测试模块。
ⓡ 每一个单元测试函数带有'#[test]'注解标记。测试文件名称命名是由用户自己确定的。
每个测试函数都是单独地调用正在开发的共享软件篋进行运行的。因此Cargo工具将共享软件篋纳入到每个测试函数的范围里。
Ⓓ 所有公共接口的单元测试文件存储于默认测试目录tests下。
基于目录tests的集成测试文件i_hello.rs
在下面的集成测试程序里,有两个测试函数,第一个函数功能是判断外部共享篋的函数hello()返回值与字符串文字“Hello, World!”是否完全一致。第二个函数功能是判断正在开发共享篋与外部共享篋的函数hello()返回值是否完全一致。
// Rust File: tests/i_hello.rs #[test] fn it_works_with_extern() { assert_eq!("Hello, World!", i_crate::hello()); } #[test] fn it_works_with_the_crate_and_extern() { assert_eq!(hello_exercism::hello(), i_crate::hello()); }
集成测试与基于测试目录tests的集成测试
Ⓘ 集成测试(Integration tests)是与外部的多个共享篋的测试。它们比较大,但仅测试正在开发共享篋的公共接口。它们的目的是与其它篋能否正常协同工作。
集成测试可以存储于这里探讨的基于测试目录tests,也可以存储于下面将要解释的基于共享篋目录src。但是它们处理代码的方式是完全不同的。
Ⓒ 集成测试不需要使用注释'#[cfg(test)]'来注释任何测试代码。
ⓡ 每一个单元测试函数注解带有'#[test]'标记。
ⓡ 在默认情况下,集成测试文件存储于测试目录tests下。
参考资料
- unit_testing
- unit-test-vs-integration-test
- rust-unit-test-placement
- writing-integration-tests-in-rust
- integration-testing-a-service-written-in-rust-and-iron
- practical-rust-web-development-testing
- book/contrib-test
- testing-in-rust-temporary-files
- unit-tests-with-rust-tutorial-101
- use-declarations
共享篋hello_exercism:目录src的测试代码解释
学习内容
- 了解共享篋目录src下的测试代码结构
- 了解私有代码的单元测试方法
- 了解目录src下的集成测试方法
篇目
目录src测试代码结构
$ tree ./src -L 3
./src
├── integration_tests
│ ├── i_hello.rs
│ └── mod.rs
├── lib.rs
└── private_tests
├── mod.rs
└── owned_hello.rs
默认模块文件mod.rs
Ⓓ 从目录src开始,Cargo项目共享篋程序目录名称就是模块名称。目录src就是共享篋模块名称,其模块文件就是lib.rs。
如:这里共享篋名称hello_exercism就是模块名称
Ⓓ 所有目录src的子目录也是模块名称,其模块文件就是mod.rs。
如,目录private_tests就是模块hello_exercism的子模块。
Ⓓ 除了lib.rs和mod.rs文件以外,所有其它文件名称也就是模块名称,且其文件名称就是模块名称。
如,文件i_hello.rs就是一个模块,其模块名称为i_hello。
私有代码的单元测试
↳ 所有私有函数的单元测试代码必须在其相关的程序文件内。
Cargo项目私有代码的单元测试思路:单元测试与共享篋程序代码是融为一体的。所以测试代码都是在私有代码相关的可访问模块里,是不可分开的。
程序文件src/lib.rs与私有代码的单元测试
为了测试共享篋程序文件src/lib.rs的私有函数hallo(),需要将测试代码存放在该文件的可访问模块里,或者分离到另外若干个文件里。
这里说明程序文件src/lib.rs的第二部分私有代码的单元测试代码,有四段单元测试代码,它们是为三个不同模块:hello_exercism::private_tests::owned_hello、hello_exercism::private_tests_with_use和hello_exercism::private_tests_without_use,而每一个模块都有一个单元测试函数,其测试目的和代码含义都是完全一样的,只是代码形式不一样。
// Rust File: src/lib.rs pub fn hello() -> &'static str { println!("{}", hallo()); "Hello, World!" } fn hallo() -> &'static str { "Hallo, Welt!" } // BEGIN: unit tests for private code // code 1 #[cfg(test)] #[path = "./private_tests/owned_hello.rs"] mod owned_hello; // code 2 #[cfg(test)] #[path = "./private_tests/mod.rs"] mod private_tests; // code 3 #[cfg(test)] mod private_tests_with_use { use super::*; //use super::hallo; #[test] fn it_works_at_private() { assert_eq!("Hallo, Welt!", hallo()); } } // code 4 #[cfg(test)] mod private_tests_without_use { #[test] fn it_works_at_private() { assert_eq!("Hallo, Welt!", super::hallo()); } } // END unit tests for private code // BEGIN: integration tests #[cfg(test)] #[path = "./integration_tests/i_hello.rs"] mod i_hello; #[cfg(test)] #[path = "./integration_tests/mod.rs"] mod integration_tests; // END: integration tests
多文件结构的私有代码的单元测试
在程序文件lib.rs的第二部分代码里,第一段代码和第二段代码方法都是把测试代码分离到另外文件里,这里它们指向相同的单元测试文件或者说模块。分离文件'src/private_tests/owned_hello.rs'如下所示里。它们的第二行说明其下一行模块的位置。
Ⓓ 因为第二段代码的访问模块方式是默认方式,所以第二行代码可以省略。
// src/private_tests/owned_hello.rs use super::*; //use super::hallo; #[test] fn it_works_at_private() { assert_eq!("Hallo, Welt!", hallo()); }
在程序文件mod.rs和owned_hello.rs里,第一行代码都是需要访问其父模块的所有函数。因为从模块owned_hello出发,需要访问其上两层模块,所以两个模块里都需要使用super语句。
// src/private_tests/mod.rs use super::*; mod owned_hello;
单一文件结构的私有代码的单元测试
在程序文件lib.rs的第二部分代码里,第三段代码和第四段代码方法是把测试代码存放在可访问私有代码的模块里。它们仅仅是否使用了关键词use不同而已。
第三段代码的第三行说明该模块hello_exercism::private_tests_with_use需要访问其父模块hello_exercism的所有函数。
第四段代码里super也是说明了需要使用期父模块的函数hallo()。
基于目录src内的集成测试
基于共享篋目录src内的集成测试,与私有代码的单元测试思路有类似性,其测试代码也都是在模块程序代码里,但是有本质上区别,它仅仅使用了共享篋模块结构属性,而非共享篋的原代码,因此它是只能访问共享篋的公共接口。
这里说明程序文件src/lib.rs的第三部分集成测试代码,存在两段测试代码。第一段代码和第二段代码方法都是把测试代码分离到另外文件里,这里它们指向相同的集成测试文件或者说模块。代码原理与前面私有代码的单元测试是完全一样的。
// Rust File: ./integration_tests/mod.rs mod i_hello;
不同代码的是,程序文件mod.rs和集成测试文件i_hello.rs。程序文件mod.rs没有super相关语句,只是说明了使用i_hello模块。集成测试文件i_hello.rs也是不一样的,也没有super相关语句,而是引用了一行使用自己模块的语句,且把自己也称之为模块hello_exercism,这个模块名称可以随意自己命名。
// Rust File: ./integration_tests/i_hello.rs use crate as hello_exercism; #[test] fn it_works_with_only_extern() { assert_eq!("Hello, World!", i_crate::hello()); } #[test] fn it_works_with_the_crate_and_extern() { assert_eq!(hello_exercism::hello(), i_crate::hello()); }
参考资料
- unit_testing
- unit-test-vs-integration-test
- rust-unit-test-placement
- writing-integration-tests-in-rust
- integration-testing-a-service-written-in-rust-and-iron
- practical-rust-web-development-testing
- book/contrib-test
- testing-in-rust-temporary-files
- unit-tests-with-rust-tutorial-101
- use-declarations
本地程序:开发命令
学习内容
- 熟悉和使用Cargo工具命令
- 熟悉和使用本地程序项目开发命令
篇目
创建项目命令
# 先进入作业区根目录,且创建项目目录,然后进入本地程序项目根目录
mkdir bin-local-hello && cd bin-local-hello
# 创建名称为bin-hello的可执行软件篋
cargo init --name bin-local-hello --bin
修改项目配置文件
# 进入本地程序项目根目录
echo 'hello_exercism = { path = "../lib-hello"}' >> Cargo.toml
开发主程序文件代码
# 进入本地程序项目根目录
vi src/main.rs
运行主程序文件
# 进入本地程序项目根目录
cargo run
子项目:本地程序项目bin-local-hello
在共享篋hello_exercism发布以前,Cargo项目本地程序可以以独立的Cargo项目使用该共享篋,且开发应用和检查代码。
学习内容
- 阐述项目本地程序开发方法
- 理解项目本地程序代码
篇目
修改项目配置文件Cargo.toml
下面文件Cargo.toml里,与项目关系最大的一行代码是最后一行代码。这行代码说明了共享篋源代码所处的位置。
[package]
name = "bin-local-hello"
version = "0.1.0"
authors = ["cnruby <gudao.luo@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hello_exercism = { path = "../lib-hello"}
开发主程序文件main.rs
主程序文件main.rs与前面代码非常类似,但是这个程序的执行命令与以前是不一样的,并且代码文件结构形式也是不一样的,程序代码和测试代码存在于一个文件里。
fn main () { println!("{}",hello_exercism::hello()); } #[test] fn test_hello_world() { assert_eq!("Hello, World!", hello_exercism::hello()); }
运行主程序及其结果
# 运行主程序及其结果
$ cargo run -q
Hallo, Welt!
Hello, World!
运行测试代码及其结果
# 运行测试代码及其结果
$ cargo test -q
running 1 test
.
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
参考资料
仓库程序:开发命令
只有发布了自己共享软件篋以后,才能开发这个项目。
学习内容
- 熟悉和使用Cargo工具命令
- 熟悉和使用仓库程序项目开发命令
篇目
创建项目命令
# 创建项目命令
# 先进入作业区根目录,且创建项目目录,然后进入仓库程序项目根目录
mkdir bin-hello && cd bin-hello
# 创建名称为bin-hello的可执行软件篋
cargo init --name bin-hello --bin
修改项目配置文件
# 修改项目配置文件
# 进入仓库程序项目根目录
echo 'hello_exercism = "0.4.1"' >> Cargo.toml
开发主程序文件代码
# 开发主程序文件代码
# 进入仓库程序项目根目录
rm src/main.rs
mkdir -p src/bin
touch src/bin/hello.rs
vi src/bin/hello.rs
touch src/bin/hallo.rs
vi src/bin/hallo.rs
运行主程序文件
# 运行主程序文件
# 进入仓库程序项目根目录
cargo run --bin hello
cargo run --bin hallo
安装仓库程序于本地系统
# 安装仓库程序于本地系统
# 进入仓库程序项目根目录
# 所有Cargo软件篋都安装于目录~/.cargo/bin/
ls ~/.cargo/bin/
cargo install --path .
ls ~/.cargo/bin/
运行安装于本地系统的仓库程序
# 运行安装于本地系统的仓库程序
# 可以在本地系统任何目录下运行下面命令
hello
hallo
删除本地系统的仓库程序
# 删除本地系统的仓库程序
# 进入仓库程序项目根目录
ls ~/.cargo/bin/
# cargo uninstall <来自于Cargo.toml里的仓库程序名称>
cargo uninstall bin-hello
ls ~/.cargo/bin/
子项目:仓库程序项目bin-hello
在共享篋hello_exercism发布以后,Cargo项目仓库程序可以以独立的Cargo项目使用共享篋,且开发应用和检查代码。一般情况下。用户使用共享篋是以这种项目形式出现的。
这个仓库程序项目介绍了一种方法,在一个可执行的软件篋里,存在多个独立的可执行程序。
学习内容
- 阐述项目仓库程序开发方法
- 理解项目仓库程序代码
篇目
项目配置文件Cargo.toml
下面文件Cargo.toml里,与项目关系最大的一行代码是最后一行代码。这行代码说明了所使用的共享篋,包括共享篋名称和版本号。
[package]
name = "bin-hello"
version = "0.5.3"
authors = ["cnruby <gudao.luo@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# https://crates.io/crates/hello_exercism
# change the follow version to current version
hello_exercism = "0.5.3"
主程序文件src/bin/hello.rs
下面主程序文件main.rs与前面项目bin-local-hello完全是一样的。
fn main () { println!("{}",hello_exercism::hello()); }
执行上面程序的命令及其结果,如下所示:
$ cargo run -q
Hallo, Welt!
Hello, World!
主程序文件src/bin/hallo.rs
fn main () { assert_eq!("Hello, World!", hello_exercism::hello()); }
集成测试文件i_hello.rs
下面的集成测试文件i_hello.rs与前面项目bin-local-hello完全是一样的。
# #![allow(unused_variables)] #fn main() { #[test] fn test_hello_world() { assert_eq!("Hello, World!", hello_exercism::hello()); } #}
题外话
篇目
- Cargo工具命令
- 构建和运行软件篋目录examples下文件
- Cargo项目构建命令
- Cargo项目测试特定代码命令
- 思考问题
- 类型
&str
和&'static str
有什么区别? - 使用关键词use和extern有什么区别?
- 注解#[cfg(test)]有什么意义?
- 参考资料
Cargo工具命令
构建和运行软件篋目录examples下文件
# cargo build --example <目录examples下无扩展名的文件名称>
# cargo run --example <目录examples下无扩展名的文件名称>
cargo build --example i_hello
cargo run --example i_hello
Cargo项目构建命令
cargo build
cargo build --release
Cargo项目测试特定代码命令
cargo test tests::it_works_at_uint
cargo test test_hello_world
思考问题
类型&str
和&'static str
有什么区别?
- 在语句里,它们是没有任何区别。
- 在函数返回类型里,它们是有区别的。
使用关键词use和extern有什么区别?
- 自Rust2018版本以来几乎不再需要extern语句。
- 关键词use方法与以前相同。
- 关键词use仅仅是引用标准符号的简写,或者使用trait时必须出现。
注解#[cfg(test)]有什么意义?
- 它告诉编译器在测试环境下进行编译,
- 仅当使用命令'cargo test'运行测试时,Cargo工具才会编译测试代码。
参考资料
- whats-the-difference-between-use-and-extern
- crates-and-modules
- cfg-test-and-cargo-test-a-missing-information
- ch11-03-test-organization
- writing-integration-tests-in-rust
- what-is-the-difference-between-str-and-static-str-in-a-static-or-const
install MacOS
Install Rust Install Visual Studio Code Install VS Code Extensions: Rust (rls) Install VS Code Extensions: CodeLLDB
use vscode on MacOS
- select Cargo Project
- Menu >> Click Debug -> Add Configuration >> LLDB: Custom Launch >> This should create and open launch.json
- Next, you should verify breakpoints are enabled.
- F5
install CodeLLDB, comm to result
Acquiring platform package for CodeLLDB.
Package is located at https://github.com/vadimcn/vscode-lldb/releases/download/v1.4.0/vscode-lldb-x86_64-darwin.vsix
Downloading...
Downloaded 0%
Downloaded 5%
Downloaded 10%
Downloaded 15%
Downloaded 20%
Downloaded 25%
Downloaded 30%
Downloaded 35%
Downloaded 40%
Downloaded 45%
Downloaded 50%
Downloaded 55%
Downloaded 60%
Downloaded 65%
Downloaded 70%
Downloaded 75%
Downloaded 80%
Downloaded 85%
Downloaded 90%
Downloaded 95%
Downloaded 100%
Installing...
Done.
use gdbgui
https://github.com/cs01/gdbgui/tree/master/examples/rust https://www.gdbgui.com/
参考资料
- How to Debug Rust with Visual Studio Code
- rust-language-linux
- Step by step interactive debugger for Rust?
软件篋trait_exerci
本章学习内容
- 什么是Rust语言衔接关键词trait
- 为什么需要衔接关键词trait
- 怎么样实现Rust语言衔接关键词trait
- 实现关键词impl与衔接关键词trait是什么关系
本节篇目
题外话
怎么样使用Rust语言nightly版本
rustc --version
rustup default nightly
rustc --version
rustup default stable
rustup update
rustup show
参考资料
- Setting "rustup default nightly" and back to stable ends up
- Toolchain 'nightly-x86_64-apple-darwin' missing
本章参考资料
- Impls & Traits
- Traits from 'rust-by-example'
- Traits from 'The Rust Programming Language'
- Traits: Defining Shared Behavior
类型关键词struct
通过了解和学习本节Rust语言的基本概念,可以实现本章需要开发的共享软件篋程序代码。
学习内容
- 理解和掌握Rust语言结构性的类型关键词
struct
定义形式 - 理解和掌握Rust语言结构性的类型关键词
struct
实例化方式 - 理解和掌握Rust语言结构性的类型关键词
struct
调用手段
篇目
类型关键词struct
是什么
类型关键词struct提供定义一种结构性的类型方式。这种类型的定义分析如下:
名称 | 事物描述 | Rust语言描述 |
---|---|---|
结构性事物 | 类别 | struct |
事物名称 | 人 | Person |
事物属性 | 姓名,年龄 | name: string, age:u32 |
从上图所示,可以理解到,结构类型关键词struct将一组不同的数据类型作为整体在一起分析和处理。
同时还可以看到,在Rust语言里,结构类型关键词struct可以将这种结构性事物以两种不同的表达形式进行定义。它们分别称之为:C语言形式和元组形式。图上左边的C语言形式是以哈希结构表达的类型,而图上右边的元组形式是以数组结构表达的类型。
类型关键词struct
的私有性
Rust语言规定,Ⓓ 默认情况下,关键词struct定义的类型及其属性都是私有的,默认实例代码如下所示:
# #![allow(unused_variables)] #fn main() { struct Person { name: String, age: u32, } #}
C语言形式的类型关键词struct
代码
这里通过下面的代码,说明如下内容:
- 使用关键词mod,关键词
struct
定义结构类型的方式。 - 使用关键词pub,实现关键词
struct
定义的类型及其属性公开性。 - 使用结构类型属性的公开性,实现结构类型的实例化方式。
- 结构类型的实例调用结构类型的属性手法。
- 使用宏方法
assert_eq!
,验证实例调用结果的正确性。
mod trait_exerci { pub struct ClikeStructType { // data: u32, pub data: u32, } } // cargo run --example pub_field fn main() { let instance = trait_exerci::ClikeStructType { data: 0 }; let data = instance.data; assert_eq!(0, data); }
元组形式的类型关键词struct
通过下面的代码,可以学习到这些知识:
- 使用关键词
mod
,关键词struct定义结构类型的方式。
ⓡ 注意:元组形式的类型关键词struct
定义的类型是以分号结束的,而C语言形式的类型关键词struct
代码是没有分号的。 - 使用关键词
pub
,实现关键词struct
定义的类型及其属性公开性。 - 使用结构类型属性的公开性,实现结构类型的实例化方式。两种形式的类型不同的。
- 结构类型的实例调用结构类型的属性手法。元组形式的类型是以其属性的顺序号实现调用的,而C语言形式的类型是以其公开性属性实现调用的。
- 使用宏方法
assert_eq!
,验证实例调用结果的正确性。
mod trait_exerci { pub struct TupleStructType (pub u32); } // cargo run --example tuple_struct fn main() { let instance = trait_exerci::TupleStructType(0); let data = instance.0; assert_eq!(0, data); }
参考资料
关键词impl
与方法代码实现
学习内容
- 阐述关键词
impl
基本概念 - 理解关键词
impl
实现代码的方式
篇目
关键词impl概念
关键词impl
是为类型实现结构类型或者其它一些类型的不同行为功能。这里仅仅说明了直接针对结构类型本身的不同行为实现方式。
实现功能关键词impl
Rust语言规定:
- Ⓓ 关键词
impl
始终是公开的,且不可增加修饰关键词pub
; - Ⓓ 关键词
impl
实现的函数和方法默认都是私有的,且可增加修饰关键词pub
。
默认实例代码如下所示。尽管下面代码已实现了函数new()
,但是该函数外部还是不可访问的。
# #![allow(unused_variables)] #fn main() { #struct Person { # name: String, # age: u32, #} impl Person { fn new(name: String, age: u32) -> Person { Person { name: name, age: age, } } } #}
实现功能关键词impl与函数代码实现
通过下面的代码,可以学习到这些知识:
- 使用关键词
impl
,实现结构类型StructType
的实例化函数new()
; - 使用关键词
pub
,实现结构类型StructType
的函数new()
公开性; - 实例是一种类型的具体对象;
- 借助于公开性函数
new()
,实现该结构类型的实例化方式; - 借助于结构类型属性的公开性,实现了实例调用结构类型的属性手法;
- 使用宏方法
assert_eq!
,验证实例调用结果的正确性;
mod trait_exerci { pub struct StructType { pub data: u32, } impl StructType { pub fn new(data: u32) -> StructType { StructType { data: data } } } } // cargo run --example function_instance fn main() { let instance = trait_exerci::StructType::new(0); println!("instance.data = {}", instance.data); let instance = trait_exerci::StructType{ data:0, }; println!("instance.data = {}", instance.data); }
程序结构图与功能关键词impl
参考资料
函数与方法代码实现
学习内容
- 阐述Rust语言函数与方法概念区别
- 理解实现函数与方法手段
篇目
修饰实例关键词mut
概念
关键词let
用来定义实例变量,其值是不可改变的,而在一组关键词let mut
也用来定义实例变量,但其值是可改变的。
下面具体实际代码,这样执行的话,一切正常,但是去掉注释行,就会出现编译错误。
fn main() { let instance = 1; // instance = 2; let mut instance = 1; instance = 2; }
函数与方法概念
在Rust语言里,给予了函数(function)或者更明确地说关联函数(associated function)与方法(method)两个名称不同的概念。
引用:Methods are functions attached to objects,直接翻译:方法是附加到对象的函数(行为功能),可以这么理解,方法是附属于类实例的行为功能。
而关联函数是附属于类的功能。在前面一节里,可以看到,实现类的关联函数代码方法及其调用手法。下面通过代码详细说明它们的区别。
实现方法代码
通过下面代码,实现了如下内容:
- 实现结构类型
StructType
的实例化函数new()
; - 实现结构类型
StructType
获取其属性的方法get_data()
; - 实现结构类型
StructType
变更其属性的方法set_data()
; - 实现结构类型
StructType
的属性data
是私有的; - 借助于公开性方法
get_data()
,实现结构类型的属性内容获取手法; - 借助于公开性方法
set_data()
,实现结构类型的属性内容变更手法;
在Rust语言里,ⓡ 方法的第一个参数使用其本身的引用如&self
或者&mut self
,且在调用该函数时不需要传递该参数。凡是第一个参数不是引用的就是函数。
调用函数如new()
是使用类型名称如StructType
实现的,其调用函数new()的手法是使用::
的形式,而调用函数方法如get_data()
是使用类型的实例变量如instance
实现的,其调用方法的手法是使用.
的形式。
mod trait_exerci { pub struct StructType { data: u32, } impl StructType { pub fn new(data: u32) -> StructType { StructType { data: data } } pub fn get_data(&self) -> u32 { self.data } pub fn set_data(&mut self, data: &u32) { self.data = *data; } } } // cargo run --example function_methods fn main() { let instance = trait_exerci::StructType::new(0); let data = instance.get_data(); println!("{0: <20} = {1: <20}", "instance data", data); let mut instance = trait_exerci::StructType::new(0); instance.set_data(&10); let data = instance.get_data(); println!("{0: <20} = {1: <20}", "mut instance data", data); let data = trait_exerci::StructType::new(20).get_data(); println!("{0: <20} = {1: <20}", "data", data); }
结构类型属性的私有性
结构类型StructType
的属性是私有的,所以模块之外是不可访问的,如使用语句instance.data;
是不可以的。但是使用方法get_data()
可以实现了对该属性的访问。
结构类型属性私有性的好处是隐蔽了结构类型的属性。
题外话
在终端里怎么样使用表格形式
下面打印宏println!
语句,实现了第一列和第二列占位20个字符的输出。
# #![allow(unused_variables)] #fn main() { let data = 10; println!("{0: <20} = {1: <20}", "data", data); println!("{0: <20} = {1: <20}", "data", data); println!("{0: <20} = {1: <20}", "data", data); #}
参考资料
衔接关键词trait
学习内容
- 阐述衔接类型关键词
trait
基本概念
篇目
关键词trait概念表述
关键词trait概念表述之一:
关键词trait是Rust语言的一项功能,可以告诉Rust编译器一种类型必须提供的功能。
关键词trait概念表述之二:
关键词trait是为任何未知类型定义方法的集合。
关键词trait概念表述之三:
关键词trait告诉Rust编译器一种特定的类型具有且可与其他类型共享的功效性质。
关键词trait提供了一种类型或者几种类型之间的衔接方式。它应该包含下面内容:
- 存在一种类型或者几种类型
- 使用关键词trait定义衔接特质名称
- 使用关键词trait代码块定义默认方法和函数
- 使用关键词"impl"和"for"组合,实现针对这一种类型或者这几种类型的方法和函数
衔接类型关键词trait概念
Trait definitions are a way to group method signatures together to define a set of behaviors necessary to accomplish some purpose.
直接翻译:特质(Trait)定义是一种途径,将方法一部分进行分组在一起,为实现某些目的所需以定义行为。
衔接类型关键词trait
包含这些信息:
- 衔接类型关键词
trait
提供了类型的一种通道。 - 衔接类型关键词
trait
定义了类型行为功能的一部分,且也可实现行为功能。 - 这一组或者部分类型的行为功能是为了完成一项特有明确的任务。
- 单个类型可以使用关键词trait定义多组类型行为功能,来实现不同任务。
实现类型关键词trait
Rust语言规定:
- 关键词
trait
默认是私有的,但可增加修饰关键词pub
; - 使用关键词
trait
可以定义一个称之为特质的一组类型行为功能; - 一旦定义了衔接类型特质,其函数和方法都是公共的,且且不可增加修饰关键词
pub
;
默认实例代码如下所示。尽管下面代码已定义了函数new()
或者实现了默认函数init()
,但是该函数外部还是不可访问的,因为该特质是私有的。注意,定义了函数new()
是语句,是带分号;
,而实现了默认函数init()
是表达式,是无分号;
。
# #![allow(unused_variables)] #fn main() { #struct Person { # name: String, # age: u32, #} # trait TraitPerson { fn new(name: String, age: u32) -> Person; fn init() -> Person { Person { name: String::new(), age: 0, } } } #}
关键词impl
和for
概念
Rust语言规定:
- 关键词
impl
和for
默认是公开的; - 使用关键词
impl
和for
实现的函数或者方法是不可增加修饰关键词pub
;
默认实例代码如下所示。尽管使用关键词impl
和for
实现了特质TraitPerson
,且其默认是公开的,但是为了使用其功能,其相关的类型Person
和特质TraitPerson
必须要公开的。
##![allow(dead_code)] #mod trait_exerci { # pub struct Person { # name: String, # age: u32, # } # # pub trait TraitPerson { # fn new(name: String, age: u32) -> Person; # fn init() -> Person { # Person { # name: String::new(), # age: 0, # } # } # } # impl TraitPerson for Person { fn new(name: String, age: u32) -> Person { Person { name: name, age: age, } } } #} # #//use self::trait_exerci::TraitPerson; #use crate::trait_exerci::TraitPerson; # #fn main() { # trait_exerci::Person::init(); # trait_exerci::Person::new(String::from("Leo"), 24); #}
实现关键词trait代码
通过下面的代码,可以学习到这些知识:
- 使用关键词
trait
,定义了特质TraitCanal
的函数new()
; - 使用关键词
impl
和for
,基于结构类型StructType
,为特质TraitCanal
实现了实例化函数new()
; - 借助于特质
TraitCanal
,实现该结构类型的实例化方式; - 借助于结构类型属性的公开性,实现了实例调用结构类型的属性手法;
- 使用宏方法
assert_eq!
,验证实例调用结果的正确性;
#![allow(dead_code)] mod trait_exerci { pub struct StructType { pub data: u32, } pub trait TraitCanal { fn new(data: u32) -> StructType; } impl TraitCanal for StructType { fn new(data: u32) -> StructType { StructType { data: data } } } } use self::trait_exerci::TraitCanal; // cargo run --example trait fn main() { let instance = trait_exerci::StructType::new(0); assert_eq!(0, instance.data); }
题外话
参考资料
特质实现及其对象
学习内容
- 理解衔接类型关键词
trait
的实现
篇目
程序结构图与衔接类型关键词trait
实现基于默认方法的关键词trait代码
通过下面的代码,可以学习到这些知识:
- 使用关键词
trait
,定义了特质TraitCanal
的默认实例化函数init()
; - 使用关键词
impl
和for
,基于结构类型StructType
,为特质TraitCanal
实现了方法new()
、get_data()和
set_data()`; - 借助于特质
TraitCanal
的默认实例化函数init()
,实现该结构类型的实例化方式;
mod trait_exerci { #[derive(Debug)] pub struct StructType { pub data: u32, } pub trait TraitCanal { fn new(data: u32) -> StructType; fn init() -> StructType { StructType{data:0} } fn get_data(&self) -> u32; fn set_data(&mut self, data: &u32); } impl TraitCanal for StructType { fn new(data: u32) -> StructType { StructType { data: data } } fn get_data(&self) -> u32 { self.data } fn set_data(&mut self, data: &u32) { self.data = *data; } } } use self::trait_exerci::TraitCanal; // cargo run --example trait_with_default_method fn main() { let mut instance = trait_exerci::StructType::new(10); instance.set_data(&11); println!("new {:?}", instance); let mut instance = trait_exerci::StructType::init(); instance.set_data(&12); println!("init {:?}", instance); }
特质对象解释
特质本身不能定义特质对象,而是通过类型的对象,进行强制转换得到的特质对象。特质对象可以访问类型的公共数据和公共特质的行为。
Rust语言把面向对象编程的思想更加深化了。把类的数据与行为分散化定义,而把类的实例集成化使用。不仅如此,而且Rust语言在代码里完全把这种过程都隐藏起来了。
题外话
什么是可衍生特质
Rust语言标准库或者第三方提供了一些非常有用的特质,称之为可衍生特质(Derivable Trait)。通过注释#[derive(特质名称)]
,编译器能够为这些特质提供实现。比如,要求类型实现是可打印的,可以使用特质std::fmt::Debug。具体说,使用可衍生特质#[derive(Debug)],所有类型都可以自动创建地实现std::fmt::Debug。
下面的代码里第一行就是注释可衍生特质Debug
,为类型Person
实现了特质Debug
,这些后面的宏println!()
就可以使用了这个特质。
注意,使用注释#[derive(特质名称)]
,必须紧挨着类型定义之上。
#[derive(Debug)] struct Person { name: String, age: u32, } impl Person { fn init() -> Person { Person { name: String::new(), age: 0, } } } fn main() { let person = Person::init(); println!("{:?}", person); }
参考资料
- trait std::fmt::Debug
- rust-by-example derive
- rust-by-example print_debug
- appendix-03-derivable-traits
题外话:标准库默认衔接特质Default
学习内容
- 理解标准库默认衔接特质
Default
篇目
默认衔接特质Default
功能
标准库默认衔接特质Default
也是一个有用的可衍生特质,为类型提供默认值的特质,也就是为类型提供默认的实例化手法。
使用默认衔接特质Default
在使用默认衔接特质Default
的函数default()定义变量时,必须说明变量类型。
#![allow(dead_code)] mod trait_exerci { #[derive(Default, Debug)] pub struct StructType { pub data: u32, } } // cargo run --example trait_default fn main() { let instance: trait_exerci::StructType = Default::default(); println!("{0: <20} = {1}", "instance.data", instance.data); println!("{0: <20} = {1:?}", "instance", instance); }
实现自己默认衔接特质Default
#[derive(Debug)]`` pub struct Person { name: String, age: u32, } impl Default for Person { fn default() -> Person { Person { name: String::from("Leo"), age: 24, } } } fn main() { let instance: Person = Default::default(); println!("{:?}", instance); let mut instance = Person { age: 23, ..Default::default() }; instance.age = 24; println!("{:?}", instance); }
参考资料
共享篋:程序代码结构
这一节总结前面程序代码结构。
篇目
特质与类型实现方式结构示意图
仅仅类型实现方式示意图
仅仅特质实现方式示意图
特质与类型实现比较示意图
关于软件篋trait_exerci
学习内容
- 了解项目名称和目录
篇目
项目名称清单
项目类型 | 项目名称 | 相对路径 | 项目说明 |
---|---|---|---|
作业区 | hello-trait | ./hello-trait | 开发共享软件篋工作区 |
共享篋 | lib-hello | ./hello-trait/lib-hello | 开发共享软件篋实例 |
本地程序 | bin-local-hello | ./hello-trait/bin-local-hello | 使用在本地的共享篋 |
仓库程序 | bin-hello | ./hello-trait/bin-hello | 使用在crates.io上共享篋 |
软件篋类型清单
篋类型 | 篋名称 | 相对路径 |
---|---|---|
共享软件篋 | trait_exerci | ./hello-trait/lib-hello |
可执行程序 | bin-local-hello | ./hello-trait/bin-local-hello |
可执行程序 | bin-hello | ./hello-trait/bin-hello |
共享篋:开发命令
篇目
创建共享篋
mkdir lib-hello && cd lib-hello
cargo init --name trait_exerci --lib
创建应用程序
mkdir bin-hello && cd bin-hello
cargo init --name hello-trait --bin
共享篋:程序代码解释
学习内容
- 学习和理解关键词
trait
开发过程
篇目
项目共享篋程序结构示意图
下面示意图是共享篋trait_exerci
程序的结构。该共享篋提供了对外三个衔接通道:两个特质TraitCanal
和TraitKanal
以及一个类型的自我实现StructType
。
程序代码解释
通过下面代码,实现了如下内容:
- 结构类型
StructType
进行了自我实现; - 针对结构类型
StructType
,定义了两个特质TraitCanal
和TraitKanal
; - 对于结构类型
StructType
,实现了两个特质TraitCanal
和TraitKanal
;
#[derive(Debug, PartialEq, Default)] pub struct StructType { data: u32, } pub trait TraitCanal { fn get_data(&self) -> u32; } pub trait TraitKanal { fn set_data(&mut self, data: &u32); } impl TraitCanal for StructType { fn get_data(&self) -> u32 { self.data } } impl TraitKanal for StructType { fn set_data(&mut self, data: &u32) { self.data = *data; } } // impl AllTrait for StructType { impl StructType { pub fn new(data: u32) -> StructType { dbg!("impl StructType: new"); StructType { data: data } } pub fn get_data_for_all(&self) -> u32 { self.data } pub fn set_data_for_all(&mut self, data: &u32) { self.data = *data; } }
结构类型StructType
自我实现,包含了一个函数new()
和两个方法get_data_for_all()
和set_data_for_all()
。
针对一个结构类型StructType
,定义且实现了两个不同的特质TraitCanal
和TraitKanal
。特质TraitCanal
包含一个方法get_data()
,而特质TraitKanal
包含一个方法set_data()
。
理解关键词trait
和impl
关系
针对一个结构类型StructType
实例,可以存在不同的特质,只要把这些特质在一起使用,这些不同特质实现的函数和方法是可以相通的。
任何结构类型StructType
的特质实现,都可以使用结构类型StructType
自我实现的函数和方法。
题外话
标准库平等比较特质PartialEq
标准库平等比较特质PartialEq
,可以比较类型的实例以检查它们是否相等。
#[derive(Default, Debug, PartialEq)] pub struct Person { name: String, age: u32, } fn main() { assert_eq!( Person::default(), Person { name: String::new(), age: 0 } ); }
参考资料
共享篋:目录测试代码解释
篇目
- 结构类型
StructType
自我实现的单元测试代码 - 特质
TraitCanal
实现的单元测试代码 - 特质
TraitKanal
实现的单元测试代码 - 所有实现的单元测试代码
- 基于模块的单元测试代码
- 题外话
- 浅说指针类型
Box
- 参考资料
结构类型StructType
自我实现的单元测试代码
下面单元测试仅仅使用了结构类型StructType
的自我实现。四个方法分别测试了:
- 使用结构类型
StructType
自我实现的实例化函数new(); - 使用标准库默认特质
Default
实现的实例化函数default(); - 使用结构类型
StructType
自我实现的方法get_data_for_all(); - 使用结构类型
StructType
自我实现的方法set_data_for_all();
use trait_exerci::StructType; #[test] fn it_works_with_new() { let instance = StructType::new(10); assert_eq!(StructType::new(10), instance); } #[test] fn it_works_with_default() { let instance :StructType = Default::default(); assert_eq!(StructType::new(0), instance); } #[test] fn it_works_with_get() { let instance = StructType::new(11); assert_eq!(11, instance.get_data_for_all()); assert_eq!(StructType::new(11), instance); } #[test] fn it_works_with_set() { let mut instance = StructType::new(0); instance.set_data_for_all(&12); assert_eq!(StructType::new(12), instance); }
特质TraitCanal
实现的单元测试代码
下面单元测试使用了结构类型StructType
的自我实现和特质TraitCanal
实现。特别需要注意的是,从代码表面上看,第二行语句与后面代码没有任何关系。之所以代码里可以使用方法get_data()
,就是因为第二行语句的作用。当我们写下第二行语句时,就应该知道接下来我们将要使用什么函数或/和方法。
方法it_works_with_get()
测试了:
- 使用结构类型
StructType
自我实现的实例化函数new()
; - 使用特质
TraitCanal
实现的方法get_data()
;
方法it_works_with_default()
测试了:
- 使用标准库默认特质
Default
实现的实例化函数default()
; - 使用特质
TraitCanal
实现的方法get_data()
;
use trait_exerci::StructType; use trait_exerci::TraitCanal; #[test] fn it_works_with_get() { let instance = StructType::new(20); assert_eq!(20, instance.get_data()); assert_eq!(StructType::new(20), instance); } #[test] fn it_works_with_default() { let instance :StructType = Default::default(); assert_eq!(0, instance.get_data()); assert_eq!(StructType::new(0), instance); }
特质TraitKanal
实现的单元测试代码
下面单元测试使用了结构类型StructType
的自我实现和特质TraitKanal
实现。所以这里没有使用到特质TraitCanal
实现。尽管下面代码没有使用特质TraitCanal
实现,但是我们使用了标准库平等比较特质PartialEq
,实现了代码测试。
方法it_works_with_get()
测试了:
- 使用结构类型
StructType
自我实现的实例化函数new()
; - 使用特质
TraitKanal
实现的方法get_data()
;
方法it_works_with_default()
测试了:
- 使用标准库默认特质
Default
实现的实例化函数default()
; - 使用特质
TraitKanal
实现的方法get_data()
;
use trait_exerci::StructType; use trait_exerci::TraitKanal; #[test] fn it_works_with_set() { let mut instance = StructType::new(0); instance.set_data(&30); assert_eq!(StructType::new(30), instance); } #[test] fn it_works_with_default() { let mut instance :StructType = Default::default(); instance.set_data(&30); assert_eq!(StructType::new(30), instance); }
所有实现的单元测试代码
下面单元测试程序的所有三个实现。所以最前面三行语句表示使用这三个实现。
use trait_exerci::StructType; use trait_exerci::TraitCanal; use trait_exerci::TraitKanal; #[test] fn it_works_with_both_traits_and_new() { let mut instance = StructType::new(40); instance.set_data(&41); assert_eq!(41, instance.get_data()); assert_eq!(StructType::new(41), instance); } #[test] fn it_works_with_both_traits_and_default() { let mut instance :StructType = Default::default(); instance.set_data(&41); assert_eq!(41, instance.get_data()); assert_eq!(StructType::new(41), instance); }
基于模块的单元测试代码
下面单元测试主要说明在模块下如何进行单元测试。除了定义模块之外,最重要的是,使用三个实现语句必须在模块内。
#[cfg(test)] mod tests { use trait_exerci::StructType; use trait_exerci::TraitCanal; #[test] fn it_works_with_new() { let instance = StructType::new(50); assert_eq!(StructType::new(50), instance); } #[test] fn it_works_with_get() { let instance = StructType::new(51); assert_eq!(51, instance.get_data()); } #[test] fn it_works_with_new_and_box() { let instance = Box::new(StructType::new(52)); assert_eq!(Box::new(StructType::new(52)), instance); } #[test] fn it_works_with_get_and_box() { let instance = Box::new(StructType::new(53)); assert_eq!(53, instance.get_data()); } use trait_exerci::TraitKanal; #[test] fn it_works_with_set() { let mut instance = StructType::new(54); instance.set_data(&55); assert_eq!(StructType::new(55), instance); } #[test] fn it_works_with_set_and_box() { let mut instance = Box::new(StructType::new(56)); instance.set_data(&57); assert_eq!(Box::new(StructType::new(57)), instance); } }
题外话
浅说指针类型Box
Rust标准库提供了类型Box<T>
。可以使用该类型Box<T>
在堆上分配内容。此类型用于安全地抽象指向堆内存的指针。同时它具有更大的灵活性,允许将实现特质如TraitCanal
的任何事物进行Box
类型化。
#[derive(Debug, Default)] pub struct Person { name: String, age: u32, } fn main() { let person :Box<Person> = Box::new(Default::default()); println!("{:?}", person); }
参考资料
题外话
Cargo工具第三方插件
# --------------------------
# install
cargo install cargo-update
# --------------------------
# install
cargo install cargo-audit
# or upgrade with the crate `cargo`
cargo install --force cargo-audit
# or upgrade with the crate `cargo-update`
cargo install-update cargo-audit
# using
cargo audit
# --------------------------
# install
cargo install cargo-bloat
# using
# only 'bin' and 'cdylib' crate types are supported.
cargo bloat --release -n 10
cargo bloat --release --crates
# --------------------------
# install
cargo install cargo-edit
# using
cargo add <CRATE>
cargo rm <CRATE>
cargo upgrade
# --------------------------
# install
cargo install cargo-asm
cargo asm <CRATE_NAME>::<MOD_NAME>::<FUNCTION_NAME>
cargo llvm-ir <CRATE_NAME>::<MOD_NAME>::<FUNCTION_NAME>
参考资料
- Keeping-secure-with-cargo-audit-0.9
- cargo-audit
- cargo-update
- cargo-watch
- cargo-asm
- how-to-get-assembly-output-from-building-with-cargo
软件篋mod_trait_exerci
学习内容
- 学习软件篋文件、模块与程序结构
- 了解和学习动态调度关键词
dyn
- 学习和理解动态与静态调度(Static vs Dynamic Dispatch)
- 衔接类型关键词
trait
作用
参考资料
- rust_testing_mocking/slides/2113/testing_in_rust_by_donald_whyte.pdf
- std keyword dyn
- dyn-trait-for-trait-objects
- rust-traits-and-trait-objects
- a_quick_look_at_trait_objects_in_rust
- Code
- what-makes-something-a-trait-object
- Dynamic_dispatch
- reference identifiers
- rust-traits-and-trait-objects
- traits-on-generics
- learning-generics-in-rust
- generic-returns-in-rust
- writing-a-hashmap-to-struct-procedural-macro-in-rust
- impls_and_traits
关于软件篋mod_trait_exerci
学习内容
- 了解项目名称和目录
篇目
项目名称清单
项目类型 | 项目名称 | 相对路径 | 项目说明 |
---|---|---|---|
作业区 | hello-mod-trait | ./hello-mod-trait | 开发共享软件篋工作区 |
共享篋 | lib-hello | ./hello-mod-trait/lib-hello | 开发共享软件篋实例 |
本地程序 | bin-local-hello | ./hello-mod-trait/bin-local-hello | 使用在本地的共享篋 |
仓库程序 | bin-hello | ./hello-mod-trait/bin-hello | 使用在crates.io上共享篋 |
软件篋类型清单
篋类型 | 篋名称 | 相对路径 |
---|---|---|
共享软件篋 | mod_trait_exerci | ./hello-mod-trait/lib-hello |
可执行程序 | bin-local-hello | ./hello-mod-trait/bin-local-hello |
可执行程序 | bin-hello | ./hello-mod-trait/bin-hello |
项目目录文件结构
$ tree -L 3
.
├── Cargo.lock
├── Cargo.toml
├── README.md
├── bin-hello
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── src
│ │ ├── bin
│ │ └── main.rs
│ └── target
│ └── debug
├── bin-local-hello
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── src
│ │ └── main.rs
│ ├── target
│ │ └── debug
│ └── tests
│ └── type_both_hello.rs
└── lib-hello
├── Cargo.lock
├── Cargo.toml
├── README.md
├── examples
│ ├── bare_hello.rs
│ ├── box_dynamic_hello.rs
│ ├── box_static_hello.rs
│ ├── generics_fn_hello.rs
│ ├── generics_impl_hello.rs
│ ├── generics_trait_hello.rs
│ ├── generics_type_hello.rs
│ ├── simple_dynamic_dispatch.rs
│ ├── simple_static_dispatch.rs
│ ├── trait_dispatch_abstract.rs
│ ├── trait_dispatch_concrete.rs
│ ├── trait_fn_hello.rs
│ ├── trait_instance_hello.rs
│ └── trait_where_hello.rs
├── src
│ ├── lib.rs
│ ├── mod_bare
│ ├── mod_dynamic_fn.rs
│ ├── mod_static_fn.rs
│ └── mod_where_fn.rs
└── tests
├── box_dynamic_hello.rs
├── box_static_hello.rs
├── mod_bare.rs
└── mod_trait.rs
文件与模块
在这一节里,学习Cargo项目文件、目录与模块相互关系。Rust语言表达模块的三种方式。
学习内容
- 了解和学习本软件篋模块文件结构
- 理解和掌握Cargo项目目录与文件关系
- 理解和掌握Cargo项目模块与文件关系
- 理解和掌握Cargo项目文件与文件关系
篇目
文件本身表达模块方式
Ⓓ 使用模块关键词mod
和代码块{}
的表达式,在Rust语言程序文件里,可以定义任何一个或者多个不同名称的模块。
在文件lib.rs里,创建了称之为mod_trait
模块,该模块实现代码也在该文件里。
文件名称表达模块方式
Ⓓ 使用模块关键词mod
语句,在Rust语言程序文件里,可以定义任何一个或者多个不同的模块。
使用关键词mod
语句,实现模块代码存在两种形式:以文件名称作为模块名称方式和以目录名称为模块名称方式。这里先解释前面一种情况,如程序文件mod_generics.rs
。
实现以文件名称作为模块名称具体方法是,在文件lib.rs
里,使用语句pub mod mod_generics;
,且在与文件lib.rs
相同的目录下,创建名称为mod_generics.rs
模块程序文件,为了其自身模块,该文件不需要使用模块关键词了。
目录名称表达模块方式
程序文件mod.rs
是第三种表达模块方式,即以目录名称为模块名称方式。
在程序文件lib.rs
里,使用语句pub mod mod_bare;
,说明了该模块是外部文件实现模块代码,但是从中微分确认其实现方式。我们看到在与文件lib.rs
相同目录下存在目录mod_bare
,说明了该实现在该目录下的文件,默认情况下就是程序文件mod.rs
,所有模块目录的入口文件默认情况下都是该文件名称。
参考资料
- how-to-use-one-module-from-another-module-in-a-rust-cargo-project
- modules-in-rust-programming-language
- rust-gentle-intro/modules
- ch07-01-mod-and-the-filesystem
- how-to-use-one-module-from-another-module-in-a-rust-cargo-project
共享篋:程序结构和代码解释
在本节里,了解两个模块mod_bare
和mod_trait
结构和代码实现。
学习内容
- 了解和学习不同结构类型关键词
struct
实现方法
篇目
基于结构类型的实现
模块mod_bare
结构
模块mod_bare
代码
https://doc.rust-lang.org/stable/error-index.html#E0038
#[derive(Debug, Default, PartialEq)] pub struct StructType { pub data: (u32), } impl StructType { pub fn get_tuple(&self) -> (u32) { (self.data) } } #[derive(Debug, Default, PartialEq)] pub struct TupleType (pub u32); impl TupleType { pub fn get_tuple(&self) -> (u32) { (self.0) } }
基于衔接特质的实现
模块mod_trait
结构
模块mod_trait
代码
两个类型的函数new()是通过属性值,实现创建其类型的实例,而特质方法get_object()是通过其类型本身实例,实现创建一个新的类型实例。
pub mod mod_bare; pub mod mod_where_fn; pub mod mod_static_fn; pub mod mod_box_static_fn; pub mod mod_dynamic_fn; pub mod mod_box_dynamic_fn; pub mod mod_trait { #[derive(Debug, Default, PartialEq, Copy, Clone)] pub struct StructType { data: (u32), } #[derive(Debug, Default, PartialEq, Copy, Clone)] pub struct TupleType(pub u32); pub trait TraitCanal { fn get_object(&self) -> Self where Self: Sized; // For keyword `dyn` //fn get_object(&self) -> Self; // E0038 For keyword `dyn`; OK for static functions fn get_tuple(&self) -> (u32); } impl TraitCanal for StructType { fn get_object(&self) -> Self { StructType{data: self.data} } fn get_tuple(&self) -> (u32) { println!("impl TraitCanal for StructType"); (self.data) } } impl TraitCanal for TupleType { fn get_object(&self) -> Self { TupleType(self.0) } fn get_tuple(&self) -> (u32) { println!("impl TraitCanal for TupleType"); (self.0) } } impl StructType { pub fn new(_data: u32) -> Self { StructType{data: _data} } } impl TupleType { pub fn new(_data: u32) -> Self { TupleType(_data) } } }
题外话
学习理解编译错误
在程序文件src/lib.rs
里,使用关键词trait
定义衔接特质TraitCanal
代码块的第一行代码注释掉,而第二行代码去掉注释,一旦执行编译,就会出现下面错误信息:error[E0038]
。
error[E0038]: the trait `mod_trait::TraitCanal` cannot be made into
an object
--> lib-hello/src/mod_dynamic_fn.rs:4:1
|
4 | pub fn get_dynamic_trait_ref(canal: &dyn TraitCanal) -> (u32) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ t
he trait `mod_trait::TraitCanal` cannot be made into an object
|
= note: method `init` references the `Self` type in its arguments
or return type
下面链接里的E0038
就是该错误编号,点击下面链接就可以了解到错误的原因信息。
https://doc.rust-lang.org/stable/error-index.html#E0038
参考资料
三种调用方式解释
在本节里,通过两个模块mod_bare
和mod_trait
实现及其三种调用方式比较,详细说明衔接关键词trait
作用。
学习内容
- 理解衔接关键词
trait
的重要性 - 掌握衔接关键词
trait
实现的应用方法
篇目
- 基于结构类型的实现:使用实例调用
- 基于衔接特质的实现:使用实例调用
- 基于衔接特质的模块
- 基于衔接特质的实现:作为参数调用
- 题外话
- 浅说面向对象编程、命令式编程和声明式编程
- 数据类型数组
- 泛型函数与方法
- 类型范围关键词
where
- 参考资料
基于结构类型的实现:使用实例调用
从上面图示,可以了解到下面程序文件直接访问模块mod_bare
,该模块是基于结构类型的实现,不存在衔接特质。
// File: examples/bare_hello.rs use mod_trait_exerci::mod_bare::StructType; use mod_trait_exerci::mod_bare::TupleType; fn get_data_from_struct(instances: [StructType; 2]) { let data = instances[0].get_tuple(); assert_eq!(0, data); assert_eq!((0), data); assert_eq!(instances[0], instances[1]); println!("{:?}", instances[0]); println!("{:?}", instances[1]); } fn get_data_from_tuple(instances: [TupleType; 2]) { let data = instances[0].get_tuple(); assert_eq!(0, data); assert_eq!((0), data); assert_eq!(instances[0], instances[1]); println!("{:?}", instances[0]); println!("{:?}", instances[1]); } // clear && cargo run --example bare_hello fn main() { let instances: [StructType; 2] = [Default::default(), StructType{data:0}]; get_data_from_struct(instances); let instances: [TupleType; 2] = [Default::default(), TupleType(0)]; get_data_from_tuple(instances); }
基于衔接特质的实现:使用实例调用
从上面图示,下面程序文件也是直接访问模块mod_trait
本身,与上面应用实例不同,这里通过衔接特质方式实现代码,从代码的关键词use
语句也可以了解到这一点。
比较上面程序文件bare_hello.rs
和下面文件trait_instance_hello.rs
的关键词use
语句,可以看到,上面程序文件里的关键词use
语句访问模块,采用了绝对路径方式,而下面文件既有绝对路径方式,也有相对路径方式。
// File: examples/trait_instance_hello.rs use mod_trait_exerci::mod_trait; use mod_trait::TraitCanal; use mod_trait::StructType; use mod_trait::TupleType; fn get_data_from_struct(instances: [StructType; 2]) { let data = instances[0].get_tuple(); assert_eq!(0, data); assert_eq!((0), data); assert_eq!(instances[0], instances[1]); println!("{:?}", instances[0]); println!("{:?}", instances[1]); } fn get_data_from_tuple(instances: [TupleType; 2]) { let data = instances[0].get_tuple(); assert_eq!(0, data); assert_eq!((0), data); assert_eq!(instances[0], instances[1]); println!("{:?}", instances[0]); println!("{:?}", instances[1]); } // clear && cargo run --example trait_instance_hello fn main() { let instances: [StructType; 2] = [Default::default(), StructType::new(0)]; get_data_from_struct(instances); let instances: [TupleType; 2] = [Default::default(), TupleType::new(0)]; get_data_from_tuple(instances); }
基于衔接特质的模块
从上面图示,可以了解到,下面程序文件也是一个模块mod_where_fn
,该模块应用了模块mod_trait
的功能,注意该文件关键词use
语句只使用了衔接特质TraitCanal
,这也是一种访问其父模块的相对路径方式。它的存在意义是什么?这是为用户提供更加灵活的实现而设计的。这里提供了怎么样使用模块,而不必用户完成实现。
下面代码的方法是一种泛型方法的表达方式,其概念参考下面说明。
下面将会说明关键词where
。在文件mod_where_fn.rs
里的两个函数是两个完全相同的实现,只是语法表达方式不同而已。
// File: src/mod_where_fn.rs use super::mod_trait::TraitCanal; pub fn get_static_type_ref<Type: TraitCanal>(typ: &Type) -> (u32) { typ.get_tuple() } pub fn get_static_type_ref_with_where<Type>(typ: &Type) -> (u32) where Type: TraitCanal { typ.get_tuple() }
基于衔接特质的实现:作为参数调用
从上面图示,可以了解到下面程序文件并不是直接访问模块mod_trait
本身,而是访问中间模块mod_where_fn
完成的,这是一种更加方便和灵活的三层程序设计结构。与模块mod_trait
不同,它实现的功能更多的是数据操作功能,这里称之为“数据模块
”,而这种中间模块mod_where_fn
,它提供更多样化的有实际意义知识性功能,这里称之为“知识模块
”。
无论是什么类型结构,为了获取不同的结构类型属性,都不需要访问其属性名称。
在使用模块mod_where_fn
的函数时,结构类型的实例是作为参数传递给该模块的函数。我们仅仅告诉它我们是‘谁’,我们需要做什么。
模块mod_trait
告诉我们是什么功能,这好比是产品生产原料。模块mod_where_fn
解决了怎么做问题,这好比是为用户预先打造的特定产品,当然用户也可以自己制造自己需要的产品。这里程序文件说明了要做什么事情,这好比作为用户使用现成的产品。
// File: examples/trait_where_hello.rs use mod_trait_exerci::mod_where_fn; use mod_trait_exerci::mod_trait; use mod_trait::StructType; use mod_trait::TupleType; fn static_struct_ref_with_where(instance: &StructType) { let data = mod_where_fn::get_static_type_ref_with_where(instance); assert_eq!(0, data); assert_eq!((0), data); } fn static_tuple_ref_with_where(instance: &TupleType) { let data = mod_where_fn::get_static_type_ref_with_where(instance); assert_eq!(0, data); assert_eq!((0), data); } // clear && cargo run --example trait_where_hello fn main() { let instance: StructType = Default::default(); static_struct_ref_with_where(&instance); let instance: TupleType = Default::default(); static_tuple_ref_with_where(&instance); }
题外话
浅说面向对象编程、命令式编程和声明式编程
面向对象编程回答了是什么,命令式编程回答了怎么做,而声明式编程回答了做什么。
数据类型数组
在Rust语言里,数组是一种基本数据类型。
泛型函数与方法
在类型理论中,泛型称之为参数多态(parametric polymorphism),对于给定参数(parametric)能够有多种形式(poly是多,morph是形态)的函数或类型。
在Rust语言里,泛型是一种非常广泛采用的技术,不仅应用函数与方法关键词fn
,也应用衔接特质关键词trait
等等。其目的是,减少代码重复。
类型范围关键词where
关键词where
用于向泛型类型添加约束,并为编译器提供解决问题所需的信息!
参考资料
理解动态与静态调度实现
在这一节里,介绍Rust语言调度动态与静态函数的方式。动态与静态调度函数是计算机语言广泛应用的概念和技术。
学习内容
- 了解和学习Rust语言静态和动态函数概念
- 理解和掌握静态和动态函数基本实现
篇目
最常见静态函数实例
Ⓓ 在默认情况下,Rust语言方法都是静态函数。如下面代码的函数static_dispatch()
。
静态调度或者分派(static dispatch)在编译时就知道被调用方是谁,而动态调度只有在运行时,才知道被调用方是谁。显然,在常见的情况下,相比动态调度,在运行程序时,静态调度会更快速,而相比静态调度,在编译程序时,动态调度会更快速。
在使用静态分派会更有效,因为总是可以使用静态分派包装器函数来执行动态调度,但反之则不然。由于这个原因,比如标准库尝试在尽可能的情况下进行静态调度。
// File: examples/simple_static_dispatch.rs #[derive(Default)] struct NormalStruct { data: (u8) } impl NormalStruct { fn static_dispatch(&self) -> (u8) { (self.data) } } // clear && cargo run --example simple_static_dispatch fn main() { let instance_struct :NormalStruct = Default::default(); assert_eq!((0u8), instance_struct.static_dispatch()); }
静态函数的动态调度实例
上面程序与下面程序的前面一部分是完全一致的。而下面程序的后面一部分代码动态方式调度静态函数。
下面程序的关键词dyn
就是告诉编译器需要进行动态方式调度。但是,要是关键词dyn
不在,也能够通过编译,只是有警告提示“不带显式dyn
的衔接特质对象已弃用”,这告诉我们代码需要增加动态调度关键词dyn
。
这个警告提示提供了一条信息:我们正在涉及到的是衔接特质的一个或者一组对象,之所以需要动态方式,是因为,在Rust语言里衔接特质关键词trait
可以针对任何类型实现功能,这是一种未知类型行为的随时调度方式。衔接特质的对象如同一个衔接插口,可以随时插到任何一个类型上。
在动态调度时,Rust语言需要衔接特质对象的指针。从下面后面的三段代码里,可以理解到这个概念。比如。指针类型Box
把实例instance_struct
包装为指针类型;类型Vec的内部项都是实例instance_struct
的指针类型。
// File: examples/simple_dynamic_dispatch.rs trait Trait { fn static_dispatch(&self) -> (u8); } #[derive(Default)] struct Struct {data: (u8)} impl Trait for Struct { fn static_dispatch(&self) -> (u8) { (self.data) } } // clear && cargo run --example simple_dynamic_dispatch fn main () { let instance_struct: Struct = Default::default(); assert_eq!((0u8), instance_struct.static_dispatch()); let instance_struct: Struct = Default::default(); let box_struct: Box<dyn Trait> = Box::new(instance_struct); assert_eq!((0), box_struct.static_dispatch()); let instance_struct: Struct = Default::default(); let mut v: Vec<&dyn Trait> = Vec::new(); v.push(&instance_struct); for instance_struct in v.iter() { assert_eq!((0), instance_struct.static_dispatch()); } let instance_struct: Struct = Default::default(); let mut v: Vec<Box<dyn Trait>> = Vec::new(); v.push(Box::new(instance_struct)); for instance_struct in v.iter() { assert_eq!((0), instance_struct.static_dispatch()); } }
调用动态函数的实例
这里将说明基于衔接特质关键性trait
的静态和动态函数实现,但是这静态函数与之前的也是完全不一样的概念。另外将会看到调用这种动态函数,它们看起来是一些更复杂的静态和动态函数。
下面程序第二段代码的两个函数static_dispatch()
和dynamic_dispatch()
,它们的目的是解决代码重复的相同问题。但是其手段是不同的。
Rust语言没有继承概念,继承编程不再是软件开发的思想。通过关键词trait
定义函数,借助于关键词impl实现函数及其泛型编程方法,以实现多态式编程方法。静态函数static_dispatch()
使用了泛型编程方法,关于泛型编程将有另外专题说明。
Rust语言也通过衔接特质对象及动态调度编程方法,来实现多态式编程方法。动态函数dynamic_dispatch()
使用了动态编程方法。特性对象是不限类型的,动态绑定类型是通过实时运行时具体地匹配类型。
从代码上看,静态函数static_dispatch()
和动态函数dynamic_dispatch()
都是实现相同的功能。
下面程序第三段代码里,无论是类型NormalStruct
,还是类型TupleStruct
,它们都是以同一方式分别调用函数static_dispatch()
和函数dynamic_dispatch()
。
不管是静态函数,还是动态函数,它们都是基于衔接特质对象的指针实现,这一点是非常重要的。
下面程序是Rust语言非常经典的代码结构。
// File: examples/trait_dispatch_concrete.rs // 1. Define and Implement struct, trait and impl..for struct NormalStruct { data: (u8) } trait Trait { fn _fn(&self) -> (u8); } impl Trait for NormalStruct { fn _fn(&self) -> (u8) { (self.data) } } struct TupleStruct(u8); impl Trait for TupleStruct { fn _fn(&self) -> (u8) { (self.0) } } // 2. Implement static and dynamic dispatch fn static_dispatch<TraitObject: Trait>(r#type: &TraitObject) { r#type._fn(); } fn dynamic_dispatch(r#trait: &dyn Trait) { r#trait._fn(); } // 3. Use these dispatch functions // clear && cargo run --example trait_dispatch_concrete fn main() { let instance_struct = NormalStruct{data: 0}; assert_eq!((), static_dispatch(&instance_struct)); assert_eq!((), dynamic_dispatch(&instance_struct)); let instance_tuple = TupleStruct(0); assert_eq!((), static_dispatch(&instance_tuple)); assert_eq!((), dynamic_dispatch(&instance_tuple)); }
题外话
原始标识符前缀r#
原始标识符也是一种标识符,其前缀是r#
,之后也可以加上任何严格或保留的关键字,但除关键字crate, extern, self, super, Self
外。
向量数据类型Vec
向量数据类型Vec
也是一种数组,其内部是以0开始进行排序的,但是这种数组大小是可调整的,或者说是一种连续的且可增长的数组类型。
参考资料
- reference identifiers
- static-and-dynamic-dispatch
- book static-and-dynamic-dispatch
- when-does-dynamic-vs-static-dispatch-matter
- riptutorial.com static-and-dynamic-dispatch
- exploring-dynamic-dispatch-in-rust
- rust-traits-and-trait-objects
- Static vs Dynamic dispatch
- a_quick_look_at_trait_objects_in_rust
- b5.impls_and_traits
- std keyword dyn
- Vector of objects belonging to a trait
深度解析动态与静态调度实现
在本节里。主要解释静态函数与动态函数的内部结构。
学习内容
- 进一步学习静态函数与动态函数
- 通过Miri了解分析Rust语言代码
篇目
什么是Miri
Miri网站说明如下:
An experimental interpreter for Rust's mid-level intermediate representation (MIR).
直接翻译为:Rust语言的中间中级水平表达层(mid-level intermediate representation,MIR)的实验解释器。这里有两个中间是什么意思?“intermediate”是说明Miri是介于Rust语言与汇编语言编译过程的中间位置;“mid-level”可以理解为表达层代码水平。
Miri作用是什么?在编译器中引入这一表达层(MIR),消除了Rust语言代码大部分表面的表示层(mid-level),留下了一种更简单的形式,目的是适合于类型检查和翻译成汇编语言(intermediate)。
与Miri相关的Cargo工具命令
在项目根目录下,执行代码命令,可以得到Miri代码文件。一般情况下,该文件是不会出现在项目目录里的。
这个命令是两部分,在`cargo之前部分,是告诉编译器想到得到额外的编译结果。后面部分是想编译什么内容。
与Miri编译相关内容是在release
编译版本下才能得到。
RUSTFLAGS="--emit mir" cargo build --release --example trait_dispatch_concrete
打开Miri代码文件命令
可以通过资源管理器来寻找如下命令里的目录文件,也可以使用下面命令打开,其中-t
是告诉命令open
使用默认编辑器打开该文件。该文件名称非常长,所以命令里使用了星号。
从网络上看,还没有能够显示Miri代码的工具。
open -t ./target/release/examples/trait_dispatch_concrete-*.mir
Miri代码实例说明
为了说明问题,下面Miri代码仅仅是其一部分代码,包括静态函数和动态函数,并且还是省略过的。凡是"..."都是两个函数相同的代码。
从下面的Miri代码里,可以看到,函数static_dispatch()
的参数只有一个,它是衔接特质的对象指针,函数dynamic_dispatch
的参数也只有一个,它是衔接特质的指针。除了这一点区别之外,其余都是一样的。
从上面分析可以了解到,衔接特质的对象是确定的,使用它,可以理解为已知类型的特质准备的,而动态的衔接特质是不确定的,可以理解为未知类型的特质准备的。
fn static_dispatch(_1: &TraitObject) -> () {
...
let mut _3: &TraitObject; // in scope 0 at lib-hello/examples/trait_dispatch_concrete.rs:23:5: 23:11
bb0: {
...
_2 = const <TraitObject as Trait>::fn(move _3) -> bb1; // bb0[3]: scope 0 at lib-hello/examples/trait_dispatch_concrete.rs:23:5: 23:18 //...
}
bb1: {
...
}
}
fn dynamic_dispatch(_1: &dyn Trait) -> () {
...
let mut _3: &dyn Trait; // in scope 0 at lib-hello/examples/trait_dispatch_concrete.rs:27:5: 27:12
bb0: {
...
_2 = const <dyn Trait as Trait>::fn(move _3) -> bb1; // bb0[3]: scope 0 at lib-hello/examples/trait_dispatch_concrete.rs:27:5: 27:19 //...
}
bb1: {
...
}
}
类型泛型参数与特质对象
类型泛型参数(generics type parameters)与特质对象(trait objects)
参考资料
共享篋:简单三层结构实现
在前面学习概念基础之上,在这一节里,将会实现共享篋的简单三层结构。
学习内容
- 理解和掌握知识模块的实现
篇目
静态函数的知识模块实现
下面模块mod_static_fn
代码,利用衔接特质TraitCanal
,实现了两个不同功能的静态函数:get_static_type_ref()
和print_static_all_daten()
。
关于函数get_static_type_ref()
,输入实例是类型StructType
或者TupleType
的指针,输出是类型tuple
,其元素值是它们属性data
值。
关于函数print_static_all_daten()
,输入实例是StructType
或者TupleType
数组的指针,程序接受输入实例以后,打印输入实例及其属性内容。
为了使用函数print_static_all_daten()
里的打印宏,程序里需要做到两点:
- 使用语句:
use std::fmt::Debug;
; - 函数参数里增加该特质
Debug
;
但是第一条语句应该不需要,因为类型StructType
和TupleType
已经声明过了特质Debug
。如特质PartialEq
就没有使用use
语句,这是因为类型StructType
和TupleType
也已经声明过了PartialEq
。但是有一点是肯定的:先要声明特质,然后还要在静态函数里使用它们,才能真正实现使用这些特质。
// File: src/mod_static_fn.rs use super::mod_trait::TraitCanal; use std::fmt::Debug; use std::cmp::PartialEq; //get_static_type_ref<T> //get_static_trait_ref::<StructType> //get_static_trait_ref::<TupleType> // get_static_type_ref<Type: TraitCanal>(typ: &Type) -> (u32) //get_static_trait_ref(typ: &StructType) -> (u32) //get_static_trait_ref(typ: &TupleType) -> (u32) // static: Generics type parameters // dynamic: trait objects pub fn get_static_type_ref<Type: TraitCanal>(typ: &Type) -> (u32) { (typ.get_tuple()) } pub fn print_static_all_daten<Type: TraitCanal+Debug+PartialEq>(typs: &[Type]) { for typ in typs { let data = typ.get_tuple(); println!("{:?}", typ); // FOR Debug println!("{:?}", data); // FOR Debug assert_eq!(*typ, typ.get_object()); // FOR PartialEq } }
动态函数的知识模块实现
下面模块mod_dynamic_fn
代码的函数,与前面的说明完全类似。
// File: src/mod_dynamic_fn.rs use super::mod_trait::TraitCanal; pub fn get_dynamic_trait_ref(canal: &dyn TraitCanal) -> (u32) { (canal.get_tuple()) }
应用实例
// File: examples/trait_fn_hello.rs // clear && cargo run --example trait_fn_hello use mod_trait_exerci::mod_static_fn; use mod_trait_exerci::mod_dynamic_fn; use mod_trait_exerci::mod_trait; use mod_trait::StructType; use mod_trait::TupleType; fn get_data_from_struct(instance: &StructType) { let data = mod_static_fn::get_static_type_ref(instance); assert_eq!(0, data); assert_eq!((0), data); let data = mod_dynamic_fn::get_dynamic_trait_ref(instance); assert_eq!(0, data); assert_eq!((0), data); } fn get_data_from_tuple(instance: &TupleType) { let data = mod_static_fn::get_static_type_ref(instance); assert_eq!(0, data); assert_eq!((0), data); let data = mod_dynamic_fn::get_dynamic_trait_ref(instance); assert_eq!(0, data); assert_eq!((0), data); } // clear && cargo run --example trait_fn_hello fn main() { let instance: StructType = Default::default(); get_data_from_struct(&instance); let instance: TupleType = Default::default(); get_data_from_tuple(&instance); let instance: TupleType = Default::default(); let instances = vec![instance, TupleType::new(100)]; mod_static_fn::print_static_all_daten(&instances); }
参考资料
共享篋:基于封装的静态调度实现
在这一节里,列出基于指针类型Box
封装的静态调度实现。
学习内容
- 了解和学习基于指针类型
Box
封装的静态函数实现方法
篇目
静态模块实现
// File: src/mod_static_fn.rs use super::mod_trait::TraitCanal; pub fn get_static_box_ref<Type: TraitCanal>(typ: &Box<Type>) -> (u32) { (typ.get_tuple()) } pub fn get_static_box<Type: TraitCanal>(typ: Box<Type>) -> (u32) { (typ.get_tuple()) } pub fn get_static_box_type_ref<Type: TraitCanal + ?Sized>(typ: Box<&Type>) -> (u32) { (typ.get_tuple()) } pub fn get_static_box_and_type_ref<Type: TraitCanal + ?Sized>(typ: &Box<&Type>) -> (u32) { (typ.get_tuple()) }
静态模块应用
use mod_trait_exerci::mod_box_static_fn; use mod_trait_exerci::mod_trait::StructType; use mod_trait_exerci::mod_trait::TraitCanal; use mod_trait_exerci::mod_trait::TupleType; // clear && cargo run --example box_static_hello fn main() { let instance: StructType = Default::default(); let instance_box_type: Box<StructType> = Box::new(instance); assert_eq!((0), mod_box_static_fn::get_static_box_ref(&instance_box_type)); assert_eq!((0), mod_box_static_fn::get_static_box(instance_box_type)); let instance: StructType = Default::default(); let instance_box_type: Box<&dyn TraitCanal> = Box::new(&instance); assert_eq!((0), mod_box_static_fn::get_static_box_and_type_ref(&instance_box_type)); assert_eq!((0), mod_box_static_fn::get_static_box_type_ref(instance_box_type)); let instance: TupleType = Default::default(); let instance_box_type: Box<TupleType> = Box::new(instance); assert_eq!((0), mod_box_static_fn::get_static_box_ref(&instance_box_type)); assert_eq!((0), mod_box_static_fn::get_static_box(instance_box_type)); let instance: TupleType = Default::default(); let instance_box_type: Box<&dyn TraitCanal> = Box::new(&instance); assert_eq!((0), mod_box_static_fn::get_static_box_and_type_ref(&instance_box_type)); assert_eq!((0), mod_box_static_fn::get_static_box_type_ref(instance_box_type)); }
参考资料
共享篋:基于封装的动态调度实现
在这一节里,列出基于指针类型Box
封装的动态调度实现。
学习内容
- 了解和学习基于指针类型
Box
封装的动态函数实现方法
篇目
动态模块实现
// File: src/mod_dynamic_fn.rs use super::mod_trait::TraitCanal; pub fn get_dynamic_box(canal: Box<dyn TraitCanal>) -> (u32) { canal.get_tuple() } pub fn get_dynamic_box_trait_ref(canal: Box<&dyn TraitCanal>) -> (u32) { canal.get_tuple() } pub fn get_dynamic_box_ref(canal: &Box<dyn TraitCanal>) -> (u32) { canal.get_tuple() } pub fn get_dynamic_box_and_trait_ref(canal: &Box<&dyn TraitCanal>) -> (u32) { canal.get_tuple() }
动态模块应用
// File: examples/box_dynamic_hello.rs use mod_trait_exerci::mod_box_dynamic_fn; use mod_trait_exerci::mod_trait::StructType; use mod_trait_exerci::mod_trait::TraitCanal; use mod_trait_exerci::mod_trait::TupleType; // clear && cargo run --example box_dynamic_hello fn main() { let instance: StructType = Default::default(); let instance_box_trait: Box<dyn TraitCanal> = Box::new(instance); assert_eq!((0), mod_box_dynamic_fn::get_dynamic_box_ref(&instance_box_trait)); assert_eq!((0), mod_box_dynamic_fn::get_dynamic_box(instance_box_trait)); let instance: StructType = Default::default(); let instance_box_trait: Box<&dyn TraitCanal> = Box::new(&instance); assert_eq!((0), mod_box_dynamic_fn::get_dynamic_box_and_trait_ref(&instance_box_trait)); assert_eq!((0), mod_box_dynamic_fn::get_dynamic_box_trait_ref(instance_box_trait)); let instance: TupleType = Default::default(); let instance_box_trait: Box<dyn TraitCanal> = Box::new(instance); assert_eq!((0), mod_box_dynamic_fn::get_dynamic_box_ref(&instance_box_trait)); assert_eq!((0), mod_box_dynamic_fn::get_dynamic_box(instance_box_trait)); let instance: TupleType = Default::default(); let instance_box_trait: Box<&dyn TraitCanal> = Box::new(&instance); assert_eq!((0), mod_box_dynamic_fn::get_dynamic_box_and_trait_ref(&instance_box_trait)); assert_eq!((0), mod_box_dynamic_fn::get_dynamic_box_trait_ref(instance_box_trait)); }
参考资料
共享篋:单元测试代码解释
在这一节里,实现共享篋每一个模块的单元测试代码。
学习内容
- 了解和学习实现基于不同类型的单元测试方法
篇目
模块mod_bare
单元测试
// File: tests/u_for_mod_bare.rs // clear && cargo test // clear && cargo test --package mod_trait_exerci // $ clear && cargo test --test u_for_mod_bare #[cfg(test)] mod tests { use mod_trait_exerci::mod_bare; use mod_bare::StructType; use mod_bare::TupleType; #[test] fn it_works_with_struct_default() { let instance: StructType = Default::default(); assert_eq!(0, instance.get_tuple()); assert_eq!((0), instance.get_tuple()); } #[test] fn it_works_with_struct_struct() { let instance = StructType{data:(0)}; assert_eq!(0, instance.get_tuple()); assert_eq!((0), instance.get_tuple()); } #[test] fn it_works_with_struct() { let instance_default: StructType = Default::default(); let instance_struct = StructType{data:(0)}; assert_eq!(instance_default, instance_struct); } #[test] fn it_works_with_tuple_default() { let instance: TupleType = Default::default(); assert_eq!(0, instance.get_tuple()); assert_eq!((0), instance.get_tuple()); } #[test] fn it_works_with_tuple_struct() { let instance = TupleType(0); assert_eq!(0, instance.get_tuple()); assert_eq!((0), instance.get_tuple()); } #[test] fn it_works_with_tuple() { let instance_default: TupleType = Default::default(); let instance_tuple = TupleType(0); assert_eq!(instance_default, instance_tuple); } }
基于实例的模块mod_trait
单元测试
// File: tests/u_for_mod_trait_instance.rs // clear && cargo test // clear && cargo test --package mod_trait_exerci // $ clear && cargo test --test u_for_mod_trait_instance #[cfg(test)] mod tests { use mod_trait_exerci::mod_trait; use mod_trait::TraitCanal; use mod_trait::StructType; use mod_trait::TupleType; #[test] fn it_works_with_struct_default() { let instance: StructType = Default::default(); assert_eq!(0, instance.get_tuple()); assert_eq!((0), instance.get_tuple()); } #[test] fn it_works_with_struct_new() { let instance = StructType::new(0); assert_eq!(0, instance.get_tuple()); assert_eq!((0), instance.get_tuple()); } #[test] fn it_works_with_struct() { let instance_default: StructType = Default::default(); let instance_struct = StructType::new(0); assert_eq!(instance_default, instance_struct); } #[test] fn it_works_with_tuple_default() { let instance: TupleType = Default::default(); assert_eq!(0, instance.get_tuple()); assert_eq!((0), instance.get_tuple()); } #[test] fn it_works_with_tuple_struct() { let instance = TupleType::new(0); assert_eq!(0, instance.get_tuple()); assert_eq!((0), instance.get_tuple()); } #[test] fn it_works_with_tuple() { let instance_default: TupleType = Default::default(); let instance_tuple = TupleType::new(0); assert_eq!(instance_default, instance_tuple); } }
基于中间模块的实例单元测试
// File: tests/u_for_mod_trait_fn.rs // clear && cargo test // clear && cargo test --package mod_trait_exerci // $ clear && cargo test --test u_for_mod_trait_fn #[cfg(test)] mod tests { use mod_trait_exerci::mod_static_fn; use mod_trait_exerci::mod_dynamic_fn; use mod_trait_exerci::mod_trait; use mod_trait::StructType; use mod_trait::TupleType; #[test] fn it_works_with_fn_struct_default() { let instance: StructType = Default::default(); let data = mod_static_fn::get_static_type_ref(&instance); assert_eq!(0, data); assert_eq!((0), data); let data = mod_dynamic_fn::get_dynamic_trait_ref(&instance); assert_eq!(0, data); assert_eq!((0), data); } #[test] fn it_works_with_fn_tuple_default() { let instance: TupleType = Default::default(); let data = mod_static_fn::get_static_type_ref(&instance); assert_eq!(0, data); assert_eq!((0), data); let data = mod_dynamic_fn::get_dynamic_trait_ref(&instance); assert_eq!(0, data); assert_eq!((0), data); } }
基于中间模块静态函数的类型Box
单元测试
// File: tests/u_for_box_static_hello.rs // clear && cargo test // clear && cargo test --package mod_trait_exerci // clear && cargo test --test u_for_box_static_hello #[cfg(test)] mod tests { use mod_trait_exerci::mod_box_static_fn; use mod_trait_exerci::mod_trait; use mod_trait::StructType; use mod_trait::TraitCanal; use mod_trait::TupleType; #[test] fn struct_static_box() { let instance: StructType = Default::default(); let instance_box_type: Box<StructType> = Box::new(instance); //assert_eq!(0, mod_box_static_fn::get_static_type_ref(&instance_box_type)); //assert_eq!(0, mod_box_static_fn::get_static_box_ref(&instance)); assert_eq!(0, mod_box_static_fn::get_static_box_ref(&instance_box_type)); assert_eq!(0, mod_box_static_fn::get_static_box(instance_box_type)); } #[test] fn struct_static_box_ref_and_type_ref() { let instance: StructType = Default::default(); let instance_box_type: Box<&dyn TraitCanal> = Box::new(&instance); assert_eq!(0, mod_box_static_fn::get_static_box_and_type_ref(&instance_box_type)); assert_eq!(0, mod_box_static_fn::get_static_box_type_ref(instance_box_type)); } #[test] fn tuple_static_box() { let instance: TupleType = Default::default(); let instance_box_type: Box<TupleType> = Box::new(instance); assert_eq!(0, mod_box_static_fn::get_static_box_ref(&instance_box_type)); assert_eq!(0, mod_box_static_fn::get_static_box(instance_box_type)); } #[test] fn tuple_static_box_type() { let instance: TupleType = Default::default(); let instance_box_type: Box<&dyn TraitCanal> = Box::new(&instance); assert_eq!(0, mod_box_static_fn::get_static_box_and_type_ref(&instance_box_type)); assert_eq!(0, mod_box_static_fn::get_static_box_type_ref(instance_box_type)); } }
基于中间模块动态函数的类型Box
单元测试
// File: tests/u_for_box_dynamic_hello.rs // clear && cargo test // clear && cargo test --package mod_trait_exerci // clear && cargo test --test u_for_box_dynamic_hello #[cfg(test)] mod tests { use mod_trait_exerci::mod_box_dynamic_fn; use mod_trait_exerci::mod_trait; use mod_trait::StructType; use mod_trait::TraitCanal; use mod_trait::TupleType; #[test] fn struct_dynamic_box() { let instance: StructType = Default::default(); let instance_box_trait: Box<dyn TraitCanal> = Box::new(instance); assert_eq!(0, mod_box_dynamic_fn::get_dynamic_box_ref(&instance_box_trait)); assert_eq!(0, mod_box_dynamic_fn::get_dynamic_box(instance_box_trait)); } #[test] fn struct_dynamic_box_and_trait() { let instance: StructType = Default::default(); let instance_box_trait: Box<&dyn TraitCanal> = Box::new(&instance); assert_eq!(0, mod_box_dynamic_fn::get_dynamic_box_and_trait_ref(&instance_box_trait)); assert_eq!(0, mod_box_dynamic_fn::get_dynamic_box_trait_ref(instance_box_trait)); } #[test] fn tulpe_dynamic_box() { let instance: TupleType = Default::default(); let instance_box_trait: Box<dyn TraitCanal> = Box::new(instance); assert_eq!(0, mod_box_dynamic_fn::get_dynamic_box_ref(&instance_box_trait)); assert_eq!(0, mod_box_dynamic_fn::get_dynamic_box(instance_box_trait)); } #[test] fn tulpe_dynamic_box_trait() { let instance: TupleType = Default::default(); let instance_box_trait: Box<&dyn TraitCanal> = Box::new(&instance); assert_eq!(0, mod_box_dynamic_fn::get_dynamic_box_and_trait_ref(&instance_box_trait)); assert_eq!(0, mod_box_dynamic_fn::get_dynamic_box_trait_ref(instance_box_trait)); } }
参考资料
题外话
篇目
泛型类型实例
#![allow(unused_variables)] struct Struct<T> (T); // clear && cargo run --example generics_type_hello fn main() { let instance = Struct(0u8); let instance = Struct(0u32); let instance = Struct('0'); let instance = Struct(0.0); let instance = Struct("0.0"); let instance = Struct(()); let instance = Struct([0]); let instance = Struct(Struct(0.0f64)); }
泛型方法实例
#![allow(unused_variables)] use std::fmt::Debug; fn print<T: Debug>(x: T) { println!("{:?}", x); } // clear && cargo run --example generics_fn_hello fn main() { print(0u8); print(0u32); print('0'); print(0.0); print("0.0"); print(()); print([0]); }
泛型实现实例
#![allow(unused_variables)] struct Struct<T> (T); impl<S> Struct<S> { fn get(&self) -> &S { &self.0 } } // clear && cargo run --example generics_impl_hello fn main() { let instance = Struct(0u8); instance.get(); let instance = Struct(0u32); instance.get(); let instance = Struct('0'); instance.get(); let instance = Struct("0"); instance.get(); }
泛型特质实例
#![allow(unused_variables)] struct Struct<T> (T); trait Trait<U> { fn get(&self) -> &U; } impl<S> Trait<S> for Struct<S> { fn get(&self) -> &S { &self.0 } } // clear && cargo run --example generics_trait_hello fn main() { let instance = Struct(0u8); instance.get(); let instance = Struct(0u32); instance.get(); let instance = Struct('0'); instance.get(); let instance = Struct("0"); instance.get(); }
// Dynamically Sized Types (DSTs) // https://doc.rust-lang.org/nomicon/exotic-sizes.html struct MySuperSliceable<T: ?Sized> { info: u32, data: T } fn main() { let sized: MySuperSliceable<[u8; 8]> = MySuperSliceable { info: 17, data: [0; 8], }; let dynamic: &MySuperSliceable<[u8]> = &sized; // prints: "17 [0, 0, 0, 0, 0, 0, 0, 0]" println!("{} {:?}", dynamic.info, &dynamic.data); }
参考资料
$$\text{只有心灵的淡定宁静,继而产生的身心愉悦,才是幸福的真正源泉}$$
软件篋borrowing_exerci(一)
借用和所有权机制的基本思想很简单。每个值都由一个对象拥有,在一个时刻只能由一个对象拥有,并且当对象超出范围时,该值将从内存中删除。只能借用该对象,而不能拥有该对象的所有权,但是可以创建引用对象拥有该对象值的指针,引用对象拥有该对象值的所有权,但是该对象可以随时收回其所有权。
软件篋borrowing_exerci
Rust语言的借用机制是其最主要的特点。通过实现借用方法,使得代码更加安全和可靠。
Rust语言存在两大数据类型:固定大小类型和可变大小类型。本章重点说明固定大小类型的借用机制。
固定大小类型
固定大小类型也称之为静态类型、常规固定大小类型或者栈分配值的类型。在编译时知道这种类型的大小,在编译以后,它们大小不可增长或缩小。比如,固定数组是元素的固定大小的列表。
可变大小类型
可变大小类型也称之为动态类型、可变大小的容器或者堆分配值的类型。在编译时不知道其类型的大小,但是它们可以随时增长或缩小其容量大小。比如,向量是可调整大小的数组,一种连续的可增长数组类型。
参考资料
- split-a-module-across-several-files
- ch07-05-separating-modules-into-different-files
- splitting-up-modules-in-rust
- references-and-borrowing
- rust-by-example/scope/borrow
- rust-borrowing-and-ownership
- understanding-rust-ownership-borrowing-lifetimes
- you-can-t-turn-off-the-borrow-checker-in-rust
- cant-derive-copy-because-of-string
- whats-the-difference-between-trait-copy-and-clone
- what-is-the-difference-between-copy-and-clone
- rust-move-copy
- rust-crash-course-02-basics-of-ownership
- move-clone-copy
- the-basics-of-rust-structs
- a-single-command-to-compile-and-run-rust-programs
- how-to-execute-rust-code-directly-on-unix-systems-using-the-shebang
- rust-ownership
关于软件篋borrowing_exerci
安装运行Rust语言脚本工具cargo-script
cargo install cargo-script
安装本软件箧borrowing_exerci
cargo install borrowing_exerci
使用方法
帮助命令
bw -h
运行错误借用代码实例命令
比如运行Rust程序kw_fn.rs
命令如下:
bw -f kw_fn -m err | bat -l rs
# tip: `f`, Forward one window
# tip: `b`, Backward one window
# tip: `q`, Exit.
运行借用代码实例命令
比如运行Rust程序kw_fn.rs
命令如下:
bw -f kw_fn -m ok | bat -l rs
参考资料
克隆(Clone)和复制(Copy)
学习内容
- 了解和学习Rust语言引用
Reference
、类型与原始指针Pointer
关系
篇目
- 复制特质
Copy
解释 - 克隆特质
Clone
解释 - 解释语句
let y = x;
- 复制特质
Copy
和克隆特质Clone
区别 - 表达方式不同
- 内部实现不同
- 类型区别不同
- 复制特质
Copy
和克隆特质Clone
的相互关系 - 复制特质
Copy
元素的数组实例 - 两种不同特质实现
- 题外话
- 问题:什么时候我的类型应该使用复制特质
Copy
? - 问题:为什么存在克隆?
- 参考资料
复制特质Copy
解释
官方说明:
Types whose values can be duplicated simply by copying bits.
直接翻译:只需拷贝二进制数字位即可复制其值的类型。
解读:
- 通过简单的内存拷贝,实现该类型的复制。
- 含简单值的类型(POD,Pain Old Data),如数字类型、bool类型和引用类型,都是具有复制特质
Copy
的类型。 - 只有当结构性类型内部的每个项都是Copy类型时,允许实现此类型复制特质
Copy
。如数组类型、tuple类型、struct类型和enum类型,它们内部的每个项都是Copy类型。 - 当结构性类型内部的项存在不是Copy类型时,它们就不能实现该类型的复制特质。如Box类型、字符串String和向量Vec,它们内部的项不都是Copy类型。
解读延伸:
- 复制特质
Copy
没有任何可实现的方法或函数。 - 复制特质
Copy
的行为是不可重载的。
克隆特质Clone
解释
官方说明:
A common trait for the ability to explicitly duplicate an object.
直接翻译:能够显式地复制一个对象的通用特质。
解读:
- 克隆特质
Clone
是通用的,除了不确定大小或者动态大小类型之外,适合所有类型,可实现所有类型的克隆特质Clone
。 - 只有手动调用克隆特质
Clone
方法,才能发挥其作用。作者称之为显式行为(an explicit action)。 - 克隆特质
Clone
的实现与具体类型密切相关。 - 克隆特质
Clone
有可以实现的方法。
解读延伸:
- 对于实现了复制特质
Copy
的类型,其克隆特质Clone
与其复制特质Copy
语义是一样的,等同于按位拷贝。 - 一般情况下,Rust语言使用克隆特质
Clone
方法来执行对象的复制。
解释语句let y = x;
// {T} is a data type value;
let x = {T};
let y = x;
上面两行代码仅仅是为了说明问题,不是Rust语言可运行代码。
在上面代码里,当尚未确定{T}
的具体类型对象值时,第二行代码的作用可能出现两种可能性。
在Rust语里,一种称之为复制(copy)的可能性:对象x
和y
是两个可同时独立存在的对象;另一种称之为转移或者移动(move)的可能性:对象x
和y
是两个不能同时独立存在的对象,一旦执行了第二行代码以后,对象x
不再存在了。
下面给出具体实例说明这两种情况。第一个是所谓的复制(copy)实例,这是可运行程序。而第二个是所谓的转移(move)实例,这是不可运行程序。
# #![allow(unused_variables)] #fn main() { let x = 33u8; let y = x; println!("x = {:p}", &x); println!("y = {:p}", &y); #}
# #![allow(unused_variables)] #fn main() { let x = String::from("Hello"); let y = x; println!("x = {:p}", &x); println!("y = {:p}", &y); #}
复制特质Copy
和克隆特质Clone
区别
表达方式不同
复制特质Copy
是以隐含方式表达,Rust语言也称之为为拷贝(copy),而克隆是一个显式行为方式表达,Rust语言也称之为克隆(clone)。
注意:使用第一个字母大写Copy
是指复制特质,而使用第一个字母小写(copy)是指复制特质Copy
的行为,中文这里称之为“拷贝”或者“复制“。同理,使用第一个字母大写Clone
是指克隆特质,而使用第一个字母小写(clone)是指克隆特质Clone
的行为,中文这里称之为“克隆”或者”复制“。
下面两个程序分别说明两种行为:第一个是拷贝(copy)行为,第二个是克隆(clone)行为。
# #![allow(unused_variables)] #fn main() { // Copy:copy behavior // The variables x and y are different instances, and both can be used let x = 33u8; let y = x; #}
# #![allow(unused_variables)] #fn main() { // Clone: clone behavior // The variables x and y are different instances, and both can be used let x = String::from("Hello"); let y = x.clone(); #}
对于一些类型,克隆和复制行为是等价的,如静态类型u8
。而对于另外一些类型,只有克隆没有复制,如类型String
。当然也有类型,克隆和复制行为都存在,但是它们并是不等价的。
对于一些类型,克隆和复制行为都实现了,如静态类型u8
。而对于另外一些类型,仅克隆行为实现了,如类型String
。当然自定义类型,克隆和复制行为都没有实现,需要完全自己实现或者借助于Rust语言派生属性实现。这里仅仅提供一些概念,没有完全展开说明。
内部实现不同
复制特质Copy
可以安全地复制类型对象的值。对于具有复制特质Copy
的类型,编译器负责管理复制特质Copy
类型的对象。
克隆特质Clone
专为程序开发任意类型复制而设计。任何类型克隆特质Clone
的实现可以执行创建类型所需的任意复杂方法。像正常特质一样使用及其方法调用。
类型区别不同
复制特质Copy
适合于含值类型,如正整数u8类型。在类型u8的情况下,通过克隆无法来提高效率,这时候就不用再考虑克隆。
克隆特质Clone
适合于重量级的类型,对于一种类型,两种特质都可以实现时,要是克隆(clone)比拷贝(copy)更有效,就尽可能使用克隆(clone)。
复制特质Copy
和克隆特质Clone
的相互关系
克隆特质Clone
是复制特质Copy
的父特质,因此特质Copy
的所有内容也必须实现特质Clone
。如果类型为特质Copy
,则其特质Clone
实现仅需要返回*self
。每个特质Copy
的类型也必须具有特质Clone
的类型。
复制特质Copy
元素的数组实例
从下面的实例可以看到,具有复制特质Copy
元素的数组也是可以简单进行移动和拷贝。具有复制特质Copy
元素的数组本质上就是复制自身,因此克隆特质Clone
的实现可以不需要再引用直接返回自身的传递值。
# #![allow(unused_variables)] #fn main() { // File: lib-hello/src/other/clone.rs // Function use_clone_array() let a: [u8; 0] = []; dbg!(a); let b = a.clone(); let c = a; dbg!(a, b, c); let a: [u8; 0] = []; dbg!(a); let b = a; let c = a.clone(); dbg!(a, b, c); #}
两种不同特质实现
复制特质Copy
是特定的编译器特征,它指示编译器,开发人员希望为该类型激活隐式复制。仅当浅表拷贝与深度拷贝等效时,此特质才可用,这样可确保不会发生内存分配作为这些隐式拷贝的一部分。复制特质Copy
是一种复制的方法,仅可拷贝若干个内存地址字节的类型。
克隆特质Clone
指示编译器,开发人员创建新的对象,且必须显式调用才对象。大多数类型(但不是全部)都可以使用它进行拷贝。克隆特质Clone
是也一种复制方法,可以拷贝运行任意代码的类型。
题外话
问题:什么时候我的类型应该使用复制特质Copy
?
一般来说,如果一种类型可以实现复制特质Copy
,则应该去实现该类型的复制特质Copy
。但是如果该类型将来可能变为非复制特质Copy
类型,则最好从一开始就不要实现复制特质,以避免以后发生重大更改。
如果在编译代码时,警告显示,请添加复制特质Copy
,除非有充分的理由不这样做。
问题:为什么存在克隆?
从内部分析,克隆特质Clone
的克隆(clone),与复制特质Copy
的拷贝(copy)是一样的,也有拷贝(copy),只是这种拷贝(copy)资源不能让开发人员访问。为了区分两种情况,需要克隆特质Clone
。
我们知道,复制特质Copy
的拷贝(copy)要求可以使用堆栈中字节的简单内存来复制该值。但是克隆特质Clone
的克隆(clone),不仅需要拷贝(copy)在栈(Stack)上的值(即容量、长度和指向内容的指针内存地址等),而且还需要创建(create)一个新的内存储存位置来复制其具体的内容。所以,克隆(clone)不仅仅是拷贝(copy)还有创建(create)行为,它也称之为深度拷贝(deep copy),而仅仅只有拷贝(copy)行为称之为浅表拷贝(shallow copy)。
需要指出的是,Rust语言的克隆特质Clone
的实现既可以是深度拷贝,也可以是浅表拷贝。
比如,字符串String
类型对象是可复制的,即克隆(clone)行为,请使用克隆方法clone()
;字符串String
类型对象不可完成隐式复制的目的,即拷贝(copy)行为,因为这将导致发生非显而易见的内存分配。
参考资料
- std/marker/trait.Copy
- whats-the-difference-between-copy-and-clone
- what-is-the-difference-between-copy-and-clone
- whats-the-difference-between-trait-copy-and-clone
- when_should_my_type_be_copy
- cant-derive-copy-because-of-string
- how-do-i-implement-copy-and-clone-for-a-type-that-contains-a-string
- how-to-define-a-copyable-struct-containing-a-string
- Clone VS Copy
- move-clone-copy
可变类型派生分析
首先,通过自定义结构类型Struct(u8)
,展示派生属性Clone
和Copy
的代码,且说明两种派生属性区别。其次,对于派生属性Clone
和Copy
,应用于结构类型Struct(u8)
与Struct(String)
的区别。最后说明自定义类型Struct<'cn>(&'cn &str)
的派生属性。
学习内容
- 了解和学习Rust语言可变类型的派生
篇目
- 可变类型的派生属性
Clone
和Copy
- 可变类型的派生属性
Clone
- 可变类型的派生属性
Copy
- 分析不同类型的派生属性
- 自定义类型Struct<'cn>(&'cn str)的派生属性
- 题外话
- 参考资料
可变类型的派生属性Clone
和Copy
在介绍可变类型的派生属性Clone
和Copy
之前,先考察下面不可变类型的实例。
# #![allow(unused_variables)] #fn main() { // File: ./examples/expand/use_u8.rs // clear && cargo expand --example expand -- use_u8 // clear && cargo run --example expand -- use_u8 #![allow(unused_variables)] pub fn adjoin() { let instance = 42u8; let clone_instance = instance.clone(); let copy_instance = instance; let use_instance = instance; } #}
上面程序是可运行的。使用派生工具cargo-expand
,展开其代码的结果如下:
mod use_u8 {
#![allow(unused_variables)]
pub fn adjoin() {
let instance = 42u8;
let clone_instance = instance.clone();
let copy_instance = instance;
let use_instance = instance;
}
}
对于自定义的可变类型,要能够保证其对象克隆和复制,必须实现Clone
和Copy
两个特质,该类型才能使用克隆和复制其对象。途径有两条:要么自己实现该类型的这两个特质;要么使用语言提供的默认实现。这里使用后面方法。
下面程序是包含了自定义的可变类型。之所以该程序能够正常运行,是因为,下面与上面程序比较可以知道,除了类型不同之外,还多增加了包括两个派生属性Clone
和Copy
的一行代码,其作用是使得该类型可以克隆和复制其对象。对于不可变类型,Rust语言已经实现了克隆和复制功能,在这种情况下,克隆和复制功能是等价的。
# #![allow(unused_variables)] #fn main() { // File: ./examples/expand/struct_u8.rs // #[cfg(feature = "ok")] #[derive(Clone, Copy)] struct Struct(u8); let instance: Struct = Struct(42u8); let clone_instance = instance.clone(); let copy_instance = instance; // can copy and clone, because derive *Clone* // can move, because derive *Copy*, // and instance and copy_instance live let use_instance = instance; #}
下面看看上面程序其两个派生属性Clone
和Copy
代码是怎么样实现的。使用派生工具运行结果如下。可以看到对于自定义类型Struct(u8)
,存在两个特质Clone
和Copy
实现:
mod struct_u8 {
#![allow(unused_variables)]
#[cfg(feature = "ok")]
pub fn adjoin() {
struct Struct(u8);
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::clone::Clone for Struct {
#[inline]
fn clone(&self) -> Struct {
{
let _: ::core::clone::AssertParamIsClone<u8>;
*self
}
}
}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::marker::Copy for Struct {}
let instance: Struct = Struct(42u8);
let clone_instance = instance.clone();
let copy_instance = instance;
let use_instance = instance;
}
}
对于上面这种可变类型,要是其每一元素都是不可变类型,Rust语言可以实现了克隆和复制功能,同样,在这种情况下,克隆和复制功能也是等价的。
在上面程序里,之所以该类型对象instance
能够被克隆和复制,是因为该类型实现了克隆特质Clone
;之所以该类型对象instance
能够被转移(move),是因为该类型实现了复制特质Copy
。下面看看这是为什么。
可变类型的派生属性Clone
注意比较上下两个程序的代码,下面代码缺少了复制特质Copy
,且最后一行代码不是使用对象instance
,而是使用copy_instance
作为变量的绑定值。
# #![allow(unused_variables)] #fn main() { // File: ./examples/expand/struct_u8.rs // #[cfg(feature = "cp")] #[derive(Clone)] struct Struct(u8); let instance = Struct(42u8); let clone_instance = instance.clone(); let copy_instance = instance; // can copy and clone, because derive *Clone* // but can NOT move, because without derive *Copy*, // and instance live not, but copy_instance live let use_copy_instance = copy_instance; #}
还是先让我们使用派生工具,展开其代码,其运行结果:
mod struct_u8 {
#![allow(unused_variables)]
#[cfg(feature = "cp")]
pub fn adjoin() {
struct Struct(u8);
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::clone::Clone for Struct {
#[inline]
fn clone(&self) -> Struct {
match *self {
Struct(ref __self_0_0) => Struct(::core::clone::Clone::clone(&(*__self_0_0))),
}
}
}
let instance = Struct(42u8);
let clone_instance = instance.clone();
let copy_instance = instance;
let use_copy_instance = copy_instance;
}
}
从上面展开的代码来看,代码里不仅没有了复制特质Copy
的实现,而且复制特质Copy
存在与否直接影响到克隆特质Clone
代码的实现。
在上面程序里,之所以该类型对象instance
能够被克隆和复制,是因为该类型实现了克隆特质Clone
;之所以该类型对象instance
不能被转移(move),是因为该类型没有实现复制特质Copy
。下面看看这是为什么。
上下程序唯一不同是最后一行代码,但是其结果完全不同了。
# #![allow(unused_variables)] #fn main() { // File: ./examples/expand/struct_u8.rs // ANCHOR = "struct_u8_error_01" // error[E0382]: use of moved value: `instance` #[derive(Clone)] struct Struct(u8); let instance = Struct(42u8); let clone_instance = instance.clone(); let copy_instance = instance; // can copy and clone, because derive *Clone* // but can NOT move, because without derive *Copy*, // and instance live not let use_instance = instance; #}
上面程序以错误运行结果结束:
error[E0382]: use of moved value: `instance`
--> bin-hello/examples/expand/struct_u8.rs:76:24
|
69 | let instance = Struct(42u8);
| -------- move occurs because `instance` has type `struct_u8::adjoin::Struct`, which does not implement the `Copy` trait
70 | let clone_instance = instance.clone();
71 | let copy_instance = instance;
| -------- value moved here
...
76 | let use_instance = instance;
| ^^^^^^^^ value used here after move
可变类型的派生属性Copy
对于自定义类型,要是只有复制特质Copy
,程序编译会是什么结果?
# #![allow(unused_variables)] #fn main() { // File: ./examples/expand/struct_u8.rs // ANCHOR = "struct_u8_error_03" // error[E0277]: the trait bound `struct_u8::adjoin::Struct: std::clone::Clone` is not satisfied #[derive(Copy)] struct Struct(u8); let instance = Struct(42u8); let clone_instance = instance.clone(); let copy_instance = instance; // derive *Copy* error, because without derive *Clone* let use_instance = instance; #}
该程序运行结果是以错误结束。而这种错误说明需要先实现克隆特质Clone
。
error[E0277]: the trait bound `main::Struct: std::clone::Clone` is not satisfied
--> bin-hello/examples/struct_u8.rs:56:14
|
56 | #[derive(Copy)]
| ^^^^ the trait `std::clone::Clone` is not implemented for `main::Struct`
尽管该程序不能编译,但是还是可以运行派生工具,其结果如下:
#![feature(prelude_import)]
#![allow(unused_variables)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std;
#[cfg(feature = "err_02")]
fn main() {
struct Struct(u8);
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::marker::Copy for Struct {}
let instance = Struct(42u8);
let clone_instance = instance.clone();
let copy_instance = instance;
let get_instance = instance;
}
分析不同类型的派生属性
将前面包含派生属性Clone
和Copy
程序进行这样的调整:把结构类型的元素类型u8
修改为String
,得到如下程序:
# #![allow(unused_variables)] #fn main() { // File: ./examples/expand/struct_string.rs // ANCHOR = "struct_string-error_01" // error[E0204]: the trait `Copy` may not be implemented for this type #[derive(Clone, Copy)] struct Struct(String); let instance = Struct(String::from("Hello")); let clone_instance = instance.clone(); let copy_instance = instance; let use_instance = instance; #}
上面程序运行结果如下。我们知道前面类似程序运行是正常的,为什么到这里就不能运行了呢?这是因为类型String
是可变类型,对于所有这种形式的类型,Rust语言都没有实现其复制特质Copy
.
error[E0204]: the trait `Copy` may not be implemented for this type
--> bin-hello/examples/expand/struct_string.rs:38:21
|
38 | #[derive(Clone, Copy)]
| ^^^^
39 | struct Struct(String);
| ------ this field does not implement `Copy`
那么什么是这种类型一般性使用方法呢?对于这种类型,仅使用Rust语言提供的克隆特质Clone
,其代码如下:
# #![allow(unused_variables)] #fn main() { // File: ./examples/expand/struct_string.rs // #[cfg(feature = "ok")] #[derive(Clone)] struct Struct(String); let instance = Struct(String::from("Hello")); let clone_instance = instance.clone(); let copy_instance = instance; // can copy and clone, because derive *Clone* // can not move, because without derive *Copy*, // and instance live not, copy_instance live let use_copy_instance = copy_instance; #}
通过派生工具命令,展开该程序代码。注意这里克隆特质Clone
实现,比较前面类型Struct(u8)
程序实现,可以了解到两者实现是完全一样的。对于仅仅使用克隆特质Clone
,两种类型Struct(u8)
和Struct(String)
是完全相似的。
# #![allow(unused_variables)] #fn main() { mod struct_string { #![allow(unused_variables)] #[cfg(feature = "ok")] pub fn adjoin() { struct Struct(String); #[automatically_derived] #[allow(unused_qualifications)] impl ::core::clone::Clone for Struct { #[inline] fn clone(&self) -> Struct { match *self { Struct(ref __self_0_0) => Struct(::core::clone::Clone::clone(&(*__self_0_0))), } } } let instance = Struct(String::from("Hello")); let clone_instance = instance.clone(); let copy_instance = instance; let use_copy_instance = copy_instance; } } #}
下面程序说明了,对于这种类型Struct(String)
也是不能转移(move)的。
# #![allow(unused_variables)] #fn main() { // File: ./examples/expand/struct_string.rs // ANCHOR = "struct_string-error_02" // error[E0382]: use of moved value: `instance` #[derive(Clone)] struct Struct(String); let instance = Struct(String::from("Hello")); let clone_instance = instance.clone(); let copy_instance = instance; // can copy and clone, because derive *Clone* // but can NOT move, because without derive *Copy*, // and instance live not let use_instance = instance; #}
自定义类型Struct<'cn>(&'cn str)
的派生属性
对于结构类型Struct<'cn>(&'cn str)
的元素是字符串文字类型,只是其类型定义的表达方式上差距比较大,下面给出实现代码。在实际中经常会使用这种类型。
# #![allow(unused_variables)] #fn main() { // File: ./examples/expand/struct_str.rs // #[cfg(feature = "ok")] #[derive(Clone, Copy)] struct Struct<'cn>(&'cn str); let instance: Struct = Struct("Hello"); let clone_instance = instance.clone(); let copy_instance = instance; // can copy and clone, because derive *Clone* // can move, because derive *Copy*, // and instance and copy_instance live let use_instance = instance; #}
但是,其处理方法上与类型Struct(u8)
是完全相同的,通过派生工具展开其代码,可以解释这个问题,把前面展开代码与下面展开代码相比较。
该程序派生工具运行结果如下:
mod struct_str {
#![allow(unused_variables)]
#[cfg(feature = "ok")]
pub fn adjoin() {
struct Struct<'cn>(&'cn str);
#[automatically_derived]
#[allow(unused_qualifications)]
impl<'cn> ::core::clone::Clone for Struct<'cn> {
#[inline]
fn clone(&self) -> Struct<'cn> {
{
let _: ::core::clone::AssertParamIsClone<&'cn str>;
*self
}
}
}
#[automatically_derived]
#[allow(unused_qualifications)]
impl<'cn> ::core::marker::Copy for Struct<'cn> {}
let instance: Struct = Struct("Hello");
let clone_instance = instance.clone();
let use_copy_instance = instance;
let use_instance = instance;
}
}
题外话
参考资料
- cargo-expand
- borrowed-pointer-tutorial
- book rust-for-c
- rust-borrowed-pointers-syntax
- fighting-the-borrow-checker-in-a-loop
引用Reference
与指针Pointer
基本概念
学习内容
- 了解和学习引用
Reference
与指针Pointer
概念
篇目
- 什么是指针
Pointer
- 什么是引用
Reference
- Rust语言的引用
Reference
与指针Pointer
- 引用实例解释
- 题外话
- 介绍Rust的指针类型
- 开发工具:软件篋prettytable-rs和ripgrep
- 参考资料
什么是指针Pointer
Rust语言技术术语,既有其本身专门的技术术语,如软件篋crate
、借用Borrowing
和生命周期Lifttime
等,也有一般计算机科学技术术语,如变量、类型和指针Pointer
等。但是,对于一般技术术语,在Rust语言里也是有不同的内涵意义和实现形式。
In computer science, a pointer is a programming language object that stores the memory address of another value located in computer memory.
直接翻译:在计算机科学中,指针是编程语言的对象,用于存储位于计算机内存中的另一个值的内存地址。
解读:
- 指针是一个对象
object
或者说实例instance
,它是指针类型的对象; - 指针也是变量,与其它变量完全一样;
- 指针变量也绑定一个值,与其它变量绑定不一样的值,它绑定一种特殊值,就是内存地址;
- 在绑定的内存地址值下,也是一个变量;
什么是引用Reference
Eine Referenz ist ein Verweis auf ein Objekt. Eine Referenz ist damit ein Aliasname für ein bereits bestehendes Objekt.
直接翻译:引用是关于对象的参照物。因此引用是现有对象的别名。
解读:
- 引用是总是与一个对象关联起来的;
- 从本质上,引用就是相关联的对象,只是表现形式不同而已;
Rust语言的引用Reference
与指针Pointer
在Rust语言里,所有指针都是其自己的类型。且存在很多的指针pointer
类型。比如,从Rust语言结构上分析,引用reference
类型是一种指针类型,且最简单的指针类型,从内容上分析,每一个指针类型都包含引用的内存地址。引用reference
类型的对象是最常见的。下面通过最简单的示意图了解什么是指针概念:
从说明示意图结构上看到,引用reference
类型的对象ref_u8
与整数u8类型的对象instance
是完全一样的,只是其值不同而已。
但是Rust语言在处理其值的方法有所不同。这是因为,Rust通常专注于非指针对象的值或者指针对象的引用对象值(即内容中有趣的部分),而不是指针对象的标识(内存地址)。比如下面代码,使用宏println!简单格式打印它们的值都是一样的,只有使用特殊格式,才能打印出引用对象的值,即内存地址。
# #![allow(unused_variables)] #fn main() { let instance = 42_u8; let ref_u8 = &instance; println!("{}", instance); println!("{}", ref_u8); println!("{:p}", ref_u8); #}
Rust语言指针类型可以分为三大类:引用(包括共享引用和可变引用)、原始引用和智能引用smart pointer
。其中前面两类类是Rust语言本身的,而第三类是属于标准库的。这里重点说明共享引用和原始引用。
Ⓘ 共享引用指向其他值所拥有的内存。当创建共享引用值时,引用将防止该值的直接改变。除了下面说明的原始指针之外,Rust语言其他指针都是安全的,并且都具有其生命周期。
Ⓘ 原始指针是没有安全性保证的指针。
在Rust代码中通常不提倡和不鼓励使用原始指针。Rust语言的原始指针与C语言的指针是等效的。原始指针可以为null,也可以指向垃圾,它们也没有生命周期。
为了以后说明问题简单化,我们把上面示意图统一成如下形式:
引用实例解释
借助于上面两个引用和一个原始引用的示意图,理解下面相关代码的意义。
在函数main()
里,一共有四段代码,前面两段代码形成了上面示意图内容,后面两段代码是获取它们的地址,以便说明问题。
# #![allow(unused_variables)] #fn main() { // File: lib-hello/src/immut/type_ref/mod.rs // Function use_references_simple() let instance: &str = "Hello"; let instance = "Hello"; let copy_instance: &str = instance; let copy_instance = instance; println!("instance reference address = {:p}", instance); println!("copy_instance reference address = {:p}", copy_instance); println!("instance address = {:p}", &instance); println!("copy_instance address = {:p}", ©_instance); #}
第一段的两行代码是等效的,实际只需要一行代码就可以了。这代码把字符串文字绑定了变量instance
同时,也形成了原始指针,变量instance
的地址值指向了原始指针的地址。
第二段的两行代码也是等效的,实际只需要一行代码就可以了。这代码把变量instance
绑定到新变量copy_instance
,这种绑定方式Rust语言称之为复制(Copy),这里它不会产生新原始指针,而是指向与变量instance
相同的原始指针地址。
从下面程序输出结果,也可以得到验证上面的阐述。第一行和第二行的地址就是原始指针的内存地址,而第三行和第四行的地址是两个引用变量自身的内存地址,注意,它们不是变量的内容值(内存地址)。
───────┬──────────────────────────────────────────────────────────────────────────
│ STDIN
───────┼──────────────────────────────────────────────────────────────────────────
1 │ instance reference address = 0x101c39b20
2 │ copy_instance reference address = 0x101c39b20
3 │ instance address = 0x7fff5dfe81a0
4 │ copy_instance address = 0x7fff5dfe81c0
───────┴──────────────────────────────────────────────────────────────────────────
题外话
介绍Rust的指针类型列表
种类 | 类型 | 名称 | 说明 |
---|---|---|---|
共享引用 | &T | 引用Reference | 允许一个或者多个引用来类型T |
可变引用 | &mut T | 可变引用Mutable Reference | 仅允许单个引用来读和写类型T |
智能引用 | Box | Box指针 | 处于堆上类型T的指针类型,该类型只能有单个所有者,它可以读取和写入类型T。 |
智能引用 | Rc | 参考计数指针 | 处于堆上类型T的指针类型,该类型可以有多个所有者,它们可以读取类型T。 |
智能引用 | Arc | 核参考计数指针 | 与类型Rc |
原始引用 | *const T | 原始指针Raw pointer | 不安全地读取访问类型T |
原始引用 | *mut T | 可变原始指针Mutable raw pointer | 不安全地读取和写入访问类型T |
开发工具:软件篋prettytable-rs
使用工具软件篋prettytable-rs可以使得程序输出更加美观。
为了使用该工具,需要将下面代码放入文件Cargo.toml的[dependencies]
段里:
prettytable-rs = "0.8.0"
具体使用实例代码如下:
// File: lib-hello/src/other/crate_tools/mod.rs
// Function use_prettytable()
let instance = "Hello";
let copy_instance = instance;
let table = table!(
["Name", "Value", "Remark"],
[
"instance reference address",
format!("{:p}", instance),
"is equal to the following line"
],
[
"copy_instance reference address",
format!("{:p}", copy_instance),
""
],
[
"instance address",
format!("{:p}", &instance),
"is not equal to the following line"
],
["copy_instance address", format!("{:p}", ©_instance), ""]
);
table.printstd();
程序输出结果:
───────┬─────────────────────────────────────────────────────────────────────────────────────────────────────
│ STDIN
───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ +---------------------------------+----------------+------------------------------------+
2 │ | Name | Value | Remark |
3 │ +---------------------------------+----------------+------------------------------------+
4 │ | instance reference address | 0x104f58ba0 | is equal to the following line |
5 │ +---------------------------------+----------------+------------------------------------+
6 │ | copy_instance reference address | 0x104f58ba0 | |
7 │ +---------------------------------+----------------+------------------------------------+
8 │ | instance address | 0x7fff5ad3fa50 | is not equal to the following line |
9 │ +---------------------------------+----------------+------------------------------------+
10 │ | copy_instance address | 0x7fff5ad3fa60 | |
11 │ +---------------------------------+----------------+------------------------------------+
───────┴─────────────────────────────────────────────────────────────────────────────────────────────────────
参考资料
- crate chars
- how-can-i-get-an-array-or-a-slice-from-a-raw-pointer
- mutation-slice-from-raw-pointer
- std slice fn.from_raw_parts
- why-does-printing-a-pointer-print-the-same-thing-as-printing-the-dereferenced-po
- Rust的指针类型
- pointers cheat-sheet
- Rust learning notes
- why-the-machine
引用、类型与原始指针解释
学习内容
- 了解和学习Rust语言引用
Reference
、类型与原始指针Pointer
关系
篇目
引用类型&str
与原始指针关系图
# #![allow(unused_variables)] #fn main() { // File: lib-hello/src/immut/raw_pointer/mod.rs // Function: use_raw_pointer_str() use std::slice; println!(); let instance: String = String::from("Hello"); let ref_raw: *const u8 = instance.as_ptr(); println!("instance value = {}", instance); println!("instance reference raw address = {:?}", ref_raw); println!(); let ref_str: &str = &instance; let ref_str = instance.as_str(); let ref_str: &str = instance.as_str(); let ref_str: &str = &instance[0..=4]; println!("ref_str value = {}", ref_str); println!("ref_str owned address = {:p}", ref_str); let ref_raw_str: *const u8 = ref_str.as_ptr(); assert_eq!(ref_raw, ref_raw_str); println!(); let ref_slice: &[*const u8] = unsafe { slice::from_raw_parts(&ref_raw, 5) }; dbg!(ref_slice); assert_eq!(&ref_raw, &ref_slice[0]); println!(); let u8_slice: &[u8] = unsafe { slice::from_raw_parts(ref_raw, 5) }; dbg!(u8_slice); #}
第一段代码:绑定字符串String
类型的变量instance
println!();
let instance: String = String::from("Hello");
let ref_raw: *const u8 = instance.as_ptr();
println!("instance value = {}", instance);
println!("instance reference raw address = {:?}", ref_raw);
在这一段代码里,第一个let
绑定了字符串String
类型变量instance
。这样就产生了上面图的变量instance
及其原始指针。
字符串String
类型由三个部分组成:指向原始指针的指针地址、其长度和容量。该指针地址指向内部缓冲字符串(buffer string),用于存储其数据。
在这一段代码里,第二个let
绑定了*const u8
类型变量ref_raw
。其中方法as_ptr()
功能是将字符串切片转换为原始指针。
第二段代码:绑定字符串文字&str
类型的变量instance
println!();
let ref_str: &str = &instance;
let ref_str = instance.as_str();
let ref_str: &str = instance.as_str();
let ref_str: &str = &instance[0..=4];
println!("ref_str value = {}", ref_str);
println!("ref_str owned address = {:p}", ref_str);
let ref_raw_str: *const u8 = ref_str.as_ptr();
在这一段代码里,前面四个let
绑定了字符串文字&str
类型变量ref_str
,它们是完全等效的。其中方法as_str()
功能是提取包含整个String
的字符串切片。
在这一段代码里,最后的let
绑定了*const u8
类型变量ref_raw_str
。特别需要注意的是,变量ref_raw_str
也是原始指针的地址。
第三段代码:第一次验证原始指针地址
这里将验证,第一段代码和第二段代码的原始指针变量ref_raw
和ref_raw_str
的地址是相等的。
assert_eq!(ref_raw, ref_raw_str);
第四段代码:原始指针内存地址的数组切片
在这一段代码里,使用了关键词unsafe
,然后启动一个包含不安全代码的新代码块。该代码块存在方法from_raw_parts(),它是根据原始指针的引用及其长度返回一个内存地址数组切片。这数组切片的每一项是字符串其中一个字符的内存地址。
println!();
let ref_slice: &[*const u8] = unsafe { slice::from_raw_parts(&ref_raw, 5) };
dbg!(ref_slice);
第五段代码::第二次验证原始指针地址
这里将验证,两个原始指针,第一段代码的变量ref_raw
和第四段代码数组切片ref_slice
的第一项的地址是相等的。
assert_eq!(&ref_raw, &ref_slice[0]);
第六段代码:原始指针字符值的数组切片
在这一段代码里,使用了关键词unsafe
,然后启动一个包含不安全代码的新代码块。该代码块存在方法from_raw_parts(),它是根据原始指针及其长度返回一个类型u8数组切片。这类型u8数组切片的每一项是字符串其中的一个字符值。
println!();
let u8_slice: &[u8] = unsafe { slice::from_raw_parts(ref_raw, 5) };
dbg!(u8_slice);
程序输出结果
该程序输出结果如下,可以比较上面的阐述:
[bin-hello/examples/use_raw_pointer_str.rs:35] ref_slice = [
0x00007fa772403730,
0x00000001034db528,
0x0000000000000002,
0x0000000000000000,
0x00007fff5c750d80,
]
[bin-hello/examples/use_raw_pointer_str.rs:45] u8_slice = [
72,
101,
108,
108,
111,
]
───────┬─────────────────────────────────────────────────────────────────────────
│ STDIN
───────┼─────────────────────────────────────────────────────────────────────────
1 │
2 │ instance value = Hello
3 │ instance reference raw address = 0x7fa772403730
4 │
5 │ ref_str value = Hello
6 │ ref_str owned address = 0x7fa772403730
7 │
8 │
───────┴─────────────────────────────────────────────────────────────────────────
引用类型&String
与原始指针关系图
# #![allow(unused_variables)] #fn main() { // File: lib-hello/src/immut/raw_pointer/mod.rs // Function: use_raw_pointer_string() use std::slice; println!(); let instance: String = String::from("Hello"); let ref_raw = instance.as_ptr(); println!("instance value = {}", instance); println!("instance reference raw address = {:?}", ref_raw); println!(); let ref_string: &String = &instance; let ref_string = &instance; println!("ref_string value = {}", ref_string); println!("ref_string owned address = {:p}", ref_string); let ref_raw_string: *const u8 = ref_string.as_ptr(); assert_eq!(ref_raw, ref_raw_string); println!(); let ref_slice = unsafe { slice::from_raw_parts(&ref_raw, 5) }; dbg!(ref_slice); assert_eq!(&ref_raw, &ref_slice[0]); println!(); let u8_slice = unsafe { slice::from_raw_parts(ref_raw, 5) }; dbg!(u8_slice); #}
第二段代码:绑定字符串引用&String
类型的变量instance
println!();
let ref_string: &String = &instance;
let ref_string = &instance;
println!("ref_string value = {}", ref_string);
println!("ref_string owned address = {:p}", ref_string);
let ref_raw_string: *const u8 = ref_string.as_ptr();
这是唯一一段代码与前面实例代码不同的。在这一段代码里,前面两个let
绑定了字符串引用&String
类型的变量ref_string
,它们是完全等效的。特别需要指出的是,在关键词let
等式右边类型定义有时候是必要的,尽管这个实例可以省略,但是在上面实例里,就是必须的。
在这一段代码里,最后的let
绑定了*const u8
类型变量ref_raw_string
。
程序输出结果
该程序输出结果如下。比较上面实例结果,可以看到:在这个实例结果只有两个内存地址是一样的,而上面实例有三个内存地址是完全相同的。上面两个结构图就是示意这个结果。
[bin-hello/examples/use_raw_pointer_string.rs:32] slice = [
0x00007fac6ac03770,
0x0000000101bc4520,
0x0000000000000002,
0x0000000000000000,
0x00007fff5e066ca0,
]
[bin-hello/examples/use_raw_pointer_string.rs:41] slice = [
72,
101,
108,
108,
111,
]
───────┬─────────────────────────────────────────────────────────────────────────
│ STDIN
───────┼─────────────────────────────────────────────────────────────────────────
1 │
2 │ instance value = Hello
3 │ instance reference raw address = 0x7fac6ac03770
4 │
5 │ ref_string value = Hello
6 │ ref_string owned address = 0x7fff5e066ca0
7 │
8 │
───────┴─────────────────────────────────────────────────────────────────────────
引用、类型与原始指针解释
上面两个实例有什么不同?或者说,引用类型&String
与引用类型&str
的区别在哪里?
引用类型&String
是通过类型String
访问原始指针,而引用类型&str
是直接访问原始指针。引用类型&str
的方法是Rust语言借用系统的组成部分。这种方法更安全和更快速。我们把上面两个示意图合并到一起,如下所示:
从下面示意图可以看到,Rust语言的数据类型可以理解为是一种复杂的“引用”类型。引用类型主要是储存内存地址,而数据类型尽管也储存内存地址,但其重点是储存数据。
软件篋Cha(rs)
工具Cha(rs)
可以显示各种ASCII
和Unicode
字符/代码指针的名称和编码号。
# install
cargo install chars
# use
chars 'H'
软件篋ripgrep
软件篋ripgrep
是一款系统终端的搜索工具,类似于ack
和grep
。
# install
cargo install ripgrep
# use
ifconfig | rg netmask
参考资料
- pointers-in-rust-a-guide
- www.cs.brandeis.edu pointers
- std string struct.String
- Programming Rust (2016)
- rust learn
- which-problems-does-owning-ref-solve
- crates chars
- crates ripgrep
应用篋:字符串类型借用方法
学习内容
- 了解和学习Rust语言类型
String
借用实例
篇目
变量生命周期
在官方文档英文解释中,无论是复制特质Copy
,还是克隆特质Clone
,都使用了动词duplicate
,这明确说明了实际运作时,将再产生一份新对象,即:原对象和复制对象。要是没有达到这个目的,就不能称之为复制或者克隆。
# #![allow(unused_variables)] #fn main() { let instance = String::from("Hello"); let copy_instance = instance; #}
在上面的代码实例里,尽管变量instance
和copy_instance
是不同的对象,且变量copy_instance
是通过所谓”复制“方式产生的,但是它们不能同时使用。这种”复制“没有产生第二个对象,只是改变了对象名称及其内存地址不同而已,所以不是真正的复制功能。
下面示意图说明了,从第一行let
语句开始到第二行let
语句结束,内存数据储存的状态变化过程。
在第二个let
语句结束以后,变量instance
生命周期也就结束了,之后就不能再使用它了。这是因为类型String
没有复制特质Copy
的实现。要是一种类型实现了复制特质Copy
,那么其生命周期还是存在的。
例如,类型正整数u8实现了复制特质Copy
,使用这种隐式复制方法,该类型就生命周期还是存在的。哪些类型实现了复制特质Copy
,请参考Rust语言标准库文档。
类型正整数u8
或者逻辑类型bool
等都是实现了复制特质Copy
的典型实例,而字符串String
和向量Vec等都是没有实现复制特质
Copy`的典型实例。
# #![allow(unused_variables)] #fn main() { // File: lib-hello/src/immut/type_ref/mod.rs // Function use_u8_type() let instance = 42u8; println!("{}", instance); println!("{:p}", &instance); let copy_instance = instance; println!("{:p}", ©_instance); println!("{}", instance); #}
错误使用变量实例
下面程序在方法main()
有三段代码。第一段代码创建一个类型String
对象instance
;第二段代码是一种复制或者拷贝对象的行为,从上面变量生命周期可以知道,第三段代码的变量instance
已经成为没有定义的变量。所以,编译器指出其错误,其含意如上所述。Rust语言使用了“移动(move)”,说明变量生命周期的过程。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/string_type/string_str/mod.rs // #[cfg(feature = "err_01")] // move occurs because `instance` has type `std::string::String`, // which does not implement the `Copy` trait let instance = String::from("hello"); // The variable `instance` begin to move here let copy_instance = instance; // The variable `instance` moved here // ERROR: The variable `instance` borrowed here after move println!("{}", instance); println!("{}", copy_instance); #}
该程序输出结果如下:
# #![allow(unused_variables)] #fn main() { error[E0382]: borrow of moved value: `instance` --> bin-hello/examples/string_type_str.rs:27:20 | 19 | let instance = String::from("hello"); | -------- move occurs because `instance` has type `std::string::String`, which does not implement the `Copy` trait ... 23 | let copy_instance = instance; | -------- value moved here ... 27 | println!("{}", instance); | ^^^^^^^^ value borrowed here after move #}
借用机制代码实例
下面看看Rust如何实现借用方法。这是典型Rust语言的代码。下面程序在方法main()
有三段代码。在该方法里,类型String
对象instance
和borrow_instance
始终是有效的。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/string_type/string_str/mod.rs // #[cfg(feature = "ok")] let instance = String::from("Hello"); let raw_instance = instance.as_str(); println!("raw_instance = {:p}", raw_instance); let borrow_instance: &str = &instance; println!("borrow_instance = {:p}", borrow_instance); println!("{}", instance); println!("{}", borrow_instance); #}
与前面代码实例一样,第一段代码创建一个类型String
对象instance
;第二段代码也是一种复制或者拷贝对象的行为,但是该对象的类型是引用,这是Rust语言的一种借用机制,所谓”借用“,就是把对象instance
的值借过来使用。第三段代码的变量instance
和变量borrow_instance
还都是可以使用的。
下面示意图告诉我们,程序代码对象的内存储存结构。
该程序输出结果如下,从这个结果可以看到两个内存是完全一样的,这个内存地址就是原始指针的地址。
───────┬────────────────────────────────────────────────────────────────────
│ STDIN
───────┼────────────────────────────────────────────────────────────────────
1 │ raw_instance = 0x7fca1bc03680
2 │ borrow_instance = 0x7fca1bc03680
3 │ Hello
4 │ Hello
───────┴────────────────────────────────────────────────────────────────────
题外话
开发工具cargo-hack
开发工具cargo-hack
解决工具cargo
某些限制。工具cargo-hack
,目前仅在Linux和macOS上进行过测试。在其他平台上可能无法正常工作。
cargo install cargo-hack
对于程序有属性features
时,目前工具cargo
需要对每一个属性features
生成一个命令,而工具cargo-hack
只要一个命令就可以运行所有属性features
的程序。比如,上面程序examples/string_type_str.rs
有两个属性features
,这样就要两行不同的命令来执行程序:
clear && cargo run --example string_type_str --features ok | bat -l rs
clear && cargo run --example string_type_str --features err
而使用工具cargo-hack
,只需要一行命令:
cargo hack check --example string_type_str --each-feature --no-dev-deps
参考资料
应用篋:方法借用实例
学习内容
- 了解和学习Rust语言方法借用实例
篇目
实现复制特质Copy
类型的借用实例
在下面程序方法main()
的三段代码里,表面上并没看到复制变量num
,但是实际上存在变量num
的复制。一旦调用方法fn_borrow()
,方法内部就进行了复制特质Copy
的复制。这是怎么知道的呢?
# #![allow(unused_variables)] #fn main() { // File: lib-hello/src/immut/kw_fn/mod.rs // Function use_kw_fn_u8() fn fn_borrow(_: u8) {} let num = 42; fn_borrow(num); dbg!(num); #}
下面程序代码,说明了上面问题。该程序在上面程序基础上,把变量num
在调用方法之前之后以及在方法内的内存地址都打印出来。
# #![allow(unused_variables)] #fn main() { // File: lib-hello/src/immut/kw_fn/mod.rs // Function use_kw_fn_u8_ref() fn fn_borrow(num: u8) { println!("inside fn = {:p}", &num); } let num = 42; println!("Before fn = {:p}", &num); fn_borrow(num); println!("After fn = {:p}", &num); dbg!(num); #}
这是上面程序输出结果。从结果可以看到,在调用方法之前之后变量num
内存地址是完全一样的,只是在方法内变量num
内存地址是不一样的。这说明方法内部从一开始就复制了变量num
,这是因为类型u8复制特质`Copy,所以可以存在这样借用机制。
[bin-hello/examples/use_kw_fn_u8.rs:17] num = 42
───────┬─────────────────────────────────────────────────────────────────────────
│ STDIN
───────┼─────────────────────────────────────────────────────────────────────────
1 │ Before fn = 0x7fff531fd04f
2 │ inside fn = 0x7fff531fcf57
3 │ After fn = 0x7fff531fd04f
───────┴─────────────────────────────────────────────────────────────────────────
未实现复制特质Copy
类型的借用实例
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/kw_fn/vec_u8/mod.rs // #[cfg(feature = "err_01")] fn fn_borrow(vec_u8s: Vec<u8>) { println!("Inside fn = {:p}", &vec_u8s); } let vec_instance: Vec<_> = vec![33, 42]; println!("Before fn = {:p}", &vec_instance); fn_borrow(vec_instance); println!("After fn = {:p}", &vec_instance); dbg!(vec_instance); #}
在下面的程序里,在执行该程序以后,可以看到错误提示信息,这是因为类型Vec<u8>
没有实现复制特质Copy
,而方法fn_borrow
里面还是执行了变量的复制,相当于执行了下面一行代码:
let vec_u8s = vec_instance;
这是上面程序输出错误结果:
error[E0382]: borrow of moved value: `vec_instance`
--> bin-hello/examples/kw_fn_vec_u8.rs:32:22
|
29 | let vec_instance = Vec::new();
| ------------ move occurs because `vec_instance` has type `std::vec::Vec<u8>`, which does not implement the `Copy` trait
30 | println!("{:p}", &vec_instance);
31 | fn_borrow(vec_instance);
| ------------ value moved here
32 | println!("{:p}", &vec_instance);
| ^^^^^^^^^^^^^ value borrowed here after move
上面程序类型Vec<u8>
是可以实现复制特质Copy
,但是怎么样可以简单这种功能实现呢?
借用机制代码实例
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/kw_fn/vec_u8/mod.rs // #[cfg(feature = "ok")] fn fn_borrow(vec_u8s: &Vec<u8>) { println!("Inside fn = {:p}", &vec_u8s); } let vec_instance: Vec<_> = vec![33, 42]; println!("Before fn = {:p}", &vec_instance); fn_borrow(&vec_instance); println!("After fn = {:p}", &vec_instance); dbg!(vec_instance); #}
上面程序代码,使用了Rust语言的借用机制,实现了变量的借用,以达到传递变量值到函数或者方法。在Rust语言内部,存在一行下面代码,通过传递类型Vec&vec_instance
到方法fn_borrow()
,以实现这种借用机制:
let vec_u8s = &vec_instance;
题外话
Rust语言下横杆_
在上面程序中,有两个地方存在下横杆_
,这是Rust语言待定符号。比如,下面代码,要是函数或者方法参数还没有确定如何使用,就可以使用这个待定符号下横杆_
。
fn fn_borrow(_: u8) {}
下面是另外一个代码实例,我们知道,接下来该变量会存在类型定义,如作为方法参数使用该变量,也可以使用这个待定符号下横杆_
。但是要是没有作为方法参数和其他使用该变量,编译器就会报错。
let vec_instance :Vec<_> = vec![33, 42];
向量类型完整定义方法
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/kw_fn/vec_u8/mod.rs // #[cfg(feature = "cp")] fn fn_borrow(vec_u8s: &Vec<u8>) { println!("Inside fn = {:p}", &vec_u8s); } let mut vec_instance: Vec<u8> = Vec::<u8>::new(); vec_instance.push(33); vec_instance.push(42); println!("Before fn = {:p}", &vec_instance); fn_borrow(&vec_instance); println!("After fn = {:p}", &vec_instance); dbg!(vec_instance); #}
上面程序方法`main()`第二段的第一行代码,是类型向量最完整的表达形式,它也可以简写为下面这种常见的一行代码。同时也要注意到,前面代码里方法参数也是简写形式,而上面程序代码也是完整形式。这样定义对象变量的类型Vec::<u8>
形式,与方法参数定义的引用类型&Vec::<u8>
形式具有其一致性。
let mut vec_instance = Vec::new();
向量宏vec!
在前面程序代码里,可以看到类型向量宏vec!,这是创建其对象的宏,它类似于打印宏println!
。上面一行宏vec!代码,相当于下面的三行代码。当然其中不同的是,下面类型向量对象是可变绑定方式,而前面的向量对象是不可变绑定方式。Rust语言为我们创建类型向量对象提供了两种不同的方式:不可变和可变的向量绑定对象。
# #![allow(unused_variables)] #fn main() { let mut vec_instance: Vec<u8> = Vec::<u8>::new(); vec_instance.push(33); vec_instance.push(42); #}
下面我们把上面代码整理如下,目的便于与类型字符串进行比较:
# #![allow(unused_variables)] #fn main() { // File: lib-hello/src/mutable/mut_new/mod.rs // Function use_vec_mut_new() let immut_vec: Vec<_> = vec![33, 42]; dbg!(immut_vec); let mut mut_vec: Vec<u8> = Vec::<u8>::new(); mut_vec.push(33); mut_vec.push(42); dbg!(mut_vec); #}
其实,类型字符串也有同样的功能,只是表达方式不同而已:
# #![allow(unused_variables)] #fn main() { // File: lib-hello/src/mutable/mut_new/mod.rs // Function use_string_mut_new() let immut_string = String::from("Hello"); dbg!(immut_string); let mut mut_string = String::new(); mut_string.push_str("Hello"); dbg!(mut_string); #}
参考资料
- Rustdoc: No mention of copy trait for primitive types
- Understanding Rust: ownership, borrowing, lifetimes
应用篋:闭包借用实例
学习内容
- 了解和学习Rust语言闭包借用实例
篇目
理解Rust语言闭包
闭包(closure)是现代计算机语言发展起来的重要技术,它延伸了传统计算机语言函数的概念。Rust语言中的闭包(也称为lambda表达式或lambda,)是可以捕获封闭环境的函数。传统函数就是一个独立的和重复使用的功能性代码单位,它通过被调用而与其它代码进行交互。而闭包不仅可以被其它代码调用;而且其代码块本身也可以直接与其封闭环境内的代码进行交互,捕获外部环境变量的能力,而这一点传统函数没有这样的功能。
闭包与函数比较说明:
- 闭包可以捕获外部环境变量的能力,而函数不能;
- 传递到闭包的输入参数。使用
||
,而不是像函数()
一样; - 闭包代码块也使用函数一样的
{}
,但是函数是强制性使用,而闭包在一些情况下是可选性使用; - 输入参数的类型也是可选的,而非函数是强制的;
- 闭包不存在闭包名,而函数不能,甚至闭包连绑定变量也可以不要;
- 调用闭包不仅与函数有完全一样的方式,而且还存在闭包自身特殊方式;
- 闭包输出的表达方式与函数完全一样;
- 闭包返回方式与函数完全一样;
将上面比较说明,对照下面实例代码。方法main()
的第二行代码,第一,定义了闭包,且绑定到变量i_am_closure
,可以理解为“闭包名”;第二,把第一行代码这种外部变量outside_closure
应用到闭包代码块内部;第三,输入参数input_var
省略了类型名称,之所以可以省略,是因为第一行代码定义了输入参数的类型。第三行代码里存在调用第二行定义的闭包代码。
fn main() { let outside_closure = 21u8; let i_am_closure = |input_var| -> u8 { input_var + outside_closure }; println!("i_am_closure: {}", i_am_closure(outside_closure)); }
闭包实例
通过下面实例来进一步理解闭包使用方法。这个实例看起来比较抽象,这一行代码连一个字母都没有。但这是理解闭包很好的代码。
fn main(){ (|__:()|->(){__})(()) }
通过下面程序代码逐步展开来解析上面程序的含义。程序里每一段代码都是一样的功能,从上到下还原到可以比较理解的代码。
需要说明的是,在方法main()
里,前面四段代码,既定义了闭包,同时也调用了闭包。只有第五段代码定义与调用分成了两个语句。
# #![allow(unused_variables)] #fn main() { // File: lib-hello/src/immut/closure/mod.rs // Function: use_closure() (|__: ()| -> () { __ })(()); (|input_var: (bool)| -> (bool) { input_var })((true)); |input_var: (bool)| -> (bool) { input_var }((true)); let tuple = (true); |input_var: (bool)| -> (bool) { input_var }(tuple); let tuple = (true); let closure_instance = |input_var: (bool)| -> (bool) { input_var }; closure_instance(tuple); #}
未实现复制特质Copy
类型的借用实例
下面代码说明字符串类型String
对象作为闭包参数使用以后,其生命周期结束的实例。变量作为闭包参数使用以后,在闭包里,进行了一次变量的复制,导致了Rust语言所示的移动move
,实际上,在闭包完成调用以后,该变量从内存里被剔除掉了。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/closure/immut_string/mod.rs // #[cfg(feature = "err_01")] let string_instance: String = "Hello".to_string(); let closure_instance = |hello: String| println!("{} Friend!", hello); closure_instance(string_instance); println!("{} World!", string_instance); #}
借用机制代码实例
解决上面实例的方法,与前面一节函数方法一样,使用引用类型对象。传递到闭包里,而不是类型字符串对象。引用变量对象仅仅是利用了类型字符串对象的内容而已,闭包本身实际上没有与类型字符串对象发生联系。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/closure/immut_string/mod.rs // #[cfg(feature = "ok")] let string_instance: String = "Hello".to_string(); println!("Before fn = {:p}", &string_instance); let closure_instance = |hello: &str| { println!("{} Friend!", hello); println!("Inside fn = {:p}", &hello); }; closure_instance(&string_instance); println!("After fn = {:p}", &string_instance); println!("{} World!", string_instance); #}
题外话
参考资料
- www.cs.brandeis.edu tuples
- Understanding the different types of closures in Rust
- Why Rust Closures are (Somewhat) Hard
- rust-by-example closures
应用篋:移动关键词move
借用实例
学习内容
- 了解和学习Rust语言移动关键词
move
借用实例
篇目
移动关键词move
移动关键词move
是转移对象的所有权,实际上是使得该对象失去所有权。类似于函数调用结束以后,会自动转移类型对象的所有权,但不包括引用类型对象。
通过比较下面两个程序,可以了解到移动关键词move
的表达方法。
通过比较下面两个程序,它们唯一区别就是第二个程序多了关键词move
。实际运行它们,可以看到,第一个程序正常,而第二个程序出现错误,借此可以理解到移动关键词move
的作用。即使是使用对象的引用,也会使得其对象本身失去所有权,更不用说使用对象本身。
连使用对象的引用,也失去其对象本身的所有权,这是否与借用机制不一致了?网上有人提出这样的问题。我们下面使用了一个技巧,来解决这个问题。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/closure/kw_move/mod.rs // #[cfg(feature = "cp")] let instance = vec![1, 2, 3]; // borrow this variable `instance` (|| { (&instance); })(); //|| { (&instance); }; // ok: x was not moved println!("{:?}", instance); #}
在下面程序里,也有定义闭包的语句。即使仅使用定义语句,也会出现错误。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/closure/kw_move/mod.rs // #[cfg(feature = "err_02")] let instance = vec![1, 2, 3]; // move this variable `instance` (move || { (&instance); })(); //( move || (instance.len()) )(); //move || { (&instance); }; //move || ( instance.len() ); // ERROR: x was moved println!("{:?}", instance); #}
借用机制代码实例
在使用移动关键词move
前提下,这是因为有时候需要使得一些变量失去其所有权,而另外一些变量不能失去其所有权。
为了使得一些变量不能失去其所有权,而又要借用该变量。解决办法是这样的:为了使得一个对象的借用有效,这时候需要在闭包以前先把该对象引用绑定一个变量,然后把绑定引用对象变量使用到闭包里。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/closure/move_vec/mod.rs // #[cfg(feature = "ok")] let instance = vec![1, 2, 3]; println!("The variable instance before borrowing: {:?}", instance); let ref_instance = &instance; let equal_to_val = move |input_var| input_var == ref_instance; println!("The variable instance after borrowing: {:?}", instance); // use this closure let input_instance = vec![1, 2, 3]; assert!(equal_to_val(&input_instance)); #}
题外话
参考资料
应用篋:循环for
语句不可变借用实例
在Rust语言里,固定大小类型对象与可变大小类型对象的处理方式常常是不一样的。下面通过固定数组和可变数组的实例说明这个问题。
学习内容
- 了解和学习Rust语言循环
for
语句借用实例
篇目
固定数组的借用错误实例
这是一个说明固定数组类型[T;N]
借用错误的实例。在循环语句里,不能将固定数组对象instance
作为for
语句的迭代部分,这是因为固定数组对象本身不是一个迭代器。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_arr.rs // #[cfg(feature = "err_01")] let instance = [1u8, 2, 3]; for item in instance { print!("{:p} ", item); } println!("instance array = {:?}", instance); #}
在编译该程序以后,编译器给出有用的信息提示,如下所示:
...
borrow the array with `&` or call `.iter()` on it to iterate over it(`instance`)
...
= note: arrays are not iterators, but slices like the following are: `&[1, 2, 3]`
...
这些信息告诉我们:
- 借用方法可以使用
&
或调用方法.iter()
; - 固定数组
array
不是迭代器;
下面利用这些信息,来解决固定数组的借用问题。
固定数组的借用机制实例
在循环for
语句里,对于数组类型,Rust语言提供里几种借用方法。但是对于固定数组和可变数组类型,其方法上略有不同。这里先说明固定数组类型的借用机制实例。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_arr.rs // #[cfg(feature = "ok")] let instance = [1u8, 2, 3]; println!("{:>17} {} {:p}", "instance ref", "=", &instance); print!("{:>17} {} ", "for ref", "="); for item in &instance { let ref_item: &u8 = item; print!("{:p} ", ref_item); } println!(""); print!("{:>17} {} ", "for .iter()", "="); for item in instance.iter() { let ref_item: &u8 = item; print!("{:p} ", ref_item); } println!(""); print!("{:>17} {} ", "for .into_iter()", "="); for item in instance.into_iter() { //let u8_item :u8 = item; // ERROR: item IS &u8!!! let ref_item: &u8 = item; print!("{:p} ", ref_item); } println!(""); println!("{:>17} {} {:?}", "instance arr", "=", instance); #}
在方法main()
里,一共有五段代码,说明如下:
- 中间三段代码就是固定数组的借用方法,三种方法都可以使用;
- 对于固定数组,三种方法的思路也是一致的,在循环
for
语句的迭代过程中,通过引用对象遍历其每一个项进行迭代; - 最后一行代码可以通过编译,说明了该对象
instance
还是可以使用的,其生命周期还没有结束,同时可以验证上面借用方法都是可行的。 - 从该程序下面的输出结果,可以看到,它们的内存地址都是指向相同的原始指针;
- 当中有些宏
print!
或者println!
只是为了输出结果可以更加容易理解;
该程序输出结果如下:
───────┬───────────────────────────────────────────────────────────────────────
│ STDIN
───────┼───────────────────────────────────────────────────────────────────────
1 │ instance ref = 0x7fff567a7c5d
2 │ for ref = 0x7fff567a7c5d 0x7fff567a7c5e 0x7fff567a7c5f
3 │ for .iter() = 0x7fff567a7c5d 0x7fff567a7c5e 0x7fff567a7c5f
4 │ for .into_iter() = 0x7fff567a7c5d 0x7fff567a7c5e 0x7fff567a7c5f
5 │ instance arr = [1, 2, 3]
───────┴───────────────────────────────────────────────────────────────────────
可变数组的简单借用错误实例
下面是一个可变数组的循环for
语句实例。注意,这个实例仅仅说明,与固定数组错误不同的是,可变数组对象是一个迭代器。
# #![allow(unused_variables)] #fn main() { pub fn adjoin() { // File: ./bin-hello/examples/for_loop/for_vec.rs // #[cfg(feature = "cp")] let instance = vec![1, 2, 3]; println!("{:>17} {} {:p}", "instance raw", "=", instance.as_ptr()); print!("{:>17} {} ", "for u8", "="); for item in instance { let u8_item: u8 = item; print!("{:p} ", &u8_item); } println!(""); #}
可能有人发现,上面程序的下面输出结果内存地址不一样,这是为什么,是因为一旦可变数组对象作为迭代器,在循环for
语句被使用以后,该对象就被转移了(moved),自然它们的内存地址就不一样了。
───────┬────────────────────────────────────────────────────────────────────────
│ STDIN
───────┼────────────────────────────────────────────────────────────────────────
1 │ instance raw = 0x7f8779500b80
2 │ for u8 = 0x7fff54a6b05f 0x7fff54a6b05f 0x7fff54a6b05f
───────┴────────────────────────────────────────────────────────────────────────
接下来才是一个可变数组的借用错误实例,只是在上面实例中多增加了最后一行代码。只是一旦将其作为迭代器,其生命周期就解释了。这样就出现下面出现所谓的“借用错误”。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_vec.rs // #[cfg(feature = "err_02")] let instance = vec![1, 2, 3]; println!("{:>17} {} {:p}", "instance raw", "=", instance.as_ptr()); print!("{:>17} {} ", "for u8", "="); for item in instance { let u8_item: u8 = item; print!("{:p} ", &u8_item); } println!(""); println!("{:>17} {} {:?}", "instance vec", "=", instance); // error here #}
可变数组的借用机制实例
下面我们使用可变数组对象作为迭代器,解决可变数组的借用问题。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_vec.rs // #[cfg(feature = "ok")] let instance = vec![1u8, 2, 3]; println!("{:>17} {} {:p}", "instance raw", "=", instance.as_ptr()); print!("{:>17} {} ", "for ref", "="); for item in &instance { let ref_item: &u8 = item; print!("{:p} ", ref_item); // OK: item IS Pointer } println!(""); println!("{:>17} {} {:?}", "instance vec", "=", instance); #}
上面实例在借用机制方法下,可以看到该程序输出的结果,它们内存地址是相同的,这是因为它们仅仅借用而已。
该程序输出结果如下:
───────┬────────────────────────────────────────────────────────────────────────
│ STDIN
───────┼────────────────────────────────────────────────────────────────────────
1 │ instance raw = 0x7f8a504035e0
2 │ for ref = 0x7f8a504035e0 0x7f8a504035e1 0x7f8a504035e2
3 │ instance vec = [1, 2, 3]
───────┴────────────────────────────────────────────────────────────────────────
迭代方法iter()
和into_iter()
区别
在上面固定数组对象实例中,我们使用了迭代方法into_iter()
,运行是正常的,为什么到了可变数组对象就不可编译了呢?
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_vec_iter.rs // #[cfg(feature = "err_03")] let instance = vec![1, 2, 3]; println!("{:>13} {} {:p}", "instance raw", "=", instance.as_ptr()); print!("{:>13} {} ", "for into_iter", "="); for item in instance.into_iter() { let u8_item: u8 = item; print!("{:p} ", &u8_item); } println!(""); println!("{:>13} {} {:?}", "instance vec", "=", instance); #}
下面分析其原因。Rust语言提供了三个迭代方法。这里代码仅仅说明其中两个方法iter()
和into_iter()
,但是这里把三个方法功能说明如下:
- 方法
iter()
通过迭代器的引用对象,遍历其每一个项; - 方法
into_iter()
将迭代器移至新迭代器,然后通过迭代器的对象,遍历其每一个项; - 方法
iter_mut()
通过迭代器的可变引用对象,遍历其每一个项;
从上面三个方法的功能,可以知道,方法into_iter()
的迭代手段不是借用,而是直接消费了。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_vec_iter.rs // #[cfg(feature = "cp")] let instance = vec![1u8, 2, 3]; println!("{:>13} {} {:p}", "instance raw", "=", instance.as_ptr()); print!("{:>13} {} ", "for into_iter", "="); for item in instance.into_iter() { let u8_item: u8 = item; //print!("{:p} ", item); // ERROR: item IS {integer}, NOT Pointer print!("{:p} ", &u8_item); } println!(""); //println!("{:>13} {} {:?}", "instance vec", "=", instance); #}
通过下面实例输出结果来分析方法into_iter()
:
- 循环语句变量
item
不是引用对象&u8
,而是类型u8
对象; - 要是把上面程序循环语句的注视去掉,就会出现错误,这再次说明,变量
item
是类型u8
对象,同时也说明了方法into_iter()
使用遍历其每一个项都是类型u8
对象,而不是方法iter()
一样的类型&u8
引用对象; - 循环语句内外内存地址是不同的;
第二个程序输出结果如下:
───────┬───────────────────────────────────────────────────────────────────────
│ STDIN
───────┼───────────────────────────────────────────────────────────────────────
1 │ instance raw = 0x7fb3604035e0
2 │for into_iter = 0x7fff5ad6705f 0x7fff5ad6705f 0x7fff5ad6705f
───────┴───────────────────────────────────────────────────────────────────────
可变数组的迭代借用机制实例
通过上面分析,下面实例代码就比较简单了。它是使用方法iter()
,实现循环语句for
的迭代借用机制。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_vec_iter.rs // #[cfg(feature = "ok")] let instance = vec![1u8, 2, 3]; println!("{:>13} {} {:p}", "instance raw", "=", instance.as_ptr()); print!("{:>13} {} ", "for vec ref", "="); for item in &instance { let ref_item: &u8 = item; print!("{:p} ", ref_item); // OK: item IS Pointer } println!(""); print!("{:>13} {} ", "for vec iter", "="); for item in instance.iter() { let ref_item: &u8 = item; print!("{:p} ", ref_item); // OK: item IS Pointer } println!(""); println!("{:>13} {} {:?}", "instance vec", "=", instance); #}
该程序输出结果如下。从中可以看到这些原始指针的内存地址都是一样的,它们指向相同的可变数组的数据。
───────┬───────────────────────────────────────────────────────────────────────
│ STDIN
───────┼───────────────────────────────────────────────────────────────────────
1 │ instance raw = 0x7f90084035e0
2 │ for vec ref = 0x7f90084035e0 0x7f90084035e1 0x7f90084035e2
3 │ for vec iter = 0x7f90084035e0 0x7f90084035e1 0x7f90084035e2
4 │ instance vec = [1, 2, 3]
───────┴───────────────────────────────────────────────────────────────────────
题外话
参考资料
- What is the difference between iter and into_iter?
- Effectively Using Iterators In Rust
- How to iterate over and filter an array?
- How to iterate over an array of integers?
- rust-patterns-ref
题外话:关键词ref
与引用符&
在下面一段代码只有一个let
语句里,目的是说明该&ref
操作等价于什么也没有做,它们是相反的操作,类似于操作*
和&
也是相反的操作。
# #![allow(unused_variables)] #fn main() { // File: lib-hello/src/immut/type_ref/mod.rs // Function use_ref_and() let x: u8 = 33; let w: &u8 = &x; let ref y: u8 = x; let z: &u8 = y; println!("x = {:p}", &x); println!("w = {:p}", w); println!("y = {:p}", y); println!("z = {:p}", z); println!("x = {}", x); println!("w = {}", w); println!("y = {}", y); println!("z = {}", z); let &ref y = &x; println!("y = {:p}", y); println!("y = {}", y); println!(); let x: Vec<u8> = vec![33, 42]; let w: &Vec<u8> = &x; let ref y: Vec<u8> = x; let z: &Vec<u8> = y; println!("x = {:p}", &x); println!("w = {:p}", w); println!("y = {:p}", y); println!("z = {:p}", z); println!("x = {:?}", x); println!("w = {:?}", w); println!("y = {:?}", y); println!("z = {:?}", z); let &ref y = &x; println!("y = {:p}", y); println!("y = {:?}", y); #}
借助于工具cargo-clippy,使用下面命令:
# 工具cargo-clippy命令
cargo clippy
可以获取如下编译器的警告信息:
warning: `ref` on an entire `let` pattern is discouraged, take a reference with `&` instead
--> lib-hello/src/immut/type_ref/mod.rs:105:9
|
105 | let ref y: u8 = x;
| ----^^^^^--------- help: try: `let y: &u8 = &x;`
|
= note: `#[warn(clippy::toplevel_ref_arg)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#toplevel_ref_arg
从上面警告,可以解读这些内容:
- 在上面程序一段代码包括四个
let
语句里,中间两个是等价的; - 不鼓励在整个
let
语句上使用关键词引用符ref
,而应使用借用符&
; - 从中可以知道,关键词
ref
不应该在let
语句上使用,而更适合在macth
语句里使用它。借用符&
要引用一个对象,而ref
通过引用而不是按值绑定到位置。换句话说,借用符&
实现简单的借用,而引用符ref
则是“借用我,到与我相匹配的位置上去”;
在宏println!()
打印内存地址信息时,只有非引用对象变量,才在变量前面使用借用符&
。要是去掉该引用符&
,编译器将会出现下面错误信息,从中也可以知道,谁是引用谁不是引用对象:
error[E0277]: the trait bound `u8: std::fmt::Pointer` is not satisfied
--> src/main.rs:5:26
|
5 | println!("x = {:p}", x);
| ^ the trait `std::fmt::Pointer` is not implemented for `u8`
|
= note: required by `std::fmt::Pointer::fmt`
参考资料
- Ref keyword versus &
- The ref pattern
- & vs. ref in Rust patterns
- ref-patterns-destructuring-and-invisible-borrows
软件篋borrowing_exerci(二)
$$\text{在茫茫的黑暗中,要能发出内在的微光}$$
应用篋:借用次数实例
在Rust语言里,从固定和可变绑定对象引用形式上是相同的,但是它们可以引用的次数是不同的。可变绑定对象引用只能是一次,而固定对象引用可以是多次。
学习内容
- 了解和学习Rust语言借用次数实例
篇目
可变关键词mut
绑定对象的借用方法
可变关键词mut
用来绑定可变对象。下面程序代码的第一行就是实例。在let
语句的基础上,增加可变关键词mut
就可以实现绑定可变对象。所谓可变对象,就是其值在其范围内肯定会被修改的。修改可变对象值的实例是第二段代码的第一行。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/mut_base/mut_ref/mod.rs let mut mut_instance = 1u8; println!("1. mut_instance = {}", mut_instance); mut_instance = 33; println!("2. mut_instance = {}", mut_instance); let ref_mut_instance = &mut mut_instance; *ref_mut_instance = 42; println!("3. mut_instance = {}", mut_instance); mut_instance = 100; println!("4. mut_instance = {}", mut_instance); #}
上面程序第三段代码的第一行代码是,绑定可变引用对象的方法实例。在紧接着引用符&
之后,增加可变关键词mut
就可实现绑定可变引用对象。修改可变引用对象值的实例是第三段代码的第二行。
需要注意的是,修改可变引用对象(如这里:ref_mut_instance
)值就是修改其可变对象(如这里:mut_instance
)值。
在第四段代码里,还可以使用可变对象mut_instance
,这说明了上面可变引用对象是可行的。下面是该程序的输出结果:
1. mut_instance = 1
2. mut_instance = 33
3. mut_instance = 42
4. mut_instance = 100
固定绑定对象的借用次数
下面程序将要说明,对于固定和可变绑定对象而已,使用固定绑定引用对象的次数是不受限制的。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/mut_base/mut_count/mod.rs // #[cfg(feature = "ok")] let instance = vec![33u8, 42]; let first_ref = &instance; // immutable reference let second_ref = &instance; // immutable reference println!("{:?} {:?}", first_ref, second_ref); let mut instance = vec![33, 42u8]; let first_mut_ref = &instance; // immutable reference let second_mut_ref = &instance; // immutable reference println!("{:?} {:?}", first_mut_ref, second_mut_ref); #}
关键词mut
绑定对象的借用次数
对于可变绑定对象而已,当其引用对象也是可变的时,在其有效范围内,这种可变引用对象的绑定同时只能存在一个。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/mut_base/mut_count/mod.rs // #[cfg(feature = "err_01")] let mut instance = vec![33u8, 42]; let first_mut_ref = &mut instance; // mutable reference let second_mut_ref = &mut instance; // mutable reference println!("{:?} {:?}", first_mut_ref, second_mut_ref); #}
下面程序里使用了两次可变引用对象的绑定,这样就会导致编译器提示错误信息:同时不能多次借用对象instance
作为可变引用变量。
error[E0499]: cannot borrow `instance` as mutable more than once at a time
--> bin-hello/examples/mut_base/mut_count.rs:38:26
|
37 | let first_mut_ref = &mut instance; // mutable reference
| ------------- first mutable borrow occurs here
38 | let second_mut_ref = &mut instance; // mutable reference
| ^^^^^^^^^^^^^ second mutable borrow occurs here
39 | println!("{:?} {:?}", first_mut_ref, second_mut_ref);
| ------------- first borrow later used here
可变绑定对象与固定绑定对象联系
对于可变绑定对象,要是先绑定了其固定引用对象,然后再绑定其可变引用对象,注意此时,只是前面固定引用对象不能再借用了,而后面可变引用对象的借用还是有效的。这是下面的实例程序。要是把该程序最后一行代码注释掉,程序还是运行正常的。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/mut_base/mut_count/mod.rs // #[cfg(feature = "err_02")] let mut instance = vec![33, 42u8]; let first_immut_ref = &instance; // immutable reference let second_mut_ref = &mut instance; // mutable reference println!("{:?}", first_immut_ref); #}
同样道理,对于可变绑定对象,要是先绑定了其可变引用对象,然后再绑定其固定引用对象,则前面可变引用对象就不能在借用了,而后面固定引用对象的借用还是有效的。这是下面的实例程序。要是把该程序最后一行代码注释掉,程序还是运行正常的。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/mut_base/mut_count/mod.rs // #[cfg(feature = "err_03")] let mut instance = vec![33u8, 42]; let first_mut_ref = &mut instance; // mutable reference let second_immut_ref = &instance; // immutable reference println!("{:?}", first_mut_ref); #}
题外话
参考资料
应用篋:绑定引用的固定对象借用实例
从可变对象(mutable variable)出发,其引用方式,使用两种不同的借用方式:固定引用(immutable reference)和可变引用(mutable reference)。本节将解释把固定和可变引用绑定到固定对象的借用方式。尤其是当固定对象与固定和可变引用交互在一起时,程序代码将会比较更复杂一些。但是通过这一节学习可以加深对借用对象的生命周期概念的理解。
学习内容
- 了解和学习把固定和可变引用绑定到固定对象的借用实例
篇目
绑定固定引用的固定对象
let immut_and_immut_ref = &immut_instance;
let immut_and_immut_ref = &mut_instance;
上面示意图说明了下面程序可变对象mut_instance
及其固定引用的对象immut_ref
(下面简称为“固定引用的固定对象”或者”引用固定对象“)的生命周期。左边图一直可以使用到它们的生命周期结束,这是下面程序的情况。图上带箭头的连接线,是程序代码顺序,而图上无箭头的连接线说明了对象的生命周期范围。
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_var_sized/string_ref.rs // #[cfg(feature = "ok")] let mut mut_instance = String::from("Hello"); mut_instance.push_str(", world"); println!("1. use mut_instance = {}", mut_instance); let immut_ref = &mut_instance; println!("1. use immut_ref = {}", immut_ref); // The variable `mut_instance` borrowed here after move println!("2. use mut_instance = {}", mut_instance); println!("2. use immut_ref = {}", immut_ref); #}
另外一种情况,就是右边示意图,当可变对象mut_instance
需要拿回所有权之时,也是引用固定对象immut_ref
生命周期结束之时。要是还想使用已经结束生命周期的对象immut_ref
,编译器就会告诉我们这是不可以的。在之后的其他实例说明过程中,不再说明左边图的情况,主要说明右边示意图的情况。
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_var_sized/string_ref.rs // ANCHOR = "string_ref-error_02" // error[E0502]: cannot borrow `mut_instance` as mutable because it is also borrowed as immutable let mut mut_instance = String::from("Hello"); mut_instance.push_str(", world"); println!("1. use mut_instance = {}", mut_instance); let immut_ref = &mut_instance; println!("1. use immut_ref = {}", immut_ref); // The variable `immut_ref` begin to move here mut_instance.push_str("!"); // The variable `immut_ref` moved here println!("2. use immut_ref = {}", immut_ref); //ERROR // The variable `mut_instance` borrowed here after move println!("2. use mut_instance = {}", mut_instance); #}
要是上面程序,在对象mut_instance
需要拿回所有权以后,不再使用对象immut_ref
,程序一切正常。
下面程序说明了这种直接转移可变对象mut_instance
,或者说使用对象copy_mut_instance
,使得可变对象mut_instance
失去其生命周期,取而代之。在对象copy_mut_instance
取而代之以后,这样,就不再可以使用对象mut_instance
。要是再使用它就会如下下面程序一样,出现编译错误。
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_var_sized/string_ref.rs // ANCHOR = "string_ref-error_01" // error[E0382]: borrow of moved value: `mut_instance` let mut mut_instance = String::from("hello"); mut_instance.push_str(", world!"); println!("1. use mut_instance = {}", mut_instance); // The variable `mut_instance` begin to move here let copy_mut_instance = mut_instance; // The variable `mut_instance` moved here // ERROR: The variable `mut_instance` borrowed here after move println!("2. use mut_instance = {}", mut_instance); //ERROR #}
绑定可变引用的固定对象
let immut_and_mut_ref = &mut mut_instance;
上面示意图也是说明下面程序可变对象mut_instance
及其可变引用的对象mut_ref
(下面简称为“可变引用的固定对象”或者”引用固定对象“)mut_ref
的生命周期。
对于这种可变对象及其可变引用的固定对象情况,从借用机制上看,与上面可变类型对象的固定引用的固定对象思路是一样的,不同的是可变引用的固定对象可以修改其可变对象的值,并且修改结果直接改变到可变对象的内容。
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_var_sized/string_ref.rs // #[cfg(feature = "cp")] let mut mut_instance = String::from("Hello"); mut_instance.push_str(", world"); println!("1. use mut_instance = {}", mut_instance); // The variable `mut_instance` begin to move here let mut_ref = &mut mut_instance; // The variable `mut_instance` moved here println!("1. use mut_ref = {}", mut_ref); mut_ref.push_str("!"); println!("2. use mut_ref = {}", mut_ref); mut_instance.make_ascii_uppercase(); println!("2. use mut_instance = {}", mut_instance); #}
下面程序说明了,当对象mut_instance
拿回所有权以后,不再可以使用引用固定对象mut_ref
了。
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_var_sized/string_ref.rs // ANCHOR = "string_ref-error_03" // error[E0499]: cannot borrow `mut_instance` as mutable more than once at a time let mut mut_instance = String::from("Hello"); mut_instance.push_str(", world"); println!("1. use mut_instance = {}", mut_instance); // The variable `mut_instance` begin to move here let mut_ref = &mut mut_instance; // The variable `mut_instance` moved here println!("1. use mut_ref = {}", mut_ref); mut_ref.push_str("!"); println!("2. use mut_ref = {}", mut_ref); // The variable `mut_ref` begin to move here mut_instance.make_ascii_uppercase(); // The variable `mut_ref` moved here println!("3. use mut_ref = {}", mut_ref); // ERROR // The variable `mut_instance` borrowed here after move println!("2. use mut_instance = {}", mut_instance); #}
绑定不同引用的固定对象
上面示意图的左边分支说明下面程序对象mut_instance
、mut_ref
和immut_ref
的生命周期。这里看到,一个可变对象涉及到其固定引用与可变引用的固定对象的使用方法。要是查看三个对象的内存地址,它们是完全相同的。
下面程序是左边示意图的正确借用方法:
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_var_sized/string_refs.rs // #[cfg(feature = "ok")] let mut instance = String::new(); instance.push_str("hello"); let mut_ref :&mut String = &mut instance; mut_ref.push_str(" world"); let immut_ref :&String = mut_ref; println!("immut_ref = {}", immut_ref); println!("mut_ref = {}", mut_ref); mut_ref.make_ascii_uppercase(); // immut_ref is moved after here //println!("immut_ref = {}", immut_ref); println!("mut_ref = {}", mut_ref); instance.push('!'); // mut_ref is moved after here //println!("immut_ref = {}", mut_ref); println!("instance = {}", instance); #}
下面程序说明了固定对象immut_ref
的生命周期编译错误问题:
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_var_sized/string_refs.rs // ANCHOR = "string_refs-error_01" // error[E0502]: cannot borrow `*mut_ref` as mutable because it is also borrowed as immutable let mut instance = String::new(); instance.push_str("hello"); let mut_ref :&mut String = &mut instance; mut_ref.push_str(" world"); let immut_ref :&String = mut_ref; println!("immut_ref = {}", immut_ref); println!("mut_ref = {}", mut_ref); mut_ref.make_ascii_uppercase(); // immut_ref is moved after here println!("immut_ref = {}", immut_ref); // ERROR println!("mut_ref = {}", mut_ref); instance.push('!'); // mut_ref is moved after here //println!("immut_ref = {}", mut_ref); println!("instance = {}", instance); #}
下面程序说明了固定对象mut_ref
的生命周期编译错误问题:
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_var_sized/string_refs.rs // ANCHOR = "string_refs-error_02" // error[E0499]: cannot borrow `instance` as mutable more than once at a time let mut instance = String::new(); instance.push_str("hello"); let mut_ref :&mut String = &mut instance; mut_ref.push_str(" world"); let immut_ref :&String = mut_ref; println!("immut_ref = {}", immut_ref); println!("mut_ref = {}", mut_ref); mut_ref.make_ascii_uppercase(); // immut_ref is moved after here //println!("immut_ref = {}", immut_ref); println!("mut_ref = {}", mut_ref); instance.push('!'); // mut_ref is moved after here println!("mut_ref = {}", mut_ref); // ERROR println!("instance = {}", instance); #}
上面示意图的右边分支是另外一种可能性。上面左边和右边示意图的分支仅仅是下面代码不同。左边分支示意图的对象immut_ref
的值取自于引用对象mut_ref
,而右边分支示意图的对象immut_ref
的值直接取自于instance
的引用:
# #![allow(unused_variables)] #fn main() { // left side image //let immut_ref :&String = mut_ref; // right side image let immut_ref :&String = &instance; #}
对于这种情况,下面程序给出了对象mut_ref
的生命周期编译错误问题:
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_var_sized/string_refs.rs // ANCHOR = "string_refs-error_03" // error[E0502]: cannot borrow `instance` as immutable because it is also borrowed as mutable let mut instance = String::new(); instance.push_str("hello"); let mut_ref :&mut String = &mut instance; mut_ref.push_str(" world"); //let immut_ref :&String = mut_ref; let immut_ref :&String = &instance; println!("immut_ref = {}", immut_ref); println!("mut_ref = {}", mut_ref); // ERROR mut_ref.make_ascii_uppercase(); // immut_ref is moved after here //println!("immut_ref = {}", immut_ref); println!("mut_ref = {}", mut_ref); instance.push('!'); // mut_ref is moved after here //println!("mut_ref = {}", mut_ref); println!("instance = {}", instance); #}
下面示意图说明还有一些其他更多的可能性。
题外话
参考资料
应用篋:作为函数参数对象的生命周期
本节将类型String
对象、类型String
引用和类型str
作为实例,分析它们作为函数参数的生命周期。
学习内容
- 了解和学习Rust语言含可变引用参数的函数借用实例
篇目
可变类型String
引用的生命周期
先把下面程序的两个对象进行一下说明:
- 对象
instance
是类型String
的可变对象,因为在使用let
绑定该对象instance
时,使用了关键词mut
。下面将对象instance
称之为可变对象; - 对象
mut_ref
是可变对象instance
的可变引用,因为在使用&
绑定对象值时,使用了关键词mut
。下面将对象mut_ref
称之为可变引用; - 对象
instance
是所有者,而对象mut_ref
仅仅是从所有者那里租用者,它们所拥有的内容是一样的; - 在对象
mut_ref
生命周期里,它可以操作对象instance
所拥有的所有内容。从这个意义上说,它们是完全一样的;
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_fn/base_string.rs // #[cfg(feature = "ok")] // FIXED fn one(input: &mut String) { input.push_str(", world"); println!("one() input = {:p}", input); } fn two(input: &mut String) { input.push('!'); println!("two() input = {:p}", input); } let mut instance = String::new(); println!("instance = {:p}", &instance); instance.push_str("Hello"); let mut_ref = &mut instance; println!("mut_ref = {:p}", mut_ref); one(mut_ref); two(mut_ref); println!("instance = {:p}", &instance); println!("{}", instance); #}
上面程序的运行结果如下。从该结果可以分析该程序,无论是可变对象instance
,还是可变引用mut_ref
或者方法参数input
,都是指向相同的内存地址,即可变对象instance
的内容。这里反映了Rust语言调用函数的借用机制。
───────┬──────────────────────────────────────────────────────
│ STDIN
───────┼──────────────────────────────────────────────────────
1 │ instance = 0x7fff5a219e70
2 │ mut_ref = 0x7fff5a219e70
3 │ one() input = 0x7fff5a219e70
4 │ two() input = 0x7fff5a219e70
5 │ instance = 0x7fff5a219e70
6 │ Hello, world!
───────┴──────────────────────────────────────────────────────
尽管主程序里,在创建可变引用mut_ref
以后,并没有看到对可变引用mut_ref
或者可变对象instance
进行赋值,但是可变对象instance
的内容一直在变化。
当方法借用可变引用mut_ref
以后,还是归还了所有权到主程序,而不是把该可变引用mut_ref
消费掉了。因此,第二个方法还可以继续使用该可变引用mut_ref
。
通过给两个方法传递可变引用mut_ref
,它们不仅可以借用其内容(这是因为绑定可变引用mut_ref
时,使用了关键词&
),而且还可以修改其内容(这是因为绑定可变引用mut_ref
时,在&
之后使用了关键词mut
)。同时方法还将其内容返回到可变引用mut_ref
,即可变对象instance
。
必须指出的是,方法参数也必须是可变引用,与调用方法时的对象类型是完全一致的,不然就会出现程序编译错误。下面程序就是说明这个问题。
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_fn/base_string.rs // #[cfg(feature = "err_05")] // error[E0596]: cannot borrow `*input` as mutable, as it is behind a `&` reference // FIXED // fn one(input: &mut String) { //OK fn one(input: &String) { input.push_str(", world"); println!("input = {:p}", input); } // fn two(input: &mut String) { //OK fn two(input: &String) { input.push('!'); println!("input = {:p}", input); } let mut instance = String::new(); println!("instance = {:p}", &instance); instance.push_str("Hello"); let mut_ref = &mut instance; println!("mut_ref = {:p}", mut_ref); one(mut_ref); two(mut_ref); println!("instance = {:p}", &instance); println!("{}", instance); // println!("instance = {:p}", &instance); //println!("{}", instance); #}
可变类型String
对象的生命周期
下面程序实例是将可变类型String
对象直接作为方法参数使用,就是方法直接借用可变类型String
对象,而不是其引用。下面实例可以正常运行,但是,它有什么问题呢?
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_fn/base_string.rs // #[cfg(feature = "okay")] // FIXED fn one(mut input: String) { input.push_str(", world"); println!("input = {:p}", &input); println!("input = {}", input); } let mut instance = String::new(); println!("instance = {:p}", &instance); instance.push_str("Hello"); one(instance); #}
上面程序的运行结果如下。当可变对象instance
作为参数传递到方法以后,该对象就被该方法消费掉了。从它们的内存地址信息可以看到,它们是不一样的。
───────┬──────────────────────────────────────────────────────
│ STDIN
───────┼──────────────────────────────────────────────────────
1 │ instance = 0x7fff53e5cf50
2 │ input = 0x7fff53e5cfc0
3 │ input = Hello, world
───────┴──────────────────────────────────────────────────────
主程序可变对象instance
是不是真的被方法消费掉了呢?通过编译下面程序,编译器会告诉我们答案。下面出现编译错误说明了可变对象instance
不转移了,就是被方法消费掉了。
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_fn/base_string.rs // #[cfg(feature = "err_04")] // error[E0382]: borrow of moved value: `instance` // FIXED fn one(mut input: String) { input.push_str(", world"); println!("input = {:p}", &input); } let mut instance = String::new(); println!("instance = {:p}", &instance); instance.push_str("Hello"); one(instance); println!("{}", instance); #}
引用类型str
对象的生命周期
上面说明了类型String
对象的可变引用,不仅可以被借用,而且还可以被修改。但是引用类型str
对象只能被借用,而不能被修改。因为它不能被修改,所以它返回时还是它本身。
引用类型str
对象作为方法参数,主要目的是传递其内容。
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_fn/base_str.rs // #[cfg(feature = "okey")] // FIXED fn one(input: &str) { println!("one() input = {:p}", input); let ret_input = input.to_ascii_uppercase(); println!("one() ret_input = {:p}", &ret_input); println!("one() ret_input = {}", ret_input); println!("one() input = {:p}", input); println!("one() input = {}", input); } let mut instance = String::new(); instance.push_str("Hello"); println!("after instance change = {:p}", &instance); println!(); let immut_ref :&str = &instance; println!("before call one instance = {:p}", immut_ref); one(immut_ref); println!("after call one instance = {:p}", immut_ref); println!("instance = {}", instance); #}
上面程序运行结果可以看到,尽管在方法里参数input
调用了方法to_ascii_uppercase()
,但是,不仅参数input
的内存地址始终与主程序输入对象的内存地址是完全一样的,而且其内容到最后还是原来的内容。这种调用了方法to_ascii_uppercase()
并不是修改其内容,而是创建一个新对象,在新对象里,可以看到其内容发生了变化。
───────┬──────────────────────────────────────────────────────
│ STDIN
───────┼──────────────────────────────────────────────────────
1 │ after instance change = 0x7fff5c99ce48
2 │
3 │ before call one instance = 0x7fff5c99ce48
4 │ one() input = 0x7f94a9c00330
5 │ one() ret_input = 0x7fff5c99cc70
6 │ one() ret_input = HELLO
7 │ one() input = 0x7f94a9c00330
8 │ one() input = Hello
9 │ after call one instance = 0x7fff5c99ce48
10 │ instance = Hello
───────┴──────────────────────────────────────────────────────
引用类型str
对象的生命周期符
通过下面实例,将引出生命周期符的概念。下面程序编译会出现错误:"缺少生命周期符"。
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_fn/base_str.rs // #[cfg(feature = "err_07")] // error[E0106]: missing lifetime specifier // FIXED fn one(x: &str, y: &str) -> &str { x } let first_str = "Hello"; let second_str = "World"; let result = one(first_str, second_str); println!("result = {}", result); #}
使用函数生命周期符,一般情况下,是因为函数返回对象存在引用对象。上面程序怎么样解决呢?下面程序将上面程序问题得到解决。之所以上面程序不能编译成功,是因为该方法存在两个引用,它们都有自己的生命周期,而结果只有一个生命周期,编译器不知道应该使用哪一个生命周期。生命周期符是统一这两个引用的生命周期。
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_fn/base_str.rs // #[cfg(feature = "ok")] // FIXED fn one<'de>(x: &'de str, y: &'de str) -> &'de str { println!("x = {:p}", x); println!("y = {:p}", y); if x.is_empty() { x } else { y } } let first_str = ""; let second_str = "World"; println!("first_str = {:p}", first_str); println!("second_str = {:p}", second_str); let result = one(first_str, second_str); println!(); println!("first_str = {:p}", first_str); println!("second_str = {:p}", second_str); println!("result = {:p}", result); println!("result = {}", result); #}
在上面程序里,生命周期符'de
是由两部分:前面单引号'
是必须的;而后面与对象命名法则(snake_case)是一样的。一般使用'a
。
题外话
类型String
与类型str
的区别
字符串类型对象是可变的。而类型str
,通常以&str
表示对象是不可更改的,它只是对字符串的内容储存。
下面程序通过两个方法make_ascii_uppercase()
与to_ascii_uppercase()
说明,两种类型对象使用时的区别。方法make_ascii_uppercase()
是直接修改其对象内容,得到其结果;而方法to_ascii_uppercase()
并不是直接修改本身引用对象,而是创建了一个新类型String
对象作为返回结果。
fn main () { let mut instance = String::from("Hello"); println!("instance = {:p}", &instance); instance.make_ascii_uppercase(); println!("{}", instance); println!("instance = {:p}", &instance); println!(); let instance = "Hello"; println!("instance = {:p}", instance); instance.to_ascii_uppercase(); println!("instance = {}", instance); println!(); let instance = "Hello"; println!("instance = {:p}", instance); let ret_instance = instance.to_ascii_uppercase(); println!("instance = {}", instance); println!("ret_instance = {:p}", &ret_instance); println!("ret_instance = {}", ret_instance); }
参考资料
- rust-ownership
- how-can-i-create-a-static-string-in-rust
- what-are-the-differences-between-rusts-string-and-str
- rust-lifetimes-a-high-wall-for-rust-newbies
- book lifetimes
- book naming
应用篋:绑定引用的可变对象借用实例
通过这一节学习,通过类型String
的不同借用实现,理解绑定引用的可变对象的借用方法,这种引用也是两种类型:固定引用和可变引用,但是它们绑定的对象是可变的。
学习内容
- 了解和学习绑定固定引用和可变引用的可变对象借用实例
篇目
可变对象及其可变引用关系
下面将列出可变对象及其固定和可变引用可能关系。关于下面程序的前面两段关系,在前面已经探讨过了。本节主要探讨其他的关系。
# #![allow(unused_variables)] #fn main() { let mut instance = String::from("Hello"); let ref_immut = &instance; let ref_immut :&String = &instance; let mut instance = String::from("Hello"); let ref_mut = &mut instance; let ref_mut :&mut String = &mut instance; let mut instance = String::from("Hello"); let mut mut_ref_immut = &instance; let mut mut_ref_immut :&String = &instance; let mut instance = String::from("Hello"); let mut mut_ref_mut = &mut instance; let mut mut_ref_mut :&mut String = &mut instance; #}
对于可变对象,下面两段代码创建了它们相关的&str
引用对象。其中第一段代码前面已经说明过了。
# #![allow(unused_variables)] #fn main() { let mut instance = String::from("Hello"); let ref_immut :&str = &instance; let mut instance = String::from("Hello"); let mut mut_ref_immut :&str = &instance; #}
绑定固定引用的可变对象
下面程序的方法的特点是,使用引用类型&str
对象作为方法参数,而使用类型String
对象作为函数返回。Rust语言标准库一部分函数使用了这种函数表达形式。
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_immut/ref_str.rs // #[cfg(feature = "ok")] fn one(input: &str) -> String { let result = format!("{}{}", input, ", world"); println!("one() input = {:p}", input); result } fn two(input: &str) -> String { let result = format!("{}{}", input, "!"); println!("two() input = {:p}", input); result } let mut instance = String::new(); println!("instance = {:p}", &instance); instance.push_str("Hello"); println!(); let mut mut_immut_ref :&str = &instance; println!("before call one() mut_immut_ref = {:p}", mut_immut_ref); let tmp_instance = one(mut_immut_ref); println!("after call one() mut_immut_ref = {:p}", mut_immut_ref); instance = tmp_instance; println!("instance = {:?}", instance); println!(); mut_immut_ref = &instance; println!("mut_immut_ref = {:p}", mut_immut_ref); instance = two(mut_immut_ref); println!("instance = {:?}", instance); println!(); println!("instance = {:p}", &instance); #}
上面程序里存在下面一行代码。这行代码对象mut_ref_immut
具有如下属性:
- 它是可变对象
instance
的固定引用作为对象值,而不是可变引用作为对象值; - 它是类型
&str
对象,而不是类型String
的对象; - 它是一个可变对象,即该对象可以多次赋值,这种对象
mut_ref_immut
也称之为"固定引用的可变对象"、”引用可变对象“或者”引用“;
let mut mut_ref_immut :&str = &instance;
在上面程序里,方法需要类型就是对象mut_ref_immut
的类型,就是类型&str
对象,并且是固定引用。
当第一次调用方法以后,方法返回值就赋予了对象tmp_instance
,此时,对象mut_ref_immut
的所有权还是一直有效的,只有当对象tmp_instance
赋值给对象instance
时,其所有权被对象instance
重新拿收回了。当然引用对象mut_ref_immut
内容也被失去了,但是引用对象mut_ref_immut
本身还是有效的,这是引用的可变对象的特点。
主程序里需要使用对象mut_ref_immut
的可变性。这是因为每一次调用方法,且同时引用对象mut_ref_immut
的所有权被收回以后,需要重新给可变对象mut_ref_immut
进行赋值。一旦给对象mut_ref_immut
赋值以后,对象mut_ref_immut
就是全新的内容和内存地址,这种对象是传统意义上的变量,变化其量。
无论是对象instance
,还是引用mut_ref_immut
,在这个程序里,它们的生命周期一直到方法main()
。
引用的可变对象与引用的固定对象不同的是,当前者被一个对象收回了所有权时,这仅仅是收回了该对象的内容,而不是其实体。它的实体还是存在的,且可以赋值使其重新有效。尽管该对象没有赋值以前,不能使用它。而后者,一旦收回其所有权以后,其实体本身也无效了。
从下面程序运行结果也可以知道,对象instance
和引用mut_ref_immut
的生命周期。
───────┬──────────────────────────────────────────────────────
│ STDIN
───────┼──────────────────────────────────────────────────────
1 │ instance = 0x7fff5ee45c58
2 │
3 │ before call one() mut_immut_ref = 0x7fa320c03710
4 │ one() input = 0x7fa320c03710
5 │ after call one() mut_immut_ref = 0x7fa320c03710
6 │ instance = "Hello, world"
7 │
8 │ mut_immut_ref = 0x7fa320c03720
9 │ two() input = 0x7fa320c03720
10 │ instance = "Hello, world!"
11 │
12 │ instance = 0x7fff5ee45c58
───────┴──────────────────────────────────────────────────────
绑定可变引用的可变对象
let mut mut_and_mut_ref = &mut mut_instance;
与前面了解到的绑定可变引用的固定对象与绑定固定引用的可变对象实例类似,这种如上Rust语句的对象mut_and_mut_ref
,无论是所引用对象mut_instance
值,还是对象mut_and_mut_ref
本身值都是可变的。
下面是如何使用绑定可变引用的可变对象具体实例:
# #![allow(unused_variables)] #fn main() { // File: ./examples/mut_immut/mut_string.rs // #[cfg(feature = "okey")] fn one(input: &mut String) -> Box<String> { input.push('?'); Box::new(input.to_string()) } fn two(input: &mut String) -> Box<String> { input.push('!'); Box::new(input.to_string()) } let mut mut_instance = String::new(); mut_instance.push_str("Hello"); println!("1. mut_instance = {:?}", mut_instance); let mut mut_ref :&mut String = &mut mut_instance; mut_instance = one(mut_ref).to_string(); println!("2. mut_instance = {:?}", mut_instance); mut_ref = &mut mut_instance; mut_instance = two(mut_ref).to_string(); println!("3. mut_instance = {:?}", mut_instance); #}
在这里之所以使用可变对象属性,是因为在第一次调用方法one()
以后,方法将对象mut_ref
消费掉了,尽管对象mut_ref
本身还是存在的,但其内容已经不在了,要是直接再次调用方法two()
,就会出现错误。这也说明了对象mut_ref
的值是可变的。
要是不存在对象mut_ref
第二次的赋值,就会出现下面借用错误。
在这里之所以使用需要绑定可变引用属性,是因为调用方法的参数是可变引用。方法参数是可变引用,说明该引用将会被修改。从方法功能可以看到,参数是被修改了。
通过上面了解到绑定可变引用的可变对象,不仅对象mut_ref
本身内容是可修改的,即,之后可以将其更改为另一个对象或者其本身的可变引用,而且其值的对象mut_instance
内容也是可修改的,即,之后可以将其更改为任何字符串文字内容。
最后非常简单地阐述类型Box<T>
的作用,类型Box<T>
可以将任何类型T
打包起来,方便其打包类型T
进行传输或者处理等,比如,要是一组不同类型都打包成类型Box<T>
以后,就可以作为一个数组一起处理,另外这是一种引用数据类型。
可以把类型Box<T>
与T
理解为类型String
与str
的类似关系,前者类型String
存在可以大量操作其内容的功能,而后者主要的内容储存功能,两种各自具有不同的功能。当然类型Box<T>
主要的解压功能,而后者是数据结构功能。
题外话
连接字符串的正确方法
# #![allow(unused_variables)] #fn main() { let input = "Hello"; let result :String = [input, "!"].join(""); println!("result = {}", result); let result :String = [input, "!"].concat(); println!("result = {}", result); let result :String = format!("{}{}", input, "?"); println!("result = {}", result); #}
最主要参考资料
参考资料
- string-concatenation-in-rust-is-not-tivial
- how-do-i-str-string
- what-is-the-idiomatic-way-to-convert-str-to-string
- to-string-vs-to-owned-for-string-literals
- return-str-instead-of-stdborrowcow-str
- strategies-for-solving-cannot-move-out-of-borrowing-errors-in-rust
- best-way-to-do-string-concatenation-in-2019-status-quo
- what-is-right-ways-to-concat-strings
- allow-mut-value-not-just-mut-reference
- strategies-for-solving-cannot-move-out-of-borrowing-errors-in-rust
参考资料:题外话
- what-is-right-ways-to-concat-strings
- best-way-to-do-string-concatenation-in-2019-status-quo
- how-do-i-concatenate-strings
应用篋:宏dbg!
与可变实例
学习内容
- 了解和学习使用宏方法
dbg!
的借用实例
篇目
宏dbg!
与借用机制
宏方法dbg!
实现打印并返回给定表达式的值,目的进行快速而简陋的调试。该宏类似于程序输出宏println!
,但是宏方法dbg!
使用简单方便,并且它不是以与程序输出在一起,而是单独输出。所以,当使用在线图书执行它时,我们将看不到其输出结果。
先看一个宏方法dbg!
的简单应用实例:
# #![allow(unused_variables)] #fn main() { // File: ./examples/dbg/mut_macro.rs // #[cfg(feature = "cp")] let string :String = format!("{}", "Hello"); dbg!(std::mem::size_of_val(&string)); dbg!(&string); dbg!(string); #}
其输出结果如下:
官方文档解释宏方法dbg!
如下:
Invoking the macro on an expression moves and takes ownership of it before returning the evaluated expression unchanged. If the type of the expression does not implement Copy and you don't want to give up ownership, you can instead borrow with dbg!(&expr) for some expression expr.
中文翻译:
调用基于表达式
expr
的宏,会转移对象并获取其所有权,然后再返回已求值的表达式不变。如果表达式的类型未实现特质Copy
且不想放弃所有权,则对于一些表达式expr
可以改用这种调用方法dbg!(&expr)
。
下面程序的目的就是来验证上面阐述的第一句话。一旦使用了没有实现特质Copy
的类型String
对象,是否宏方法dbg!
转移该对象,并且调用该宏结束以后,就拿走了该对象的所有权。只要运行一下该程序,就得到了验证。在实际中,都会使用这种dbg!(&expr)
调用方法。
# #![allow(unused_variables)] #fn main() { // File: ./examples/dbg/mut_macro.rs // ANCHOR = "error_01" // error[E0382]: borrow of moved value: `string` let string = format!("{}", "Hello"); dbg!(string); dbg!(&string); #}
可变对象及其固定引用对象
对于可变类型对象,在使用宏方法dbg!
时,可以绑定其固定引用对象,具体实例如下:
# #![allow(unused_variables)] #fn main() { // File: ./examples/dbg/mut_macro.rs // #[cfg(feature = "ok")] let mut string :String = format!("{}", "Hello"); string.push('!'); let ref_string = &string; dbg!(ref_string); dbg!(ref_string); #}
上面程序运行正常,说明固定引用对象ref_string
的借用是正常的。那么我们可否绑定其可变引用对象呢?
可变对象及其可变引用对象
下面程序将回答上面的问题。对于可变类型对象,在使用宏方法dbg!
时,可以绑定其可变引用对象,将会发生什么?具体实例如下:
# #![allow(unused_variables)] #fn main() { // File: ./examples/dbg/mut_macro.rs // ANCHOR = "error_03" // error[E0382]: use of moved value: `ref_mut_string` dbg!(""); let mut string = format!("{}", "Hello"); string.push('!'); let ref_mut_string = &mut string; dbg!(ref_mut_string); dbg!(ref_mut_string); #}
对于可变引用对象,宏方法dbg!
将收回其所有权。但是前面我们看到,对于固定引用对象,宏方法dbg!
则不会收回其所有权。
题外话
创建类型String
对象方法
# #![allow(unused_variables)] #fn main() { let new_string :String = String::new(); let from_string :String = String::from("Hello"); let format_string :String = format!("{}", "Hello"); let to_string :String = "Hello".to_string(); #}
类型String
:方法push
和push_str
在创建类型String
的可变对象时,经常会使用到方法push()
和push_str()
,前者方法参数类型是char
,而后者方法参数类型&str
。
参考资料
- std macro.dbg
- rusts-dbg-macro
- does-rust-have-a-debug-macro
- Generator size: borrowed variables are...
应用篋:循环for语句可变借用实例
学习内容
- 了解和学习循环for语句可变借用实例
篇目
iter() iterates over the items by reference into_iter() iterates over the items, moving them into the new scope iter_mut() iterates over the items, giving a mutable reference to each item
方法into_iter()
与关键词mut
的组合结构
对于具有迭代器Iterator
的对象而言,方法into_iter()
是完全依赖于其对象类型,且遍历其所有的元素本身。下面通过实例再回顾一下这种方法。
如下面程序,对象instance
的类型是Vec<u8>
,这样其元素是原始类型u8
,那么方法就遍历其所有原始类型u8
的元素。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_into_iter.rs // #[cfg(feature = "okay")] let instance = vec![33u8, 42]; for item in instance.into_iter() { //println!("item = {:p}", item); println!("item = {:p}", &item); } #}
其实,上面程序从表面上,方法into_iter()
,既看不能让人看到其遍历的元素类型,也看不能让人知道其已经消费了对象instance
。
对于第一个问题,为了简单快速了解其元素类型,可以使用上面程序注释掉的宏方法println!()
,要是编译没有错误,就说明对象item
是引用类型,否则就是非引用类型。
对于第二个问题,只要在上面程序基础上,在其最后增加一行宏方法println!()
代码,如下所示。只要编译该程序,就会出现借用编译错误。说明方法into_iter()
已经把对象instance
消费掉了。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_iter_mut.rs // #[cfg(feature = "err_09")] let instance = vec![33u8, 42]; for item in instance.into_iter() { //item = 1; println!("item = {:p}", &item); } println!("instance = {:?}", instance); #}
为了使得对象instance
在调用方法into_iter()
以后,且修改其元素,而对象instance
依然存在,需要借用可变对象instance
。
需要指出的是,即使对象instance
是可变的,上面程序的方法元素本身也是不可变对象。
对于方法into_iter()
,要是对象类型是&Vec<u8>
,其元素是引用类型&u8
,那么方法就遍历其所有引用类型&u8
的元素。
下面程序可变对象instance
的类型是&mut Vec<u8>
,其元素是可变引用类型&mut u8
,那么方法就遍历其所有可变引用类型&mut u8
的元素。
下面程序是这里重点解释的结构。三个部分缺一不可:对象instance
本身是可变的;其引用本身也是可变的;使用引用作为方法into_iter()
的对象。这样可以实现对象instance
内容的修改。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_into_iter.rs // #[cfg(feature = "cp")] let mut instance = vec![33u8, 42]; println!("1. instance = {:?}", instance); let ref_instance :&mut Vec<u8> = &mut instance; println!("1. ref_instance = {:?}", ref_instance); for item in ref_instance.into_iter() { *item = 1; } println!("2. ref_instance = {:?}", ref_instance); println!("2. instance = {:?}", instance); #}
上面程序里,引用对象ref_instance
的类型是一种默认情况,即绑定对象ref_instance
时,其类型写与不写是一样的意思。但是下面程序的引用对象ref_instance
的类型可以不是默认的,而是人为绑定的,这是程序第二段代码的第一行代码。当然程序第二段代码的第二行也是一种默认绑定方式。
对象instance
的类型是&mut [u8]
,其元素是引用类型&mut u8
,那么方法就遍历其所有引用类型&mut u8
的元素。这种情况可以把&Vec<u8>
和&[u8]
看作为类似于&String
和&str
。
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_into_iter.rs // #[cfg(feature = "okey")] let mut instance = vec![33u8, 42]; println!("1. instance = {:?}", instance); println!("1. &instance = {:p}", &instance); let ref_instance :&mut [u8] = &mut instance; //let ref_instance = &mut *instance; //let ref_instance :&mut [u8] = &mut *instance; println!("1. ref_instance = {:p}", ref_instance); for item in ref_instance.into_iter() { *item = 1; println!("item = {:p}", item); } println!("2. ref_instance = {:p}", ref_instance); println!("2. instance = {:?}", instance); #}
方法iter_mut()
实例
方法
iter_mut()
= 方法into_iter()
与关键词mut
的组合结构
从上面这行文字可以了解到,接下来说明方法iter_mut()
其实就是上面程序代码的简化。
通过下面程序使用方法iter_mut()
,可以了解到它涉及到下面几方面内容:
- 方法
iter_mut()
遍历其元素都是引用; - 方法
iter_mut()
遍历其元素都是可变引用; - 在调用方法
iter_mut()
以后,对象instance
依然是存在的; - 方法
iter_mut()
隐藏了一个可变借用对象;
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_iter_mut.rs // #[cfg(feature = "ok")] let mut instance = vec![33u8, 42]; println!("1. instance = {:?}", instance); println!("1. &instance = {:p}", &instance); for item in instance.iter_mut() { *item = 1; println!("item = {:p}", item); } println!("2. instance = {:?}", instance); println!("2. &instance = {:p}", &instance); #}
题外话
方法enumerate()
实例
常常希望通过数组的顺序号访问数组。第一个程序说明了这种想法的解决方案:
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_enumerate.rs // #[cfg(feature = "cp")] let mut instance = vec![33u8; 5]; for index in 0..instance.len() { instance[index] = (index as u8) + instance[index]; println!("{:?}", instance[index]); } println!("{:?}", instance); #}
实现这种想法的比较标准方法是使用标准库方法enumerate()
。这个方法不仅遍历了数组的顺序号。而且还遍历其所有元素:
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_enumerate.rs // #[cfg(feature = "okey")] let mut instance = vec![33u8; 5]; for (index, item) in instance.iter_mut().enumerate() { *item = (index as u8) + *item; println!("{:?}", *item); } println!("{:?}", instance); #}
第三种是最广泛使用的实现,它摒弃了循环语句:
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_enumerate.rs // #[cfg(feature = "okay")] let instance: Vec<_> = vec![33u8; 5] .into_iter() .enumerate() .map(|(index, item)| (index as u8) + item) .collect(); println!("{:?}", instance); #}
方法next()
实例
通过下面程序解释使用方法next()
的技巧:
可行结构:可变对象+可变迭代器(iter_mut)+方法
next
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_next.rs // #[cfg(feature = "ok")] let mut instance = vec![33u8; 5]; let mut iter = instance.iter_mut(); loop { match iter.next() { Some(x) => println!("{:?}", x), None => break, } } println!("{:?}", instance); #}
不可行结构:可变对象+迭代器(iter_mut)+方法
next
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_next.rs // #[cfg(feature = "err_07")] let mut instance = vec![33u8; 5]; //let mut iter = instance.iter_mut(); // OK let iter = instance.iter_mut(); loop { match iter.next() { Some(x) => println!("{:?}", x), None => break, } } println!("{:?}", instance); #}
不可行结构:(可变)对象+可变迭代器(into_iter)+方法
next
# #![allow(unused_variables)] #fn main() { // File: ./bin-hello/examples/for_loop/for_next.rs // #[cfg(feature = "err_08")] let instance = vec![33u8; 5]; //let mut iter = IntoIterator::into_iter(instance); let mut iter = instance.into_iter(); loop { match iter.next() { Some(x) => println!("{:?}", x), None => break, } } println!("{:?}", instance); #}
参考资料
- rust-for-loop
- understanding-rust-loops
- fighting-the-borrow-checker-in-a-loop
- rust-by-example/flow_control
- whats-the-difference-between-for-x-in-b-and-for-x-in-b-into-iter
- how-can-i-do-a-mutable-borrow-in-a-for-loop
- pointers-in-rust-a-guide
- rust-ref
- search?q=rust+mutable+borrow+in+loop
- what-is-the-difference-between-iter-and-into-iter
- a-journey-into-iterators
- yarit-yet-another-rust-iterators-tutorial
- effectively-using-iterators-in-rust
# #![allow(unused_variables)] #fn main() { {{ #include ../../../../hello-borrowing/bin-hello/examples/for_loop/mut_string.rs:feature-okey }} #}
题外话:使用工具cargo
Run examples in subfolders using cargo
参考资料
- https://users.rust-lang.org/t/run-examples-in-subfolders-using-cargo/18154/3
软件箧some_exerci
重要参考资料
- https://blog.codeship.com/understanding-rust-loops/
参考资料
- how-to-match-a-string-against-string-literals-in-rust
- rust-quickie-matching-strings
- rust-patterns-ref
- elegant-way-to-borrow-and-return-a-mutable-reference-in-rust
- https://stackoverflow.com/questions/53511591/is-it-possible-to-unborrow-a-mutable-reference-in-rust
关于软件篋some_exerci
栈和堆
栈(stack)和堆(heap)