100个Rust问题
by FlyFlyPeng
说明
这是一个我自己在学习Rust语言中记录的一闪而过不明白想深入探究或者在写代码中经常会去搜索才能获取到答案的一些问题。
这篇文章希望可以给大家当做是一个FAQ来使用,遇到Rust相关的困惑时,直接Ctrl + F
。
希望它比ChatGPT给出的答案更接近你想要得到的标准答案!
1. Rust examples样例程序如何在Cargo.toml文件中添加依赖?
由Cargo Book - Development dependencies可知,对于tests, examples, benchmarks
程序的依赖都统一填写在Cargo.toml
文件中的[dev-dependencies]
小节中。
2. 如何将文件中内容读取到内存的Buffer中,其中文件内容长度不确定?
Rust的std::io::Read
trait提供了对于IO数据流读取的抽象,其中提供了read_to_end()
函数支持将IO数据流读取到一个Vec<u8>
的向量。
read_to_end函数定义如下所示:
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize>
3. Rust 相关学习资料
- Rust By Example:通过一个个简单的例子来学习Rust,非常适合熟悉Rust的语法
- Rust Std标准库文档:Rust std标准库的文档,不知道怎么使用库就可以来这里查查
- Rust Language Cheat Sheet:Rust语法和用法相关的快速检索表
- The Rust Reference:介绍Rust语言的内存模型,并发模型,语法设计这些语言层面的Book
- Rust 程序设计语言 简体中文版:The Rust Programming Language官方书籍的中译版
- rustlings:项目包括一些让你熟悉阅读和编写 Rust 代码的小练习。这包括阅读和响应编译器信息!
- Rust 语言之旅:通过一个一系列的在线交互方式来一步步地引导新手熟悉Rust语言
- OS Tutorial Summer of Code 2020:Rust系统编程入门指导:清华大学rCore团队在os tutorial summer of code 2020项目中给出的Rust快速入门指导
- comprehensive-rust:Google Android团队推出的Rust入门教程
- Rust语言圣经(Rust Course):国内Rust开源爱好者共同编写的一本书,关于书中不理解的部分,也有Comment讨论,个人觉得这一点很有帮助,可以帮助大家解答书中遇到的类似的问题
4. Rust中文件名、函数名、文件目录等命名的代码规范?
5. Rust如何实现数据结构序列化成json格式及其反序列化?
6. 全局的常量如何定义?
7. lazy_static!宏和static静态变量有什么区别?
lazy_static!
是在程序第一次调用时(运行时)才进行初始化static
静态变量/const
静态常量,则是在程序编译阶段由编译器进行初始化,所以static变量只能通过常量表达式或数学表达式
lazy_static!
宏可以用在什么场景?
- 初始化一个全局唯一的对象,并且该对象需要通过调用初始化函数进行构造时
- 创建一个全具的Mutex锁,供程序中不同线程之间实现互斥访问
8. 实现一个结构体相关方法?
impl <type-name> {
fn <func-signature>() {
....
}
}
impl <trait> for <type-name> {
fn <func-define-in-the-trait>() {
.....
}
}
9. anyhow库的作用?
如果有多个错误类型在同一个函数中需要处理时,返回的错误类型必须支持通过From
trait实现将函数中多种错误类型转换成返回的错误类型,例如:
fn foo() -> Result<(), FooError> {
let one: Result<(), OneError> = fn_one();
one?;
let two: Result<(), TwoError> = fn_two();
two?;
Ok(())
}
其中,返回值的FooError
错误类型,必须要实现From<OneError>
和From<TwoError>
,这样才能实现不同错误类型的归一。通常的做法就是将可能出现的错误类型通过enum
枚举类型定义出来,然后实现Display
,Debug
,Error
,From<T>
这几个trait,但是手动写这些代码显得非常枯燥和无聊。
所以,社区有人写了anyhow
crate库,它本质就是定义了一个anyhow::Error
的类型,然后默认实现了From<E> where E: Error
,这样所有实现了Error
trait的所有错误类型都可以转换成anyhow::Error
类型。
另外,如果要构建一个错误类型值返回时,可以通过anyhow!
宏记性构造,如下所示:
return Err(anyhow!("Missing attribute: {}", missing));
【Rust错误处理】使用thiserror
+anyhow
来优雅便捷地处理错误
Crate anyhow
10. warp Web异步处理框架中map,then,and_then中对应的handler闭包函数的参数是如何捕获的?
You may notice that several of these filters extract some tuple, often times a tuple of just 1 item! Why?
If a filter extracts a `(String,)`, that simply means that it extracts a `String`. If you were to `map` the filter, the argument type would be exactly that, just a `String`.
What is it? It’s just some type magic that allows for automatic combining and flattening of tuples. Without it, combining two filters together with `and`, where one extracted `()`, and another `String`, would mean the `map` would be given a single argument of `((), String,)`, which is just no fun.
从Trait warp::Filter
中上面关于Extract类型数据的描述可知,有的filter
在过滤请求的时候,会将请求中的一些数据extract解析出来,然后解析出来的数据作为输入参数,传递给请求处理的handler闭包函数。
例如,warp::filters::body::json
filter函数的定义中,就Extract出http body中的json参数信息:
pub fn json<T: DeserializeOwned + Send>(
) -> impl Filter<Extract = (T,), Error = Rejection> + Copy
而warp::filters::body::content_length_limit
filter函数的定义中,Extract的数据类型为空元组()
,空元组数据类型在后面的请求处理函数的捕获变量中会被忽略掉。
11. 异步编程框架tokio中如何支持RWLock锁?
在WasmEngine源码中,FunctionStore结构体中定义了一个Arc<RwLock<HashMap<String, FunctionEntry>>>
类型的成员,其中的RwLock
是std标准库中的一个std::sync::RwLock
读写锁,但是基于tokio的异步编程框架中,编译代码时rustc编译器却提示如下的错误信息:
future cannot be sent between threads safely
within `impl Future<Output = Result<Opaque(DefId(0:578 ~ wasm_engine[2ad7]::handlers::deploy_function::{opaque#0}::{opaque#0}), []), Rejection>>`, the trait `Send` is not implemented for `std::sync::RwLockWriteGuard<'_, HashMap<std::string::String, FunctionEntry>>`
上面的错误提示信息中说到std::sync::RwLockWriteGuard
没有实现Send
trait,导致无法实现读写锁的所有权跨线程安全传递。
补充:Send
trait是Rust中定义标记型trait(mark trait),它没有定义任何函数行为,它的作用就是如果一个数据类型实现了Send
trait,那么该类型值的所有权可以安全到从一个线程move到另外一个线程中。
std::sync::RwLockWriteGuard
类型的定义如下所示,可以看到它的确没有实现Send
trait,所以就无法实现跨线程的所有权安全移动。
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> !Send for RwLockWriteGuard<'_, T> {}
#[stable(feature = "rwlock_guard_sync", since = "1.23.0")]
unsafe impl<T: ?Sized + Sync> Sync for RwLockWriteGuard<'_, T> {}
但是tokio crate中提供了可以在异步编程框架中使用的tokio::sync::RwLock读写锁,它的功能和接口与std中的std::sync::RwLock
保持一致。
12. tracing_subscriber如何控制不收集底层依赖库输出的trace data信息?
在调试WasmEngine代码的时候,发现如果通过RUST_LOG
环境变量设置日志级别为info
的时候,会将依赖库中的info级别的日志也一起输出到控制台,一直在刷屏,导致WasmEngine中自己添加的日志信息无法看到。
问题的根因:如果是全局的global subscriber,它会将程序及其依赖库所输出的所有event和span事件都捕获并输出出来。
解决方案:解决的方法也非常简单,就是在subscriber
上添加filter过滤器,这里使用了系统默认的EnvFilter::from_default_env()
filter,它会从系统环境变量RUST_LOG
中读取过滤器的配置信息,然后应用到subscriber上。
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.try_init()?;
所以,在运行程序的时候,需要首先进行RUST_LOG
环境变量的赋值,如下所示:
RUST_LOG=wasm_engine=info
📢注意:这里wasm_engine值的表示的含义是需要输出那个crate的日志信息,info表示输出日志的最小级别。 这里有一个坑就是我的代码库的名称是
wasm-engine
,里面包含了一个-
中划线,但是在tracing系统收集数据时,它默认会将wasm-engine
代码库名称中的中划线转换成下划线形式wasm_engine
。
13. Rust中如何在运行时输出变量的类型?
Rust中没有类似C语言中typeof
函数获取变量数据类型的函数,需要通过泛型的方式获取到变量的类型,然后调用std::any::type_name
获取到类型的名称,例子如下:
fn print_type_of<T>(_: T) {
println!("{}", std::any::type_name::<T>())
}
fn main() {
let s = "Hello";
let i = 42;
print_type_of(s); // &str
print_type_of(i); // i32
print_type_of(main); // playground::main
print_type_of(print_type_of::<i32>); // playground::print_type_of<i32>
print_type_of(|| "Hi!" ); // playground::main::
}
14. 如何在函数之间传递字符串?
了解str
, &str
, String
三种不同字符串相关类型在内存中的存储结构之间差异,就可以知道在不同函数之间,传递字符串内容最好的方式就是通过&str
类型(字符串切片引用),这种方式的好处就是字符串的所有权不会被转移。
例子:
fn say_it_loud(msg:&str){
println!("{}!!!",msg.to_string().to_uppercase());
}
fn main() {
// say_it_loud can borrow &'static str as a &str
say_it_loud("你好");
// say_it_loud can also borrow String as a &str
say_it_loud(&String::from("再见"));
}
15. 引用(Reference)和指针(Raw Pointer)的区别是什么?
引用的定义:
引用本质上只是表示内存中某些字节起始位置的数字,它唯一的目的就是标识特定类型的数据存储在何处。
指针的定义:
指针(Raw Pointer)像数字一样,标识特定类型数据在内存中起始位置,它可以不受限制地复制和传递,但是Rust 不保证它指向的内存位置的有效性。
Rust中有两种指针类型:
*const T
:常量指针,该指针所指向的是类型永远是T并不会改变的数据*mut T
:可变指针,该指针所指向的是数据类型是T,但是可以修改这个数据的值
注意:Rust中裸指针的操作需要在unsafe代码块中执行
在Rust中指针类型是用usize
来表示,大小为是4字节。
引用与简单的指针(这里可以理解为就是一个64bit的数字)最重要的区别在于:Rust 的编译器将验证引用自身的生命周期不会超过它指向的内容,否则我们在使用它时会出错!(这解决了C/C++中经常遇到的悬垂指针问题)。
16. 为什么引用变量通过.
运算符访问所引用类型中的字段和方法时需要加上*
解引用符号?
下面是一个常见的通过.
运算符访问引用的字段和方法的例子:
let f = Foo { value: 42 };
let ref_ref_ref_f = &&&f;
println!("{}", ref_ref_ref_f.value);
为什么上面是通过ref_ref_ref_f.value
方式访问value字段,而不是(***ref_ref_ref_f).value
的方式呢,因为.
运算符会自动做一些解引用的操作,最后一行由编译器自动转换为以下内容:
println!("{}", (***ref_ref_ref_f).value);
17. 智能指针常用的一些组合使用方式区别是什么?
Rc<Vec<Foo>>
:允许克隆多个可以借用堆上不可变数据结构的相同vector对象的智能指针Rc<RefCell<Foo>>
:允许多个智能指针通过可变或不可变(需要通过borrow_mut()和borrow()方法实现
)的方式借用堆上的相同Foo类型对象Arc<Mutex<Foo>>
:允许多个智能指针以CPU线程独占方式锁定临时可变/不可变借用的能力
这里需要补充一些关于内部可变性 vs 外部可变性的差异:
- 外部可变性:当我们使用
let mut
显示地声明一个可变的值,或者用&mut
声明一个可变引用时,编译器可以在编译器阶段执行严格的检查,保证只有可变值或者可变的引用,才能修改值的内部数据,这种可变性被称为外部可变性,通过mut
关键字声明。 - 内部可变性:在编译器的角度,值是只读的,但是在运行时,这个值可以得到可变的借用,从而修改内部的数据,这个就是
RefCell
和Mutex
的用武之地。
18. 导入crate内常用的prelude
关键字工作原理是什么?
Rust在其标准库中附带了许多东西。但是,如果每次使用到一个模块就需要手动导入一个,那代码就会显得非常冗长;如果导入库中所有的模块,但是其中一些模块是程序中从未使用到的,这就回导致代码编译时带入许多冗余代码;所以,最好是能够提供一种机制,自动根据程序中使用到的模块信息,自动导入依赖的模块。
prelude
就是Rust提供的根据程序中使用到的模块信息,自动导入所使用模块的列表。
19. Rust项目中如何引用其他项目中非发布到crates.io的crate包?
问题描述:例如,我想在自己的Rust项目中引用到cloud-hypervisor项目中的api_client
这个lib类型的crate,那么在我的项目的Cargo.toml
中应该怎么引用到这个dep依赖包呢?
解决方法:如果crate所在的项目是通过git
工具进行管理的,那么就可以在Cargo.toml
文件中,通过git
作为关键字来来指定crate依赖,例如:
[dependencies] api_client = { git = "https://github.com/cloud-hypervisor/cloud-hypervisor" branch = "main"}
当Cargo工具检测到Cargo.toml
文件中存在通过git
关键字指定依赖crate包时,就会调用git
命令行工具将指定URL的git repo仓库下载到本地。
注意一种特殊情况:**项目依赖的crate是一个大型Rust项目[workspace]中的一个member**,
,而我们只想引用这一个crate,那么这个依赖该怎么写呢?
上面的`api_client`就是这样一个例子,只需要两步:
1. 保持依赖的crate名称和依赖的Rust项目中的**名称一致**
2. `git`关键字后面的链接**只需要填写整个Rust项目的git地址,不必填写到crate所在Cargo.toml的路径**
开发者还可以指定使用crate包所依赖的git repo仓库的分支或特定的commitid,指定分支使用branch
关键字描述,指定commitid使用rev
关键字来描述,例如:
[dependencies] regex = { git = "https://github.com/rust-lang/regex", branch = "next" }
参考资料:Cargo Book - Specifying dependencies from git repositories
20. 在Linux系统中如何管理不同的Rust版本?
查看当前Rust各个版本信息 可以从Rust Versions # 网站获取到当前Rust stable/beta/nightly 版本的情况,以及历史版本信息。
关于stable/beata/nightly版本策略和演进介绍可以参考:Rust 是如何开发的与 “Nightly Rust”
对于开发者我们主要抓住稳定性差异这一点,三者之间的稳定性:stable > beta > nightly
查看当前环境安装了那几个Rust版和工具链:
$ rustup show
Default host: x86_64-unknown-linux-gnu
rustup home: /home/flyflypeng/.rustup
installed toolchains
--------------------
stable-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu
installed targets for active toolchain
--------------------------------------
wasm32-unknown-unknown
wasm32-wasi
x86_64-unknown-linux-gnu
active toolchain
----------------
stable-x86_64-unknown-linux-gnu (default)
rustc 1.68.0 (2c8cc3432 2023-03-06)
安装stable/beta/nightly版本:
$ rustup install stable
$ rustup install beta
$ rustup install nightly
安装特定版本号的Rust版本:
$ rustup install 1.58.0
# 通过rust show 查看当前环境上已经安装的toolchain
$ rustup show
Default host: x86_64-unknown-linux-gnu
rustup home: /home/flyflypeng/.rustup
installed toolchains
--------------------
stable-x86_64-unknown-linux-gnu (default)
beta-x86_64-unknown-linux-gnu
nightly-x86_64-unknown-linux-gnu
1.58.0-x86_64-unknown-linux-gnu // 确实安装成功了
在不同Rust版本之间切换:
# 切换到nightly版本
$ rustup default nightly
# 切换到stable版本
$ rustup default stable
# 切换到指定版本号的Rust
$ rustup default 1.58.0
info: using existing install for '1.58.0-x86_64-unknown-linux-gnu'
info: default toolchain set to '1.58.0-x86_64-unknown-linux-gnu'
1.58.0-x86_64-unknown-linux-gnu unchanged - rustc 1.58.0 (02072b482 2022-01-11)r
# 验证stable或nightly版本是否切换成功,可以通过rustc --verison进行查看
$ rustc --version
rustc 1.58.0 (02072b482 2022-01-11)
21. Rust项目源码目录下rust-toolchain.toml文件的作用是什么?
rust-toolchain.toml
文件的作用是用来设置Rust项目编译的编译工具链,这个配置文件通常用在一些需要使用到nightly
版本中新功能的项目,这样可以指定Rust项目使用特定的Rust编译工具链进行编译1。
22. Rust代码中如何实现条件编译?
Cargo中通过features
提供了一种表达==条件编译和可选依赖关系的机制==。
开发者可以在Cargo.toml
文件中的[features]
项中定义一组命名的特性,每个特性都可以被启用或禁用
默认情况下,所有[features]
section中定义的feature功能都是默认关闭的,只有将需要启用的feature加入到default feature
中,这样才能再编译时include这些feature所覆盖的代码。
有时一个Crate中提供了很多独立的功能,并且通过#[cfg(feature = "xxx")]
方式在代码中实现条件编译,那么我们可以在引入依赖时同时指定需要使用的特定features,这样可以实现==快速编译和减少最终二进制文件的大小。==
开启一个Crate中所有feature功能可以使用下面full
关键词,例如:
tokio = { version = "1", features = ["full"] }
详细的Cargo.toml种的features
特性介绍可以参考:Cargo Book中Features介绍
23. r#<identifier>
这种名称的变量名或结构体成员名如何理解?
r#<identifier>
(Rust Book中称这种命令方式为Raw Identifier)这种方式命令的标识符作为变量名称或结构体成员名称时,主要解决的是identifier标识符名称与Rust系统中的关键字同名的问题,让已经被作为Rust关键字的名称也可以作为源码中的标识符使用。
例如,match
是Rust中模式匹配语法的关键字,如果你将match
作为函数名:
fn match(needle: &str, haystack: &str) -> bool {
haystack.contains(needle)
}
直接编译你会遇到下面的错误提示:
error: expected identifier, found keyword `match`
--> src/main.rs:4:4
|
4 | fn match(needle: &str, haystack: &str) -> bool {
| ^^^^^ expected identifier, found keyword
上面的错误提示说你讲一个Rust中的关键字作为标识符,这是不符合预期的使用方式。
但是如果你非要将match
作为函数名,那么你就需要用上r#<identifier>
语法糖,告诉Rustr#
后面identifier不做任何关键字重名检查,制作identifier命令方式的检查,下面就是正确的用法:
fn r#match(needle: &str, haystack: &str) -> bool {
haystack.contains(needle)
}
fn main() {
assert!(r#match("foo", "foobar"));
}
类似的情况,Rust中还有raw string
的概念,例如你想在Rust中保存一个用字符串常量表示的JSON格式字符串,为了让Rust不对字符串中的的引号做转义处理,你可以用下面的方式来使用:
fn main() {
// 注释掉下面的语句会报下面的错误:
// error: expected one of `.`, `;`, `?`, `else`, or an operator, found `": 2023}"`
// --> src/main.rs:3:27
// |
// | let json_str = "{"year": 2023}";
// | ^^^^^^^^^ expected one of `.`, `;`, `?`, `else`, or an operator
// 正确让字符串中包含双引号的方法
let json_str = "{\"year\": 2023}";
// 或者使用r#: raw string的方式
let new_json_str = r#"{"name": "Peter"}"#;
println!("Hello {}!", json_str);
println!("Happy {}!", new_json_str);
}
23. 引用(Reference)与借用(Borrow)的区别?
从语言中词语的分类来看,引用是一个名词,表示一个对象的别名,可以通过这个别名找到背后实际的对象;而借用则是一个动词,表示向一个对象借用了一些资源(比如对象的所有权,访问全兴等)。
这两者之间的联系是创建一个引用的行为就可以称为是借用,例如下面的代码,我们创建了一个 String类型的对象s,然后创建一个指向该String类型对象s的引用&s
(可以理解为从对象s那里借用了该对象的不可变所有权),最后将这个引用传递给change
函数使用,当使用完毕后,还是需要将借用的不可变所有权还给String类型对象s。
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
24. map函数闭包中传入参数x
和&x
的区别是什么?
函数闭包中的参数
25. supertrait概念
Rust中没有继承的概念,但是开发者可以通过为subtrait定义上层更抽象的supertrait来实现类似面向对象编程中的继承机制。
例子:
trait Person {
fn name(&self) -> String;
}
// Person是Student的supertrait
// 如果某个类型需要实现Student trait, 那么它同时也要实现Person trait
trait Student: Person {
fn university(&self) -> String;
}
trait Programmer {
fn fav_language(&self) -> String;
}
// Student和Programmer是ComputerScienceStudent的直接supertrait
// 而Person则是ComputerScienceStudent的上上层supertrait
trait ComputerScienceStudent: Student + Programmer {
fn study_lessons(&self) -> String;
}
fn comp_sci_student_greeting(student: &dyn ComputerScienceStudent) -> String {
format!(
"My name is {}, and I attend {}. My favorite language is {}. My study lessons is {}",
student.name(),
student.university(),
student.fav_language(),
student.study_lessons(),
)
}
struct Nothing {}
impl Person for Nothing {
fn name(&self) -> String {
"Tom".to_string()
}
}
impl Student for Nothing {
fn university(&self) -> String {
"MIT".to_string()
}
}
impl Programmer for Nothing {
fn fav_language(&self) -> String {
"Rust".to_string()
}
}
impl ComputerScienceStudent for Nothing {
fn study_lessons(&self) -> String {
"Operating System".to_string()
}
}
fn main() {
let t = Nothing {};
println!("{}", comp_sci_student_greeting(&t));
}
26. Rust中如何实现类似C语言中sizeof函数查看变量占用存储空间大小的功能?
在std::mem
crate中有两个sizeof相关的函数:
std::mem::size_of
:返回指定类型所占用的内存空间大小(以byte字节为单位)std::mem::size_of_val
:返回变量所实际指向的值所占用的内存空间大小(以byte字节为单位)。注意下面例子中关于size_of_val(&String)类型和String::len()两个函数返回大小的差异。
例子:
use std::mem;
fn main() {
let s1 = String::from("Hello");
let s = &s1;
let len = cal_string_len(s);
println!("The length of {} is {}", s, len);
// 通过String::len()返回的是heap上实际存储字符串的内存空间大小
// std::mem::size_of_val返回的是String类型变量在栈上存储的大小等于24,就是<ptr, cap, size>三个usize变量大小之和
assert_eq!(s.len(), 5);
assert_eq!(mem::size_of_val(&s), 24);
// 查看String类型和&String引用类型的大小
println!("size_of::<String>(): {}", mem::size_of::<String>());
println!("size_of::<&String>(): {}", mem::size_of::<&String>());
// 查看系统中usize类型的大小
println!("size_of::<usize>(): {}", mem::size_of::<usize>());
}
fn cal_string_len(s: &String) -> usize {
s.len()
}
对于Rust常见的指针、引用、Box<T>
等这些类型的值的大小都是usize。
The types
*const T
,&T
,Box<T>
,Option<&T>
, andOption<Box<T>>
all have the same size. IfT
is Sized, all of those types have the same size asusize
. – std::mem::size_of
下面放一个&String
, String
以及String类型字符串实际在堆内存上存储结构的关系,就更加容易理解上面的例子,以及引用与指针的区别。
27. 如何理解Rust中的自动解引用?
自己主要看了了这篇文文章受启发较大:Rust语言圣经 - Deref 解引用
Rust中编译器可以实现下面两种关于Deref的重要能力:
- 隐式地自动 Deref 解引用转换
- 连续地自动地隐式 Deref 解引用转换
下面的例子中就介绍了这两种隐式的Deref解引用使用场景:
fn display(s: &str) {
println!("{}", s);
}
fn main() {
// case 1: 自动Deref解引用
let s = String::from("Hello");
// 注意:这里传入的&s的数据类型是&String, 而display函数定义的参数类型是&str
// 实现从&String -> &str类型之间的转换的背后,就是Rust编译通过Deref自动解引用帮我们做的
// 也就是在标准库中实现String: Deref<&str>的Deref trait,标准库中的代码如下:
// #[stable(feature = "rust1", since = "1.0.0")]
// impl ops::Deref for String {
// type Target = str;
// #[inline]
// fn deref(&self) -> &str {
// unsafe { str::from_utf8_unchecked(&self.vec) }
// }
// }
display(&s);
// case 2: 连续自动Deref解引用
let b = Box::new(String::from("World"));
// 注意:1. 首先&b是&Box<String>类型,通过它的Deref解引用,可以获取到&String类型引用
// 2. 然后在连续自动的调用&String -> &str的Deref解引用
// #[stable(feature = "rust1", since = "1.0.0")]
// impl<T: ?Sized, A: Allocator> Deref for Box<T, A> {
// type Target = T;
// fn deref(&self) -> &T {
// &**self // 这里为什么要用两个*?因为第一个*是解&Box<String>, 第2个是解Box<String>得到String类型
// }
// }
display(&b);
}
关于Deref
的自动解引用规则总结:
- 一个类型为
T
的对象foo
,如果T: Deref<Target=U>
,那么有关foo
的引用&foo
在使用时可以自动地转换为&U
类型。 - 触发Deref自动解引用的条件就是存在
&T
->&U
引用类型转换,一般函数调用传递引用的场景存在较多的自动解引用转换
引用归一化 在Rust标准库中有一个非常有意思的一段代码:
impl<T: ?Sized> Deref for &T {
type Target = T;
fn deref(&self) -> &T {
*self
}
}
不过这段代码写成下面的方式,更容易理解:
impl<T: ?Sized> Deref for &T {
type Target = T;
fn deref(self: &(&T)) -> &T {
*self
}
}
这段代码所能够实现的内容就是可以实现从&&&&&T -> &T
类型的转换,不断地脱去外面冗长的&
引用符,直到返回最终实际指向的&T
类型的引用。
Subscribe via RSS