# 速成课第6节

到目前为止，我们已经介绍了生命周期和值序列话的一些细节。 现在是时候深入研究并正确了解生命周期和切片了。

这篇文章是基于 [FP](https://www.fpcomplete.com/rust) 完成 Rust 教学系列的一部分。 如果你在博客之外阅读这篇文章，你可以在[介绍文章的顶部](https://www.snoyman.com/blog/2018/10/introducing-rust-crash-course)找到这个系列中所有文章的链接。 也可[订阅 RSS](https://www.snoyman.com/feed/rust-crash-course) 频道。

## 打印 person

让我们来看看一些相当平常的代码：

```rust
#[derive(Debug)]
struct Person {
    name: Option<String>,
    age: Option<u32>,
}

fn print_person(person: Person) {
    match person.name {
        Some(name) => println!("Name is {}", name),
        None => println!("No name provided"),
    }

    match person.age {
        Some(age) => println!("Age is {}", age),
        None => println!("No age provided"),
    }
}

fn main() {
    print_person(Person {
        name: Some(String::from("Alice")),
        age: Some(30),
    });
}
```

相当简单，这是一个模式匹配的很好的示范。 但是，让我们再加上一行。 尝试在 print\_person 函数的开头添加以下内容：

```rust
println!("Full Person value: {:?}", person);
```

一切正常。 先打印 person 的全部内容，然后是模式匹配。 但是尝试在函数末尾添加上这行，你会得到一个编译错误：

```rust
error[E0382]: use of partially moved value: `person`
  --> main.rs:18:41
   |
9  |         Some(name) => println!("Name is {}", name),
   |              ---- value moved here
...
18 |     println!("Full Person value: {:?}", person);
   |                                         ^^^^^^ value used here after move
   |
   = note: move occurs because the value has type `std::string::String`, which does not implement the `Copy` trait

error: aborting due to previous error
```

> 注意：这是我使用的Rust编译器1.30.1的错误。 但是，已经有计划来改善这种情况。

问题在于我们已经消耗了 person 的一部分，因此无法打印它。 我们可以通过再次设置来解决此问题。 让 person 参数可变，然后使用默认的None 值填充移动的 person.name：

```rust
fn print_person(mut person: Person) {
    match person.name {
        Some(name) => println!("Name is {}", name),
        None => println!("No name provided"),
    }

    match person.age {
        Some(age) => println!("Age is {}", age),
        None => println!("No age provided"),
    }

    person.name = None;

    println!("Full Person value: {:?}", person);
}
```

编译运行，但现在输出结果令人困惑：

```rust
Name is Alice
Age is 30
Full Person value: Person { name: None, age: Some(30) }
```

注意最后一行中name的值是None，理想情况下应该是 Some (Alice)。 我们可以做得更好，通过返回 match 中的 name：

```rust
person.name = match person.name {
    Some(name) => {
        println!("Name is {}", name);
        Some(name)
    },
    None => {
        println!("No name provided");
        None
    }
};
```

但是这样做是不雅的。 让我们后退一步。 我们真的需要 消费/move person.name  吗？ 也不尽然。 它应该按照引用来做所有的事情。 因此，让我们回过头来，通过使用一个借词来完全避免这种移动：

```rust
match &person.name {
    Some(name) => println!("Name is {}", name),
    None => println!("No name provided"),
}
```

好多了！ 不过我们不需要在 person.age 上引用，因为 u32 是可复制的。 在这里，我们对一个引用进行了模式匹配分析，因此这个 name 也是一个引用。

但是，我们可以使用 ref 关键字更明确地说明这一点。 这个关键词表明，当模式匹配时，我们希望模式是一个引用，而不是原始值的移动。 (更多请参考[ 《in the Rust book](https://doc.rust-lang.org/book/second-edition/ch18-03-pattern-syntax.html#creating-references-in-patterns-with-ref-and-ref-mut)》) 。我们最终得到的是：

```rust
match person.name {
    Some(ref name) => println!("Name is {}", name),
    None => println!("No name provided"),
}
```

在我们的例子中，这与 \&person.name 的基本结果相同。

## Birthday!

让我们修改代码，以便在打印 age 时也将 age 增加1。首先是下面的问题，代码不能编译通过，请尝试预测原因：

```rust
match person.age {
    Some(age) => {
        println!("Age is {}", age);
        age += 1;
    }
    None => println!("No age provided"),
}
```

我们正在尝试更改局部 age 的绑定，但这是不可变的。 好吧，这很容易修复，只需将Some(age)替换为 Some(mut age)。 可以编译，但带有警告：

```rust
warning: value assigned to `age` is never read
  --> src/main.rs:16:13
   |
16 |             age += 1;
   |             ^^^
   |
   = note: #[warn(unused_assignments)] on by default
```

然后输出是：

```
Name is Alice
Age is 30
Full Person value: Person { name: Some("Alice"), age: Some(30) }
```

注意最后一行，age 仍然是30岁，而不是31岁。 花一分钟时间试着理解这里发生了什么... 完成了吗？ 酷。

1. 根据 person.age 模式匹配
2. 如果是Some，我们需要将 age 移动到局部 age
3. 但是由于类型是 u32，它将创建一个副本并移动该副本
4. 当我们递增 age 时，我们递增了一个从未使用过的副本

我们可以通过引用 person.age 来解决这个问题：

```rust
fn print_person(person: Person) {
    match person.name {
        Some(ref name) => println!("Name is {}", name),
        None => println!("No name provided"),
    }

    match &mut person.age {
        Some(age) => {
            println!("Age is {}", age);
            age += 1;
        }
        None => println!("No age provided"),
    }

    println!("Full Person value: {:?}", person);
}
```

编译器抱怨：age是一个 \&mut u32，但是我们试图在其上使用 +=  ：

```rust
error[E0368]: binary assignment operation `+=` cannot be applied to type `&mut u32`
  --> src/main.rs:16:13
   |
16 |             age += 1;
   |             ---^^^^^
   |             |
   |             cannot use `+=` on type `&mut u32`
   |
   = help: `+=` can be used on 'u32', you can dereference `age`: `*age`
```

根据编译器的踏实，我们需要采用取消年龄引用。 但又出现一个错误：

```rust
error[E0596]: cannot borrow field `person.age` of immutable binding as mutable
  --> src/main.rs:13:16
   |
7  | fn print_person(person: Person) {
   |                 ------ consider changing this to `mut person`
...
13 |     match &mut person.age {
   |                ^^^^^^^^^^ cannot mutably borrow field of immutable binding

error: aborting due to previous error
```

同样，编译器准确地告诉我们如何解决这个问题: 使 person 可变。 继续，做出改变，一切都会顺利进行。

### 练习1

对于 person.name，我们提出了两种解决方案: 借用 person.name 或使用 ref 关键字。 同样的两种解决方案也适用于我们当前的问题。 我们刚刚演示了借用方法。 尝试使用 ref 关键字来解决这个问题。

## 单一迭代器

让我们做一个傻乎乎的小迭代器，它只产生一个值。 我们将通过使用 Option 来追踪是否产生了值：

```rust
struct Single<T> {
    next: Option<T>,
}
```

让我们创建一个辅助函数来创建单个值：

```rust
fn single<T>(t: T) -> Single<T> {
    Single {
        next: Some(t),
    }
}
```

让我们编写一个 main 函数来测试这个函数是否正常工作：

```rust
fn main() {
    let actual: Vec<u32> = single(42).collect();
    assert_eq!(vec![42], actual);
}
```

如果你试图编译它，你会得到一个错误消息：

```rust
error[E0599]: no method named `collect` found for type `Single<{integer}>` in the current scope
  --> src/main.rs:12:39
   |
1  | struct Single<T> {
   | ---------------- method `collect` not found for this
...
12 |     let actual: Vec<u32> = single(42).collect();
   |                                       ^^^^^^^
   |
   = note: the method `collect` exists but the following trait bounds were not satisfied:
           `&mut Single<{integer}> : std::iter::Iterator`
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `collect`, perhaps you need to implement it:
           candidate #1: `std::iter::Iterator`
```

为了使用collect()，我们需要提供一个 Iterator 实现。 Item 将会是 T。我们已经有了一个很好的 Option ，可以用于 next() 函数的返回值：

```rust
impl<T> Iterator for Single<T> {
    type Item = T;

    fn next(&mut self) -> Option<T> {
        self.next
    }
}
```

不幸的是，这并不奏效：

```rust
error[E0507]: cannot move out of borrowed content
  --> src/main.rs:21:9
   |
21 |         self.next
   |         ^^^^ cannot move out of borrowed content

error: aborting due to previous error
```

哦，对。 我们不能将结果值移出，因为我们的 next 函数只能可变借用 self。 让我们尝试一些模式匹配:

```rust
match self.next {
    Some(next) => Some(next),
    None => None,
}
```

除此之外，这还涉及从引用中移动，因此失败。 让我们再试一次：我们将进入一个局部变量，将 self.next 设置为 None（这样就不会再次重复该值），然后返回该局部变量：

```rust
fn next(&mut self) -> Option<T> {
    let res = self.next;
    self.next = None;
    res
}
```

不，编译器仍然不满意！ 我想我们只需要放弃对单一迭代器的宏伟愿景。 我们当然可以作弊：

```rust
fn next(&mut self) -> Option<T> {
    None
}
```

但是，尽管编译完成，但它在运行时未能通过我们的测试：

```rust
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `[42]`,
 right: `[]`', src/main.rs:13:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.
```

## Swap

上面我们所做的是尝试将 self.next 与局部变量交换。 但是，借用检查员并不喜欢我们采用的方法。 但是，标准库有一个辅助函数std::mem::swap，该函数可以为我们提供帮助。 看起来像：

```rust
pub fn swap<T>(x: &mut T, y: &mut T)
```

毫无疑问，我们可以用它来解决我们的问题：

### 练习2

上面的代码不能完全编译，但是编译器可以指导您找到正确的解决方案。 试着找出上面的问题并自己解决它们。 如果做不到这一点，请求编译器帮助你。

## replace 和 take

你是否觉得创建临时变量这个东西有点冗长？ 是的，对于 Rust 标准库的作者来说也是如此。 有一个 helper 函数绕过了这个临时变量：

```rust
fn next(&mut self) -> Option<T> {
    std::mem::replace(&mut self.next, None)
}
```

好多了！ 但这应该是很容易的事情，这似乎仍然需要更多工作。幸运的是，对于 Rust 标准库的作者来说也是如此。 **使用 None 替换 Option 中的值，然后使用原始值**，这种模式非常常见，以至于他们给了它一个名称和一个方法: take。

```rust
fn next(&mut self) -> Option<T> {
    self.next.take()
}
```

我们完成了！

## 生命周期

我们在前面的课程中已经简要地提到了生命，但是现在是时候更加认真地对待它们了。 让我们来看看引用的一个简单用法：

```rust
struct Person {
    name: String,
    age: u32,
}

fn get_name(person: &Person) -> String {
    person.name
}

fn main() {
    let alice = Person {
        name: String::from("Alice"),
        age: 30,
    };
    let name = get_name(&alice);
    println!("Name: {}", name);

```

这段代码不能编译。 我们的 get\_name 函数接受一个 Person 的引用，然后尝试在其结果中移动这个 Person 的 name。 这不可能。 一个解决方案是克隆这个name：

```rust
fn get_name(person: &Person) -> String {
    person.name.clone()
}
```

虽然这样做是有效的，但是效率相对较低。 我们喜欢尽量避免复制。 相反，让我们可以简单地返回一个名称的引用：

```rust
fn get_name(person: &Person) -> &String {
    &person.name
}
```

万岁！ 但是让我们把函数变得更复杂一点。 现在我们需要一个函数，它将接受两个 Person，并返回 age 较大的 Person 的 name。 这听起来相当容易写：

```rust
struct Person {
    name: String,
    age: u32,
}

fn get_older_name(person1: &Person, person2: &Person) -> &String {
    if person1.age >= person2.age {
        &person1.name
    } else {
        &person2.name
    }
}

fn main() {
    let alice = Person {
        name: String::from("Alice"),
        age: 30,
    };
    let bob = Person {
        name: String::from("Bob"),
        age: 35,
    };
    let name = get_older_name(&alice, &bob);
    println!("Older person: {}", name);
}
```

不幸的是，编译器非常生我们的气：

```rust
error[E0106]: missing lifetime specifier
 --> src/main.rs:6:58
  |
6 | fn get_older_name(person1: &Person, person2: &Person) -> &String {
  |                                                          ^ expected lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `person1` or `person2`
```

该错误消息非常明显。 我们的函数是返回借用的值。 该值必须从某个地方借来。 仅有的两个选项是 person1 和 person2。 似乎 Rust 由于某种原因需要知道这一点。

> 这是一个小故障，请参阅下面的 “静态生命周期”。

还记得关于引用的规则吗？ 引用的生命周期不能超过它们的原始值。 我们需要跟踪结果值允许存活的时间，它必须小于或等于它来自生命的时间。 整个概念就是生命。

处于某种原因，我们稍后将要讨论(在“生命周期省略”之中)。通常我们可以使用显式生命周期绕过这个问题。 然而，有时候我们确实需要表达清楚。 为此，我们引入了一些新的参数。 但是这一次，它们是生存期参数，以单引号开头，小写。 通常它们只是一个单一的字母。 例如：

```rust
fn get_older_name<'a, 'b>(person1: &'a Person, person2: &'b Person) -> &String
```

我们仍然从编译器得到一个错误，因为我们的返回值没有生命周期。 我们应该选择 a 还是 b？ 或者我们可以创建一个新的 c 并尝试一下？ 让我们从 “ a” 开始。 我们得到了错误消息：

```rust
error[E0623]: lifetime mismatch
  --> src/main.rs:10:9
   |
6  | fn get_older_name<'a, 'b>(person1: &'a Person, person2: &'b Person) -> &'a String {
   |                                                         ----------     ----------
   |                                                         |
   |                                                         this parameter and the return type are declared with different lifetimes...
...
10 |         &person2.name
   |         ^^^^^^^^^^^^^ ...but data from `person2` is returned here
```

这是有道理的: 因为我们的结果可能来自于 person2，我们不能保证 ‘ a 寿命参数小于或等于 ’ b 寿命参数。 幸运的是，我们可以明确地声明，就像我们声明类型实现了一些 traits 一样:

```rust
fn get_older_name<'a, 'b: 'a>(person1: &'a Person, person2: &'b Person) -> &'a String {
```

这实际上可以编译！ 或者，在这种情况下，我们可以完全绕过第二个生命周期参数，并说 person1 和 person2 必须具有相同的生命周期，该生命周期必须与返回值相同：

```rust
fn get_older_name<'a>(person1: &'a Person, person2: &'a Person) -> &'a String {
```

如果你像我一样，你可能会认为这样做过于局限。 例如，我一开始认为上面的签名不能编译这段代码:

```rust
fn main() {
    let alice = Person {
        name: String::from("Alice"),
        age: 30,
    };
    foo(&alice);
}

fn foo(alice: &Person) {
    let bob = Person {
        name: String::from("Bob"),
        age: 35,
    };
    let name = get_older_name(&alice, &bob);
    println!("Older person: {}", name);
}
```

Alice 的生命周期显然比Bob的生命周期要长。 但是，函数签名中生存期的语义是所有值至少具有相同的生存期。 如果他们碰巧活得久一点，没有伤害，没有犯规。

## 多生命周期参数需求

那么，我们能否举出一个绝对需要多个生存期参数的例子呢? 当然！

```rust
fn message_and_return(msg: &String, ret: &String) -> &String {
    println!("Printing the message: {}", msg);
    ret
}

fn main() {
    let name = String::from("Alice");
    let msg = String::from("This is the message");
    let ret = message_and_return(&msg, &name);
    println!("Return value: {}", ret);
}
```

这段代码不能编译，因为我们需要一些生存期参数。 所以让我们用上面的方法，使用相同的参数：

```rust
fn message_and_return<'a>(msg: &'a String, ret: &'a String) -> &'a String {
```

可以编译，但是让我们的调用代码更加复杂：

```rust
fn main() {
    let name = String::from("Alice");
    let ret = foo(&name);
    println!("Return value: {}", ret);
}

fn foo(name: &String) -> &String {
    let msg = String::from("This is the message");
    message_and_return(&msg, &name)
}
```

现在编译器不高兴了：

```rust
error[E0597]: `msg` does not live long enough
  --> src/main.rs:14:25
   |
14 |     message_and_return(&msg, &name)
   |                         ^^^ borrowed value does not live long enough
15 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the function body at 12:1...
  --> src/main.rs:12:1
   |
12 | / fn foo(name: &String) -> &String {
13 | |     let msg = String::from("This is the message");
14 | |     message_and_return(&msg, &name)
15 | | }
   | |_^
```

我们已经说过，返回值必须与 msg 参数使用相同的生命周期。 但我们将返回值返回到 foo 函数之外，而 msg 值的生命周期不会超出 foo 的结尾。

调用代码应该没问题，我们只需要告诉编译器 msg 参数的生存期短于返回值就可以。

### 练习3

修改 message\_and\_return 的签名，以便代码编译和运行。

## 省略生命周期

为什么有时我们跳过生命周期，有时又需要包括它们？ 语言中有规则，称为“省略生命周期”。 与其试图自己解决这个问题，不如参考一下文档：

<https://doc.rust-lang.org/nomicon/lifetime-elision.html>

## 静态生命周期

上面，我暗示如果返回一个引用，那么它必须具有与其一个输入参数相同的生存期。这在大多数情况下是有道理的，因为否则我们将不得不凭空想出一些任意的生命周期。然而，这也是一个谎言。 有一个特殊的生命周期存在于整个程序中，叫做 'static。这是一个有趣的消息：自我们共同编写的第一个 Hello World 以来，您就暗中使用了它！

每个字符串实际上都是一个具有 static 生命周期的引用。

```rust
fn name() -> &'static str {
    "Alice"
}

fn main() {
    println!("{}", name());
}
```

## 数组、切片、Vec和字符串

这是另一个我们一直厚颜无耻的地方。 String 和 str 之间的区别是什么？ 这两个词都出现了不少。 我们一会儿会讲到这些。 首先，我们需要讨论数组、切片和向量。

### 数组

据我所知，关于数组的最好的官方文档是 API 文档本身。 数组是包含固定长度的单一数据类型的连续内存块。 类型表示为 \[ t; n ] ，其中 t 是值的类型，n 是数组的长度。 与任何正常的编程语言一样，数组在 Rust 中的索引是从0开始的。

初始化数组有两种语法，列出常量语法(如 Javascript、 Python 或 Haskell) :

```rust
fn main() {
    let nums: [u32; 5] = [1, 2, 3, 4, 5];
    println!("{:?}", nums);
}
```

和重复表达式：

```rust
fn main() {
    let nums: [u32; 5] = [42; 5];
    println!("{:?}", nums);
}
```

你可以让数组可以变，然后，对它们进行修改：

```rust
fn main() {
    let mut nums: [u32; 5] = [42; 5];
    nums[2] += 1;
    println!("{:?}", nums);
}
```

很好，但是如果您需要动态的东西怎么办？ 为此，我们有……

### Vec

Vec是 “ 连续的，可增长的数组类型”。 可以使用push或pull，可以在 O(1) 时间通过索引访问其长度。 我们还有一个漂亮的vec！ 用于构建它们的宏：

```rust
fn main() {
    let mut v: Vec<u32> = vec![1, 2, 3];
    v.push(4);
    assert_eq!(v.pop(), Some(4));
    v.push(4);
    v.push(5);
    v.push(6);
    assert_eq!(v.pop(), Some(6));
    assert_eq!(v[2], 3);
    println!("{:?}", v); // 1, 2, 3, 4, 5
}
```

### 切片

我将编写一个 helper 函数，它将输出 Vec 中的所有值：

```rust
fn main() {
    let v: Vec<u32> = vec![1, 2, 3];
    print_vals(v);
}

fn print_vals(v: Vec<u32>) {
    for i in v {
        println!("{}", i);
    }
}
```

当然，因为这是一个传递值，所以下面的代码不能编译：

```rust
fn main() {
    let mut v: Vec<u32> = vec![1, 2, 3];
    print_vals(v);
    v.push(4);
    print_vals(v);
}
```

很容易修复：让 print\_vals 引用一个 Vec:

```rust
fn main() {
    let mut v: Vec<u32> = vec![1, 2, 3];
    print_vals(&v);
    v.push(4);
    print_vals(&v);
}

fn print_vals(v: &Vec<u32>) {
    for i in v {
        println!("{}", i);
    }
}
```

不幸的是，这并不能泛化为，比如说，数组:

```rust
fn main() {
    let a: [u32; 5] = [1, 2, 3, 4, 5];
    print_vals(&a);
}
```

这个方法失败了，因为 print\_vals 需要一个 \&Vec\<u32> ，但是我们提供了一个 &\[u32; 5] 。 但是这是相当令人失望的。 一个动态Vec和一个固定长度的数组在很多情况下都是一样的。 有什么东西能把这两者都概括起来吗？

引入切片。 引用《The Rust book》：

***允许您引用集合中的连续元素序列，而不是整个集合。***

为了实现这些功能，我们需要将 print\_vals 的签名改&#x4E3A;***：***

```rust
fn print_vals(v: &[u32]) {
```

&\[u32] 是对切片 u32 的引用。 可以从数组或Vec创建切片，更不用说其他情况了。 （我们将稍微讨论＆借位运算符的作用）。作为一般性建议，如果您收到的是一个由一系列值组成的参数，请尝试使用切片，因为它会给调用者更好地控制数据的来源。

我在上面玩了一些文字游戏，在“引用切片”和“切片”之间切换。 显然，我们正在使用引用。 我们可以取消对切片引用的引用并获取切片本身吗？ 我们试试吧！

```rust
fn print_vals(vref: &[u32]) {
    let v = *vref;
    for i in v {
        println!("{}", i);
    }
}
```

编译器又跟我们发脾气了：

```rust
error[E0277]: the size for values of type `[u32]` cannot be known at compilation time
 --> src/main.rs:7:9
  |
7 |     let v = *vref;
  |         ^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `[u32]`
  = note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-sized>
  = note: all local variables must have a statically known size

error[E0277]: the size for values of type `[u32]` cannot be known at compilation time
 --> src/main.rs:8:14
  |
8 |     for i in v {
  |              ^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `[u32]`
  = note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-sized>
  = note: required by `std::iter::IntoIterator::into_iter`

error[E0277]: the trait bound `[u32]: std::iter::Iterator` is not satisfied
 --> src/main.rs:8:14
  |
8 |     for i in v {
  |              ^ `[u32]` is not an iterator; maybe try calling `.iter()` or a similar method
  |
  = help: the trait `std::iter::Iterator` is not implemented for `[u32]`
  = note: required by `std::iter::IntoIterator::into_iter`
```

基本上，没有办法取消对切片的引用。 从逻辑上讲，无论是在堆栈，堆还是在可执行文件本身上，只要保留对包含值的内存块的引用（例如字符串字面值，我们将在后面介绍），就合乎逻辑。

## Deref

有些地方不太对劲， 为什么 ampersand/borrow 操作符给我们提供了不同的类型！

```rust
fn main() {
    let v = vec![1, 2, 3];
    let _: &Vec<u32> = &v;
    let _: &[u32] = &v;
}
```

事实证明，借用运算符与 “ Deref强制” 交互。 如果您对此感到好奇，请查看 [Deref 特性的文档](https://doc.rust-lang.org/std/ops/trait.Deref.html)。 例如，我可以创建一个可以借用到切片中的新结构：

```rust
use std::ops::Deref;

struct MyArray([u32; 5]);

impl MyArray {
    fn new() -> MyArray {
        MyArray([42; 5])
    }
}

impl Deref for MyArray {
    type Target = [u32];

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

fn main() {
    let ma = MyArray::new();
    let _: &MyArray = &ma;
    let _: &[u32] = &ma;
}
```

感谢 udoprog [回答了这个问题](https://stackoverflow.com/questions/53250856/can-i-borrow-a-custom-type-into-a-slice)。另外，仅仅因为你能做到这一点[并不意味着你就应该这么做](https://stackoverflow.com/questions/45086595/is-it-considered-a-bad-practice-to-implement-deref-for-newtypes)。

## 使用切片

切片与其他类型一样是数据类型。您可以查看[ std::slice 模块文档](https://doc.rust-lang.org/std/slice/index.html)和[ slice 原语类型](https://doc.rust-lang.org/std/primitive.slice.html)。

一些常见的互动方式包括：

* 使用它们作为迭代器
* 使用 slice\[idx] 语法对它们进行索引
* 使用 slice\[ start .. end ] 语法获取子切片

## Byte 字面量

如果在字符串文本前面放置一个小写 b，就会得到一个字节数组。 你可以把它看作一个固定长度的数组，或者，更常见的，看作一个切片：

```rust
fn main() {
    let bytearray1: &[u8; 22] = b"Hello World in binary!";
    let bytearray2: &[u8] = b"Hello World in binary!";
    println!("{:?}", bytearray1);
    println!("{:?}", bytearray2);
}
```

请注意，您总是接收到对值的引用，而不是值本身。 数据存储在程序可执行文件中，因此无法修改(因此总是接收不可变引用)。

### 练习4

在上面的 bytearray1 和 bytearray2 类型中添加生存期参数。

## Strings

最后我们可以谈谈字符串！ 您可能认为字符串文本是一个固定长度的字符数组。 事实上，你可以创造这样一个东西:

```rust
fn main() {
    let char_array: [char; 5] = ['H', 'e', 'l', 'l', 'o'];
    println!("{:?}", char_array);
}
```

但是，这不是 str。 上面的表述是非常低效的。 由于 Rust 中的 char 具有完全的 Unicode 支持，所以它占用了4个内存字节(32位)。

&#x20;然而，对于大多数数据来说，这是过度杀伤。 像 UTF-8这样的字符编码将会更有效率。

> 注意：如果您不熟悉Unicode字符编码，可以在此处忽略这些详细信息。 了解字符串在Rust中的工作方式并不是至关重要的。

取而代之的是，字符串切片(\&str) 本质上是围绕字节切片(&\[u8]) 的新型包装，该字节包装保证采用UTF-8编码。 这需要权衡一些重要事项：

* 您可以很便宜地(自由地?)从一个 \&str 转换为一个 &\[u8] ，这对于进行系统调用非常有用
* 您无法在字符串中获得 O(1)随机访问，因为 UTF-8 编码不允许这样做。 相反，您需要使用字符迭代器来查看单个字符。

### 练习5

在 String 上使用 std::env::args 和 chars() 方法打印出每个命令行参数中的字符数。 附加说明: 还要打印出字节数。 样本用法：

```rust
$ cargo run שלום
arg: target/debug/foo, characters: 16, bytes: 16
arg: שלום, characters: 4, bytes: 8
```

不要忘记，第一个参数是可执行文件的名称。

## 结构体的生命周期

今天的最后一个主题是结构体的生命周期。 在结构体中保留引用是完全可能的。 然而，当你这样做的时候，你需要清楚地说明他们的生命周期。 例如，这将无法编译：

```rust
struct Person {
    name: &str,
    age: u32,
}
```

相反，你需要这样写：

```rust
struct Person<'a> {
    name: &'a str,
    age: u32,
}
```

我收到的一般建议是，尽可能避免这种情况，我也会转达这个建议。 在处理结构体的生存期参数时，事情最终会变得非常复杂。 通常，应该在结构体中使用属性版本的值(例如，String 而不是 \&str，Vec 或数组而不是 slice)。 在这种情况下，您需要确保结构体引用的生存期超过结构本身的生存期。

有时候，如果在结构体中使用引用，就可以避免一些额外的克隆和分配，而当您需要这样做时，可能就会出现这种情况。 但是我建议你等到你的分析指出是瓶颈时再说。 欲了解更多信息，请参阅《[The Rust Book](https://doc.rust-lang.org/book/second-edition/ch10-03-lifetime-syntax.html#lifetime-annotations-in-struct-definitions)》。

## Api 中的引用和切片

以下是我收到的一些建议，大多数情况下都是正确的：

**接收参数时，尽可能选择切片**

然而有时候这是过于简单化的。 如果你想深入了解，有一篇很棒的博客文章介绍了公共 api 的一些权衡：[On dealing with owning and borrowing in public interfaces](https://phaazon.net/blog/on-owning-borrowing-pub-interface) ，[Reddit discussion](https://www.reddit.com/r/rust/comments/9tzygo/on_dealing_with_owning_and_borrowing_in_public/) 也很棒。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://chapin666.gitbook.io/rust-crash-course-zh/6.-sheng-ming-zhou-qi-he-qie-pian/su-cheng-ban-di-6-ke.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
