pull/89/head
sunface 3 years ago
parent 3905c99634
commit e1f937c3e5

@ -60,7 +60,6 @@ Rust语言圣经是完全开源的电子书籍但是也应该受到版权的
3. 知识链知识B的学习如果需要先学习知识A则A一定在B之前出现 3. 知识链知识B的学习如果需要先学习知识A则A一定在B之前出现
4. 章节命名当用户突然想了解某个知识点时可以很快的定位到它所在的章节例如想了解Arc就应该`多线程 -> Arc`这种章节目录形式 4. 章节命名当用户突然想了解某个知识点时可以很快的定位到它所在的章节例如想了解Arc就应该`多线程 -> Arc`这种章节目录形式
## Rust社区 ## Rust社区
与国外的Rust发展如火如荼相比国内的近况不是特别理想。 与国外的Rust发展如火如荼相比国内的近况不是特别理想。

@ -21,12 +21,12 @@
- [所有权和借用](basic/ownership/index.md) - [所有权和借用](basic/ownership/index.md)
- [所有权](basic/ownership/ownership.md) - [所有权](basic/ownership/ownership.md)
- [引用与借用](basic/ownership/borrowing.md) - [引用与借用](basic/ownership/borrowing.md)
- [字符串](basic/string.md) - [字符串与切片](basic/string-slice.md)
- [复合类型(todo)](basic/compound-type/intro.md) - [复合类型(todo)](basic/compound-type/intro.md)
- [结构体(todo)](basic/compound-type/struct.md) - [结构体(todo)](basic/compound-type/struct.md)
- [枚举](basic/compound-type/enum.md) - [枚举](basic/compound-type/enum.md)
- [元组](basic/compound-type/tuple.md) - [元组](basic/compound-type/tuple.md)
- [数组与切片](basic/compound-type/array.md) - [数组](basic/compound-type/array.md)
- [类型转换](basic/type-converse.md) - [类型转换](basic/type-converse.md)
- [函数与方法(todo)](basic/function-method.md) - [函数与方法(todo)](basic/function-method.md)
- [格式化输出(todo)](basic/formatted-output.md) - [格式化输出(todo)](basic/formatted-output.md)

@ -221,17 +221,6 @@ fn main() {
虽然这种借用错误有的时候会让我们很郁闷但是你只要想想这是Rust提前帮你发现了潜在的bug其实就开心了虽然减慢了开发速度但是从长期来看大幅减少了后续开发和运维成本. 虽然这种借用错误有的时候会让我们很郁闷但是你只要想想这是Rust提前帮你发现了潜在的bug其实就开心了虽然减慢了开发速度但是从长期来看大幅减少了后续开发和运维成本.
总的来说,借用的规则可以总结如下:
1. 同一个作用域,特定数据可以有任意多个不可变借用
2. 同一个作用域,特定数据最多只有一个可变借用
3. 同一个作用域,特定数据不能同时拥有可变和不可变引用
4. 借用在最后一次使用的地方被释放
其实也不用死记硬背,你只要从安全性的角度稍微思考下,就能明白了,例如:有几个人同时在阅读一份在线文档,那么只要有一个人修改了,其它人看到的都会发生改变,这会造成错误的行为,对应上述的借用规则也就是:
1. 如果没人修改,那么再多人观看这份文档都没问题
2. 最多只能有一个人同时修改
3. 如果有一个人能修改,那么其它人不应该在同时看这份文档
### 悬垂引用Dangling References ### 悬垂引用Dangling References
所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。 所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。
@ -302,3 +291,10 @@ fn no_dangle() -> String {
``` ```
这样就没有任何错误了。所有权被移动出去,所以没有值被释放。 这样就没有任何错误了。所有权被移动出去,所以没有值被释放。
## 借用规则总结
总的来说,借用规则如下:
- 同一时刻,你只能拥有要么一个可变引用, 要么任意多个不可变引用
- 引用必须总是有效的

@ -1 +0,0 @@
# string-array-slice.md

@ -0,0 +1,352 @@
# 字符串
在其他语言,字符串往往是送分题,因为实在是太简单了,例如`"hello, world"`就是字符串章节的几乎全部内容了对吧如果你带着这样的想法来学Rust
我保证,绝对会栽跟头,**因此这一章大家一定要重视仔细阅读这里有很多其它Rust书籍中没有的内容**。
首先来看段很简单的代码:
```rust
fn main() {
let my_name = "Pascal";
greet(my_name);
}
fn greet(name: String) {
println!("Hello, {}!", name);
}
```
`greet`函数接受一个字符串类型的`name`参数,然后打印到终端控制台中,非常好理解,你们猜猜,这段代码能否通过编译?
```conole
error[E0308]: mismatched types
--> src/main.rs:3:11
|
3 | greet(my_name);
| ^^^^^^^
| |
| expected struct `std::string::String`, found `&str`
| help: try using a conversion method: `my_name.to_string()`
error: aborting due to previous error
```
Bingo果然报错了编译器提示`greet`函数需要一个`String`类型的字符串,却传入了一个`&str`类型的字符串,相信读者心中现在一定有几头草泥马呼啸而过,怎么字符串也能整出这么多花活?
在讲解字符串之前,先来看看什么是切片?
## 切片(slice)
切片并不是Rust独有的概念在Go语言中就非常流行它允许你引用集合中一段连续的元素序列而不是引用整个集合。
对于字符串而言,切片就是对`String`类型中某一部分的引用,它看起来像这样:
```rust
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
```
`hello`没有引用整个`String s`,而是引用了`s`的一部分内容,通过`[0..5]`的方式来指定。
这就是创建切片的语法,使用方括号包括的一个序列: **[开始索引..终止索引]**,其中开始索引是切片中第一个元素的索引位置,而终止索引是最后一个元素后面的索引位置,也就是这是一个`右半开区间`。在内部,切片数据结构会保存开始的位置和切片的长度,其中长度是通过`终止索引` - `开始索引`的方式计算得来的。
对于`let world = &s[6..11];`来说,`world`是一个切片,该切片的指针指向`s`的第7个字节(索引从0开始,6是第7个字节),且该切片的长度是`5`个字节。
<img alt="" src="/img/string-01.svg" class="center" style="width: 50%;" />
<span class="caption">String切片引用了另一个`String`的一部分</span>
在使用Rust的`..`区间(range)语法时如果你想从索引0开始可以使用如下的方式这两个是等效的
```rust
let s = String::from("hello");
let slice = &s[0..2];
let slice = &s[..2];
```
同样的,如果你的切片想要包含`String`的最后一个字节,则可以这样使用:
```rust
let s = String::from("hello");
let len = s.len();
let slice = &s[3..len];
let slice = &s[3..];
```
你也可以截取完整的`String`切片:
```rust
let s = String::from("hello");
let len = s.len();
let slice = &s[0..len];
let slice = &s[..];
```
>在对字符串使用切片语法时需要格外小心切片的索引必须落在字符之间的边界位置也就是UTF8字符的边界例如中文在UT8中占用三个字节,下面的代码就会崩溃:
>```rust
> let s = "中国人";
> let a = &s[0..2];
> println!("{}",a);
>```
>因为我们只取`s`字符串的前两个字节,但是一个中文占用三个字节,因此没有落在边界处,也就是连`中`字都取不完整,此时程序会直接崩溃退出,如果改成`&a[0..3]`,则可以正常通过编译.
> 因此,当你需要对字符串做切片索引操作时,需要格外小心这一点, 关于该如何操作utf8字符串参见[这里](#操作UTF8字符串)
字符串切片的类型标示是`&str`,因此我们可以这样申明一个函数,输入`String`类型,返回它的切片: `fn first_word(s: &String) -> &str `.
有了切片就可以写出这样的安全代码:
```rust
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // error!
println!("the first word is: {}", word);
}
```
编译器报错如下:
```console
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:18:5
|
16 | let word = first_word(&s);
| -- immutable borrow occurs here
17 |
18 | s.clear(); // error!
| ^^^^^^^^^ mutable borrow occurs here
19 |
20 | println!("the first word is: {}", word);
| ---- immutable borrow later used here
```
回忆一下借用的规则:当我们已经有了可变借用时,就无法再拥有不可变的借用。因为`clear`需要清空改变`String`,因此它需要一个可变借用,而之后的`println!`又使用了不可变借用,因此编译无法通过。
从上述代码可以看出Rust不仅让我们的`api`更加容易使用,而且也在编译器就位我们消除了大量错误!
#### 其它切片
因为切片是对集合的部分引用,因此不仅仅字符串有切片,其它集合类型也有,例如数组:
```rust
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);
```
该数组切片的类型是`&[i32]`,数组切片和字符串切片的工作方式是一样的,例如持有一个引用指向原始数组的某个元素和长度。对于集合类型,我们在[这一章](../advance/collection.md)中有详细的介绍。
## 字符串字面量是切片
之前提到过字符串字面量,但是没有提到它的类型:
```rust
let s = "Hello, world!";
```
实际上,`s`的类型时`&str`,因此你也可以这样声明:
```rust
let s: &str = "Hello, world!";
```
该切片指向了程序可执行文件中的某个点,这也是为什么字符串字面量是不可变的,因为`&str`时一个不可变引用。
了解完切片,可以进入本节的正题了。
## 什么是字符串?
顾名思义,字符串是由字符组成的连续集合,但是在上一节中我们提到过,**Rust中的字符是Unicode类型因此每个字符占据4个字节内存空间但是在字符串中不一样字符串是UTF8编码也就是字符所占的字节数是变长的(1-4)**,这样有助于大幅降低字符串所占用的内存空间.
Rust在语言级别只有一种字符串类型`str`,它通常是以引用类型出现`&str`,也就是上文提到的字符串切片。虽然语言级别只有上述的`str`类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是`String`类型。
`str`类型是硬编码进可执行文件,也无法被修改,但是`String`则是一个可增长、可改变且具有所有权的UTF8编码字符串**当Rust用户提到字符串时往往指的就是`String`类型和`&str`字符串切片类型这两个类型都是UTF8编码**.
除了`String`类型的字符串Rust的标准库还提供了其他类型的字符串例如`OsString`,`OsStr`,`CsString`和`CsStr`等,注意到这些名字都以`String`或者`Str`结尾了吗?它们分别对应的是具有所有权和被借用的变量。
#### 操作字符串
由于String是可变字符串因此我们可以对它进行创建、增删操作下面的代码汇总了相关的操作方式
```rust
fn main() {
// 创建一个空String
let mut s = String::new();
// 将&str类型的"hello,world"添加到中
s.push_str("hello,world");
// 将字符'!'推入s中
s.push('!');
// 最后s的内容是"hello,world!"
assert_eq!(s,"hello,world!");
// 从现有的&str切片创建String类型
let mut s = "hello,world".to_string();
// 将字符'!'推入s中
s.push('!');
// 最后s的内容是"hello,world!"
assert_eq!(s,"hello,world!");
// 从现有的&str切片创建String类型
// String与&str都是UTF8编码因此支持中文
let mut s = String::from("你好,世界");
// 将字符'!'推入s中
s.push('!');
// 最后s的内容是"hello,world!"
assert_eq!(s,"你好,世界!");
let s1 = String::from("Hello,");
let s2 = String::from("world!");
// 在下句中s1的所有权被转移走了因此后面不能再使用s1
let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used
assert_eq!(s3,"hello,world!");
// 下面的语句如果去掉注释,就会报错
// println!("{}",s1);
}
```
在上面代码中,有一处需要解释的地方,就是使用`+`来对字符串进行相加操作, 这里之所以使用`s1 + &s2`的形式,是因为`+`使用了`add`方法,该方法的定义类似:
```rust
fn add(self, s: &str) -> String {
```
因为该方法涉及到更复杂的特征功能,因此我们这里简单说明下,`self`是`String`类型的字符串`s1`,该函数说明,只能将&str类型的字符串切片添加到String类型的`s1`上,然后返回一个新的`String`类型,所以`let s3 = s1 + &s2;`就很好解释了,将`String`类型的`s1`与`&str`类型的`s2`进行相加,最终得到`String`类型的s3.
由此可推,以下代码也是合法的:
```rust
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
// String = String + &str + &str + &str + &str
let s = s1 + "-" + &s2 + "-" + &s3;
```
`String` + `&str`返回一个`String`,然后再继续跟一个`&str`进行`+`操作,返回一个`String`类型,不断循环,最终生成一个`s`,也是`String`类型。
在上面代码中,我们做了一个有些难以理解的`&String`操作,下面来展开讲讲。
## String与&str的转换
在之前的代码中,已经见到好几种从`&str`类型生成`String`类型的操作:
- `String::from("hello,world")`
- `"hello,world".to_string()`
那么如何将`String`类型转为`&str`类型呢?答案很简单,取引用即可:
```rust
fn main() {
let s = String::from("hello,world!");
say_hello(&s);
say_hello(&s[..]);
say_hello(s.as_str());
}
fn say_hello(s: &str) {
println!("{}",s);
}
```
实际上这种灵活用法是因为`deref`强制转换,具体我们会在[Deref特征](../traits/deref.md)进行详细讲解。
## 字符串索引
在其它语言中使用索引的方式访问字符串的某个字符或者子串是很正常的行为但是在Rust中就会报错:
```rust
let s1 = String::from("hello");
let h = s1[0];
```
该代码会产生如下错误:
```console
3 | let h = s1[0];
| ^^^^^ `String` cannot be indexed by `{integer}`
|
= help: the trait `Index<{integer}>` is not implemented for `String`
```
https://rustwiki.org/en/book/ch08-02-strings.html#storing-utf-8-encoded-text-with-strings
#### 深入字符串内部
字符串的底层的数据存储格式实际上是[u8],一个字节数组。对于`let hello = String::from("Hola");`这行代码来说,`hello`的长度是`4`个字节,因为`"hola"`中的每个字母在UTF8编码中仅占用1个字节但是对于下面的代码呢?
```rust
let hello = String::from("中国人");
```
如果问你该字符串多长,你可能会说`3`,但是实际上是`9`个字节的长度因为每个汉字在UTF8中的长度是`3`个字节,因此这种情况下对`hello`进行索引
访问`&hello[0]`没有任何意义,因为你取不到`中`这个字符,而是取到了这个字符三个字节中的第一个字节,这是一个非常奇怪而且难以理解的返回值。
#### 字符串的不同表现形式
现在看一下用梵文写的字符串`“नमस्ते”`, 它底层的字节数组如下形式:
```rust
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135]
```
长度是18个字节这也是计算机最终存储该字符串的形式。如果从字符的形式去看则是
```rust
['न', 'म', 'स', '्', 'त', 'े']
```
但是这种形势下,第四和六两个字母根本就不存在,没有任何意义,接着再从字母串的形式去看:
```rust
["न", "म", "स्", "ते"]
```
所以可以看出来Rust提供了不同的字符串展现方式这样程序可以挑选自己想要的方式去使用而无需去管字符串从人类语言角度看长什么样。
还有一个原因导致了Rust不允许去索引字符因为索引操作我们总是期望它的性能表现是O(1),然后对于`String`类型来说无法保证这一点因为Rust可能需要从0开始去遍历字符串来定位合法的字符。
## 字符串切片
前文提到过字符串切片是非常危险的操作因为切片的索引是通过字节来进行但是字符串是UTF8编码因此你无法保证索引的字节刚好落在字符的边界上例如
```rust
let hello = "中国人";
let s = &hello[0..2];
```
运行上面的程序,会直接造成崩溃:
```console
thread 'main' panicked at 'byte index 2 is not a char boundary; it is inside '中' (bytes 0..3) of `中国人`', src/main.rs:4:14
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
这里提示的很清楚,我们索引的字节落在了`中`字符的内部,这种返回没有任何意义。
因此在通过索引区间来访问字符串时,需要格外的小心,一不注意,就会导致你程序的崩溃!
## 操作UTF8字符串
前文提到了几中使用UTF8字符串的方式下面来一一说明。
#### 字符
如果你想要以Unicode字符的方式遍历字符串最好的办法是使用`chars`方法,例如:
```rust
for c in "中国人".chars() {
println!("{}", c);
}
```
输出如下
```console
```
#### 字节
这种方式是返回字符串的底层字节数组表现形式:
```rust
for b in "中国人".bytes() {
println!("{}", b);
}
```
输出如下:
```console
228
184
173
229
155
189
228
186
186
```
#### 获取子串
想要准确的从UTF8字符串中获取子串是较为复杂的事情例如想要从`holla中国人नमस्ते`这种变长的字符串中取出某一个子串,使用标准库你是做不到的,
你需要在`crates.io`上搜索`utf8`来寻找想要的功能。
可以考虑尝试下这个库:[utf8 slice](https://crates.io/crates/utf8_slice).
## String底层剖析
@todo

@ -1,49 +0,0 @@
# 字符串
在其他语言,字符串往往是送分题,因为实在是太简单了,例如`"hello, world"`就是字符串章节的几乎全部内容了对吧如果你带着这样的想法来学Rust
我保证,绝对会栽跟头,**因此这一章大家一定要重视仔细阅读这里有很多其它Rust书籍中没有的内容**。
首先来看段很简单的代码:
```rust
fn main() {
let my_name = "Pascal";
greet(my_name);
}
fn greet(name: String) {
println!("Hello, {}!", name);
}
```
`greet`函数接受一个字符串类型的`name`参数,然后打印到终端控制台中,非常好理解,你们猜猜,这段代码能否通过编译?
```conole
error[E0308]: mismatched types
--> src/main.rs:3:11
|
3 | greet(my_name);
| ^^^^^^^
| |
| expected struct `std::string::String`, found `&str`
| help: try using a conversion method: `my_name.to_string()`
error: aborting due to previous error
```
Bingo果然报错了编译器提示`greet`函数需要一个`String`类型的字符串,却传入了一个`&str`类型的字符串,相信读者心中现在一定有几头草泥马呼啸而过,怎么字符串也能整出这么多花活?
接下来,让我们逐点分析讲解。
## 什么是字符串?
顾名思义,字符串是由字符组成的连续集合,但是在上一节中我们提到过,**Rust中的字符是Unicode类型因此每个字符占据4个字节内存空间但是在字符串中不一样字符串是UTF8编码也就是字符所占的字节数是变长的(2-4)**,这样有助于大幅降低字符串所占用的内存空间.
Rust在语言级别只有一中字符串类型`str`,它通常是以引用类型(更准确的说法是[借用](../../core/borrowing.md),这个概念在后面会讲)出现`&str`
## String底层剖析
https://rustwiki.org/zh-CN/book/ch04-01-what-is-ownership.html#变量与数据交互的方式一移动
> 为何`String`长度和容量会不一致?
> 之前提到过`String`是一个可修改、可增长的字符串,因此它是可变的,但是不可能在每次改变,我们都重新生成一次堆上的内存空间,这种成本太高了,因此

@ -1,6 +1,9 @@
# 会导致panic的代码 # 会导致panic的代码
String slice range indices must occur at valid UTF-8 character boundaries. If you attempt to create a string slice in the middle of a multibyte character, your program will exit with an error. For the purposes of introducing string slices, we are assuming ASCII only in this section; a more thorough discussion of UTF-8 handling is in the “Storing UTF-8 Encoded Text with Strings” section of Chapter 8.
> >
> 比方说有一个 `u8` ,它可以存放从 0 到 255 的值。那么当你将其修改为范围之外的值,比如 256则会发生**整型溢出**。关于这一行为 Rust 有一些有趣的规则。当在 debug 模式编译时Rust 会检查整型溢出若存在这些问题则使程序在编译时 *panic*。Rust 使用这个术语来表明程序因错误而退出。 [该章节](../../errors/panic.md)会详细介绍 panic。 > 比方说有一个 `u8` ,它可以存放从 0 到 255 的值。那么当你将其修改为范围之外的值,比如 256则会发生**整型溢出**。关于这一行为 Rust 有一些有趣的规则。当在 debug 模式编译时Rust 会检查整型溢出若存在这些问题则使程序在编译时 *panic*。Rust 使用这个术语来表明程序因错误而退出。 [该章节](../../errors/panic.md)会详细介绍 panic。
> >

BIN
src/img/.DS_Store vendored

Binary file not shown.

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
-->
<!-- Title: %3 Pages: 1 -->
<svg
viewBox="0.00 0.00 1000.00 1279.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(4.1667 4.1667) rotate(0) translate(4 275)">
<title>%3</title>
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-275 233,-275 233,4 -4,4"/>
<!-- table0 -->
<g id="node1" class="node">
<title>table0</title>
<polyline fill="none" stroke="#000000" points="16,-121 88,-121 "/>
<text text-anchor="start" x="35.6689" y="-126.8" font-family="Times,serif" font-size="14.00" fill="#000000">world</text>
<polygon fill="none" stroke="#000000" points="16,-101 16,-121 52,-121 52,-101 16,-101"/>
<text text-anchor="start" x="18.8413" y="-106.8" font-family="Times,serif" font-size="14.00" fill="#000000">name</text>
<polygon fill="none" stroke="#000000" points="52,-101 52,-121 88,-121 88,-101 52,-101"/>
<text text-anchor="start" x="54.8413" y="-106.8" font-family="Times,serif" font-size="14.00" fill="#000000">value</text>
<polygon fill="none" stroke="#000000" points="16,-81 16,-101 52,-101 52,-81 16,-81"/>
<text text-anchor="start" x="26.2241" y="-86.8" font-family="Times,serif" font-size="14.00" fill="#000000">ptr</text>
<polygon fill="none" stroke="#000000" points="52,-81 52,-101 88,-101 88,-81 52,-81"/>
<polygon fill="none" stroke="#000000" points="16,-61 16,-81 52,-81 52,-61 16,-61"/>
<text text-anchor="start" x="25.4482" y="-66.8" font-family="Times,serif" font-size="14.00" fill="#000000">len</text>
<polygon fill="none" stroke="#000000" points="52,-61 52,-81 88,-81 88,-61 52,-61"/>
<text text-anchor="start" x="66.5" y="-66.8" font-family="Times,serif" font-size="14.00" fill="#000000">5</text>
</g>
<!-- table4 -->
<g id="node3" class="node">
<title>table4</title>
<polygon fill="none" stroke="#000000" points="148.5,-224 148.5,-244 185.5,-244 185.5,-224 148.5,-224"/>
<text text-anchor="start" x="151.4482" y="-229.8" font-family="Times,serif" font-size="14.00" fill="#000000">index</text>
<polygon fill="none" stroke="#000000" points="185.5,-224 185.5,-244 221.5,-244 221.5,-224 185.5,-224"/>
<text text-anchor="start" x="188.3413" y="-229.8" font-family="Times,serif" font-size="14.00" fill="#000000">value</text>
<polygon fill="none" stroke="#000000" points="148.5,-204 148.5,-224 185.5,-224 185.5,-204 148.5,-204"/>
<text text-anchor="start" x="163.5" y="-209.8" font-family="Times,serif" font-size="14.00" fill="#000000">0</text>
<polygon fill="none" stroke="#000000" points="185.5,-204 185.5,-224 221.5,-224 221.5,-204 185.5,-204"/>
<text text-anchor="start" x="200" y="-209.8" font-family="Times,serif" font-size="14.00" fill="#000000">h</text>
<polygon fill="none" stroke="#000000" points="148.5,-184 148.5,-204 185.5,-204 185.5,-184 148.5,-184"/>
<text text-anchor="start" x="163.5" y="-189.8" font-family="Times,serif" font-size="14.00" fill="#000000">1</text>
<polygon fill="none" stroke="#000000" points="185.5,-184 185.5,-204 221.5,-204 221.5,-184 185.5,-184"/>
<text text-anchor="start" x="200.3931" y="-189.8" font-family="Times,serif" font-size="14.00" fill="#000000">e</text>
<polygon fill="none" stroke="#000000" points="148.5,-164 148.5,-184 185.5,-184 185.5,-164 148.5,-164"/>
<text text-anchor="start" x="163.5" y="-169.8" font-family="Times,serif" font-size="14.00" fill="#000000">2</text>
<polygon fill="none" stroke="#000000" points="185.5,-164 185.5,-184 221.5,-184 221.5,-164 185.5,-164"/>
<text text-anchor="start" x="201.5552" y="-169.8" font-family="Times,serif" font-size="14.00" fill="#000000">l</text>
<polygon fill="none" stroke="#000000" points="148.5,-144 148.5,-164 185.5,-164 185.5,-144 148.5,-144"/>
<text text-anchor="start" x="163.5" y="-149.8" font-family="Times,serif" font-size="14.00" fill="#000000">3</text>
<polygon fill="none" stroke="#000000" points="185.5,-144 185.5,-164 221.5,-164 221.5,-144 185.5,-144"/>
<text text-anchor="start" x="201.5552" y="-149.8" font-family="Times,serif" font-size="14.00" fill="#000000">l</text>
<polygon fill="none" stroke="#000000" points="148.5,-124 148.5,-144 185.5,-144 185.5,-124 148.5,-124"/>
<text text-anchor="start" x="163.5" y="-129.8" font-family="Times,serif" font-size="14.00" fill="#000000">4</text>
<polygon fill="none" stroke="#000000" points="185.5,-124 185.5,-144 221.5,-144 221.5,-124 185.5,-124"/>
<text text-anchor="start" x="200" y="-129.8" font-family="Times,serif" font-size="14.00" fill="#000000">o</text>
<polygon fill="none" stroke="#000000" points="148.5,-104 148.5,-124 185.5,-124 185.5,-104 148.5,-104"/>
<text text-anchor="start" x="163.5" y="-109.8" font-family="Times,serif" font-size="14.00" fill="#000000">5</text>
<polygon fill="none" stroke="#000000" points="185.5,-104 185.5,-124 221.5,-124 221.5,-104 185.5,-104"/>
<text text-anchor="start" x="201.75" y="-109.8" font-family="Times,serif" font-size="14.00" fill="#000000"> </text>
<polygon fill="none" stroke="#000000" points="148.5,-84 148.5,-104 185.5,-104 185.5,-84 148.5,-84"/>
<text text-anchor="start" x="163.5" y="-89.8" font-family="Times,serif" font-size="14.00" fill="#000000">6</text>
<polygon fill="none" stroke="#000000" points="185.5,-84 185.5,-104 221.5,-104 221.5,-84 185.5,-84"/>
<text text-anchor="start" x="198.4448" y="-89.8" font-family="Times,serif" font-size="14.00" fill="#000000">w</text>
<polygon fill="none" stroke="#000000" points="148.5,-64 148.5,-84 185.5,-84 185.5,-64 148.5,-64"/>
<text text-anchor="start" x="163.5" y="-69.8" font-family="Times,serif" font-size="14.00" fill="#000000">7</text>
<polygon fill="none" stroke="#000000" points="185.5,-64 185.5,-84 221.5,-84 221.5,-64 185.5,-64"/>
<text text-anchor="start" x="200" y="-69.8" font-family="Times,serif" font-size="14.00" fill="#000000">o</text>
<polygon fill="none" stroke="#000000" points="148.5,-44 148.5,-64 185.5,-64 185.5,-44 148.5,-44"/>
<text text-anchor="start" x="163.5" y="-49.8" font-family="Times,serif" font-size="14.00" fill="#000000">8</text>
<polygon fill="none" stroke="#000000" points="185.5,-44 185.5,-64 221.5,-64 221.5,-44 185.5,-44"/>
<text text-anchor="start" x="201.1689" y="-49.8" font-family="Times,serif" font-size="14.00" fill="#000000">r</text>
<polygon fill="none" stroke="#000000" points="148.5,-24 148.5,-44 185.5,-44 185.5,-24 148.5,-24"/>
<text text-anchor="start" x="163.5" y="-29.8" font-family="Times,serif" font-size="14.00" fill="#000000">9</text>
<polygon fill="none" stroke="#000000" points="185.5,-24 185.5,-44 221.5,-44 221.5,-24 185.5,-24"/>
<text text-anchor="start" x="201.5552" y="-29.8" font-family="Times,serif" font-size="14.00" fill="#000000">l</text>
<polygon fill="none" stroke="#000000" points="148.5,-4 148.5,-24 185.5,-24 185.5,-4 148.5,-4"/>
<text text-anchor="start" x="160" y="-9.8" font-family="Times,serif" font-size="14.00" fill="#000000">10</text>
<polygon fill="none" stroke="#000000" points="185.5,-4 185.5,-24 221.5,-24 221.5,-4 185.5,-4"/>
<text text-anchor="start" x="200" y="-9.8" font-family="Times,serif" font-size="14.00" fill="#000000">d</text>
</g>
<!-- table0&#45;&gt;table4 -->
<g id="edge1" class="edge">
<title>table0:c&#45;&gt;table4:pointee2</title>
<path fill="none" stroke="#000000" d="M70,-91C70,-91 105.7964,-93.4639 138.4948,-93.9258"/>
<polygon fill="#000000" stroke="#000000" points="138.4743,-97.4257 148.5,-94 138.5263,-90.4259 138.4743,-97.4257"/>
</g>
<!-- table3 -->
<g id="node2" class="node">
<title>table3</title>
<polyline fill="none" stroke="#000000" points="8,-247 96,-247 "/>
<text text-anchor="start" x="49.2759" y="-252.8" font-family="Times,serif" font-size="14.00" fill="#000000">s</text>
<polygon fill="none" stroke="#000000" points="8,-227 8,-247 60,-247 60,-227 8,-227"/>
<text text-anchor="start" x="18.8413" y="-232.8" font-family="Times,serif" font-size="14.00" fill="#000000">name</text>
<polygon fill="none" stroke="#000000" points="60,-227 60,-247 96,-247 96,-227 60,-227"/>
<text text-anchor="start" x="62.8413" y="-232.8" font-family="Times,serif" font-size="14.00" fill="#000000">value</text>
<polygon fill="none" stroke="#000000" points="8,-207 8,-227 60,-227 60,-207 8,-207"/>
<text text-anchor="start" x="26.2241" y="-212.8" font-family="Times,serif" font-size="14.00" fill="#000000">ptr</text>
<polygon fill="none" stroke="#000000" points="60,-207 60,-227 96,-227 96,-207 60,-207"/>
<polygon fill="none" stroke="#000000" points="8,-187 8,-207 60,-207 60,-187 8,-187"/>
<text text-anchor="start" x="25.4482" y="-192.8" font-family="Times,serif" font-size="14.00" fill="#000000">len</text>
<polygon fill="none" stroke="#000000" points="60,-187 60,-207 96,-207 96,-187 60,-187"/>
<text text-anchor="start" x="71.2563" y="-192.8" font-family="Times,serif" font-size="14.00" fill="#000000">11</text>
<polygon fill="none" stroke="#000000" points="8,-167 8,-187 60,-187 60,-167 8,-167"/>
<text text-anchor="start" x="10.6826" y="-172.8" font-family="Times,serif" font-size="14.00" fill="#000000">capacity</text>
<polygon fill="none" stroke="#000000" points="60,-167 60,-187 96,-187 96,-167 60,-167"/>
<text text-anchor="start" x="71.2563" y="-172.8" font-family="Times,serif" font-size="14.00" fill="#000000">11</text>
</g>
<!-- table3&#45;&gt;table4 -->
<g id="edge2" class="edge">
<title>table3:c&#45;&gt;table4:pointee</title>
<path fill="none" stroke="#000000" d="M78,-217C78,-217 109.3179,-214.5994 138.3725,-214.0931"/>
<polygon fill="#000000" stroke="#000000" points="138.5326,-217.5918 148.5,-214 138.4682,-210.5921 138.5326,-217.5918"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.4 KiB

@ -1 +1,89 @@
# Deref和引用隐式转换 # Deref和引用隐式转换
`Deref` 是解引用操作符`*`的特征,比如 *v。
一般理解,`*v`操作,是`&v`的反向操作,是为了获取`&v`指针指向的堆上对象。
## 强制隐式转换
Deref最神奇、最好用的地方并不在本身`解引`这个意义上Rust的设计者在它之上附加了一个特性强制隐式转换这才是它神奇之处。
这种隐式转换的规则为:
一个类型为`T`的对象`foo`如果T: Deref<Target=U>,那么,相关`foo`的引用`&foo`在应用的时候会自动转换`&U`。
粗看这条规则,貌似有点类似于`AsRef`,而跟`解引`似乎风马牛不相及, 实际里面里面有些玄妙之处。
Rust编译器会在做`*v`操作的时候,自动先把`v`做引用归一化操作,即转换成内部通用引用的形式`&v`,整个表达式就变成 `*&v`。这里面有两种情况:
1. 把智能指针比如在库中定义的Box, Rc, Arc, Cow 等),去掉壳,转成内部标准形式`&v`
2. 把多重`&` (比如:`&&&&&&&v`),简化成`&v`(通过插入足够数量的`*`进行解引)。
所以,它实际上在解引用之前做了一个引用的归一化操作。
为什么要转呢? 因为编译器设计的能力是,只能够对 &v 这种引用进行解引用。其它形式的它不认识,所以要做引用归一化操作。
使用引用进行过渡也是为了能够防止不必要的拷贝。
下面举一些例子:
```rust
fn foo(s: &str) {
// borrow a string for a second
}
// String implements Deref<Target=str>
let owned = "Hello".to_string();
// therefore, this works:
foo(&owned);
```
因为`String`实现了`Deref<Target=str>`。
```rust
use std::rc::Rc;
fn foo(s: &str) {
// borrow a string for a second
}
// String implements Deref<Target=str>
let owned = "Hello".to_string();
let counted = Rc::new(owned);
// therefore, this works:
foo(&counted);
```
因为`Rc<T>`实现了`Deref<Target=T>`。
```rust
fn foo(s: &[i32]) {
// borrow a slice for a second
}
// Vec<T> implements Deref<Target=[T]>
let owned = vec![1, 2, 3];
foo(&owned);
```
因为`Vec<T>` 实现了`Deref<Target=[T]>`。
```rust
struct Foo;
impl Foo {
fn foo(&self) { println!("Foo"); }
}
let f = &&Foo;
f.foo();
(&f).foo();
(&&f).foo();
(&&&&&&&&f).foo();
```
上面那几种函数的调用,效果是一样的。
这种`Deref`涉及的隐式转换实际上是Rust中仅有的类型隐式转换设计它的目的是为了简化程序的书写让代码不至于过于繁琐。把人从无尽的类型细节中解脱出来让书写 Rust 代码变成一件快乐的事情。
Loading…
Cancel
Save