# 复合类型

## 元组(Tuple)

在别的语言里，你可能听过元组这个词，它表示一个大小、类型固定的有序数据组。在Rust中，情况并没有什么本质上的不同。不过Rust为我们提供了一系列简单便利的语法来让我们能更好的使用他。

```rust
let y = (2, "hello world");
let x: (i32, &str) = (3, "world hello");

// 然后呢，你能用很简单的方式去访问他们：

// 用let表达式
let (w, z) = y; // w=2, z="hello world"

// 用下标

let f = x.0; // f = 3
let e = x.1; // e = "world hello"
```

## 结构体(struct)

在Rust中，结构体是一个跟 `tuple` 类似 的概念。我们同样可以将一些常用的数据、属性聚合在一起，就形成了一个结构体。

所不同的是，Rust的结构体有三种最基本的形式。

### 具名结构体

这种结构体呢，他可以大致看成这样的一个声明形式:

```rust
struct A {
    attr1: i32,
    atrr2: String,
}
```

内部每个成员都有自己的名字和类型。

### 元组类型结构体

元组类型结构体使用小括号，类似 `tuple`。

```rust
struct B(i32, u16, bool);
```

它可以看作是一个有名字的元组，具体使用方法和一般的元组基本类似。

### 空结构体

结构体内部也可以没有任何成员。

```rust
struct D;
```

空结构体的内存占用为0。但是我们依然可以针对这样的类型实现它的“成员函数”。

不过到目前为止，除了1.9 nightly版本外，空结构体后面不能加大括号。 如果这么写，则会导致编译错误：

```rust
struct C {

}
```

### 实现结构体(impl)

Rust没有继承，它和Golang不约而同的选择了trait(Golang叫Interface)作为其实现多态的基础。可是，如果我们要想对一个结构体写一些专门的成员函数那应该怎么写呢？

答： impl

talk is cheap ,举个栗子：

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

impl Person {
    fn new(n: &str) -> Person {
        Person {
            name: n.to_string(),
        }
    }

    fn greeting(&self) {
        println!("{} say hello .", self.name);
    }
}

fn main() {
    let peter = Person::new("Peter");
    peter.greeting();
}
```

看见了self，Python程序员不厚道的笑了。

我们来分析一下，上面的`impl`中，new被Person这个结构体自身所调用，其特征是`::`的调用，Java程序员站出来了：类函数！ 而带有`self`的`greeting`，更像是一个成员函数。

恩，回答正确，然而不加分。

### 关于各种ref的讨论

Rust对代码有着严格的安全控制，因此对一个变量也就有了所有权和借用的概念。所有权同一时间只能一人持有，可变引用也只能同时被一个实例持有，不可变引用则可以被多个实例持有。同时所有权能被转移，在Rust中被称为`move`。

以上是所有权的基本概念，事实上，在整个软件的运行周期内，所有权的转换是一件极其恼人和烦琐的事情，尤其对那些初学Rust的同学来说。同样的，Rust的结构体作为其类型系统的基石，也有着比较严格的所有权控制限制。具体来说，关于结构体的所有权，有两种你需要考虑的情况。

#### 字段的ref和owner

在以上的结构体中，我们定义了不少结构体，但是如你所见，结构体的每个字段都是完整的属于自己的。也就是说，每个字段的owner都是这个结构体。每个字段的生命周期最终都不会超过这个结构体。

但是有些时候，我只是想要持有一个(可变)引用的值怎么办？ 如下代码：

```rust
struct RefBoy {
    loc: &i32,
}
```

这时候你会得到一个编译错误：

```
<anon>:6:14: 6:19 error: missing lifetime specifier [E0106]
<anon>:6         loc: & i32,
```

这种时候，你将持有一个值的引用，因为它本身的生命周期在这个struct之外，所以对这个结构体而言，它无法准确的判断获知这个引用的生命周期，这在Rust编译器而言是不被接受的。 因此，这个时候就需要我们给这个结构体人为的写上一个生命周期，并显式地表明这个引用的生命周期。写法如下：

```rust
struct RefBoy<'a> {
    loc: &'a i32,
}
```

这里解释一下这个符号`<>`，它表示的是一个`属于`的关系，无论其中描述的是*生命周期*还是*泛型*。即： `RefBoy in 'a`。最终我们可以得出个结论，`RefBoy`这个结构体，其生命周期一定不能比`'a`更长才行。

写到这里，可能有的人还是对生命周期比较迷糊，不明白其中缘由，其实你只需要知道两点即可：

1. 结构体里的引用字段必须要有显式的生命周期
2. 一个被显式写出生命周期的结构体，其自身的生命周期一定小于等于其显式写出的任意一个生命周期

关于第二点，其实生命周期是可以写多个的，用 `,` 分隔。

注：生命周期和泛型都写在`<>`里，先生命周期后泛型，用`;`分隔。

#### impl中的三种self

前面我们知道，Rust中，通过impl可以对一个结构体添加成员方法。同时我们也看到了`self`这样的关键字，同时，这个self也有好几种需要你仔细记忆的情况。

impl中的self,常见的有三种形式：`self`、`&self`、`&mut self` ，我们分别来说。

**被move的self**

正如上面例子中的impl，我们实现了一个以`self`为第一个参数的函数，但是这样的函数实际上是有问题的。 问题在于Rust的所有权转移机制。

我曾经见过一个关于Rust的笑话："你调用了一下别人，然后你就不属于你了"。

比如下面代码就会报出一个错误：

```rust
struct A {
    a: i32,
}
impl A {
    pub fn show(self) {
        println!("{}", self.a);
    }
}

fn main() {
    let ast = A{a: 12i32};
    ast.show();
    println!("{}", ast.a);
}
```

错误：

```
13:25 error: use of moved value: `ast.a` [E0382]
<anon>:13     println!("{}", ast.a);
```

为什么呢？因为Rust本身，在你调用一个函数的时候，如果传入的不是一个引用，那么无疑，这个参数的owner将被move掉。同理，`impl`中的`self`，如果你写的不是一个引用的话，也是会被默认的move掉哟！

那么如何避免这种情况呢？答案是`Copy`和`Clone`：

```rust
#[derive(Copy, Clone)]
struct A {
    a: i32,
}
```

这么写的话，会使编译通过。但是这么写实际上也是有其缺陷的。其缺陷就是：你不能在一个被copy的`impl`函数里改变它！事实上，被move的`self`其实是相对少用的一种情况，更多的时候，我们需要的是`ref`和`ref mut`。

**ref和ref mut**

关于`ref`和`mut ref`的写法和被move的`self`写法类似，只不过多了一个引用修饰符号，上面有例子，不多说。

需要注意的一点是，你不能在一个`ref`的方法里调用一个`mut ref`，任何情况下都不行！

但是，反过来是可以的。代码如下：

```rust
#[derive(Copy, Clone)]
struct A {
    a: i32,
}
impl A {
    pub fn show(&self) {
        println!("{}", self.a);
        // compile error: cannot borrow immutable borrowed content `*self` as mutable
        // self.add_one();
    }
    pub fn add_two(&mut self) {
        self.add_one();
        self.add_one();
        self.show();
    }
    pub fn add_one(&mut self) {
        self.a += 1;
    }
}

fn main() {
    let mut ast = A{a: 12i32};
    ast.show();
    ast.add_two();
}
```

需要注意的是，一旦你的结构体持有一个可变引用，你，只能在`&mut self`的实现里去改变他！

Rust允许我们灵活的对一个struct进行你想要的实现，在编程的自由度上无疑有了巨大的提高。

至于更高级的关于trait和泛型的用法，我们将在以后的章节进行详细介绍。

## 枚举类型 enum

Rust的枚举(`enum`)类型，跟C语言的枚举有点接近，然而更强大，事实上是代数数据类型(Algebraic Data Type)。

比如说，这是一个代表东南西北四个方向的枚举：

```rust
enum Direction {
    West,
    North,
    Sourth,
    East,
}
```

但是，rust的枚举能做到的，比C语言的多很多。 比如，枚举里面居然能包含一些你需要的，特定的数据信息！ 这是常规的枚举所无法做到的，更像枚举类，不是么？

```rust
enum SpecialPoint {
    Point(i32, i32),
    Special(String),
}
```

你还可以给里面的字段命名，如

```rust
enum SpecialPoint {
    Point {
        x: i32,
        y: i32,
    },
    Special(String),
}
```

### 使用枚举

和struct的成员访问符号`.`不同的是，枚举类型要想访问其成员，几乎无一例外的要用到模式匹配。并且， 你可以写一个`Direction::West`，但是你绝对不能写成`Direction.West`。虽然编译器足够聪明能发现你这个粗心的毛病。

关于模式匹配，我不会说太多，还是举个栗子

```rust
enum SpecialPoint {
    Point(i32, i32),
    Special(String),
}

fn main() {
    let sp = SpecialPoint::Point(0, 0);
    match sp {
        SpecialPoint::Point(x, y) => {
            println!("I'am SpecialPoint(x={}, y={})", x, y);
        }
        SpecialPoint::Special(why) => {
            println!("I'am Special because I am {}", why);
        }
    }
}
```

呐呐呐，这就是模式匹配取值啦。 当然了，`enum`其实也是可以`impl`的，一般人我不告诉他！

对于带有命名字段的枚举，模式匹配时可指定字段名

```rust
match sp {
    SpecialPoint::Point { x: x, y: y } => {
        // ...
    },
    SpecialPoint::Special(why) => {}
}
```

对于带有字段名的枚举类型，其模式匹配语法与匹配`struct`时一致。如

```rust
struct Point {
    x: i32,
    y: i32,
}

let point = Point { x: 1, y: 2 };

let Point { x: x, y: y } = point;
// 或
let Point { x, y } = point;
// 或
let Point { x: x, .. } = point;
```

模式匹配的语法与`if let`和`let`是一致的，所以在后面的内容中看到的也支持同样的语法。


---

# 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://yar999.gitbook.io/rustprimer/preface-3/compound-types.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.
