• 复合类型
    • 元组(Tuple)
    • 结构体(struct)
      • 具名结构体
      • 元组类型结构体
      • 空结构体
      • 实现结构体(impl)
      • 关于各种ref的讨论
        • 字段的 ref 和 owner
        • impl中的三种self
          • 被move的self
            • ref 和 ref mut
    • 枚举类型 enum
      • 使用枚举

    复合类型

    元组(Tuple)

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

    1. let y = (2, "hello world");
    2. let x: (i32, &str) = (3, "world hello");
    3. // 然后呢,你能用很简单的方式去访问他们:
    4. // 用 let 表达式
    5. let (w, z) = y; // w=2, z="hello world"
    6. // 用下标
    7. let f = x.0; // f = 3
    8. let e = x.1; // e = "world hello"

    结构体(struct)

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

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

    具名结构体

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

    1. struct A {
    2. attr1: i32,
    3. atrr2: String,
    4. }

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

    元组类型结构体

    元组类型结构体使用小括号,类似 tuple

    1. struct B(i32, u16, bool);

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

    空结构体

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

    1. struct D;

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

    不过到目前为止,在 1.9 版本之前的版本,空结构体后面不能加大括号。
    如果这么写,则会导致这部分的老编译器编译错误:

    1. struct C {
    2. }

    实现结构体(impl)

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

    答: impl

    talk is cheap ,举个栗子:

    1. struct Person {
    2. name: String,
    3. }
    4. impl Person {
    5. fn new(n: &str) -> Person {
    6. Person {
    7. name: n.to_string(),
    8. }
    9. }
    10. fn greeting(&self) {
    11. println!("{} say hello .", self.name);
    12. }
    13. }
    14. fn main() {
    15. let peter = Person::new("Peter");
    16. peter.greeting();
    17. }

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

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

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

    关于各种ref的讨论

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

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

    字段的 ref 和 owner

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

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

    1. struct RefBoy {
    2. loc: &i32,
    3. }

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

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

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

    1. struct RefBoy<'a> {
    2. loc: &'a i32,
    3. }

    这里解释一下这个符号 <>,它表示的是一个 属于 的关系,无论其中描述的是 生命周期 还是 泛型 。即: 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的笑话:”你调用了一下别人,然后你就不属于你了”。

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

    1. struct A {
    2. a: i32,
    3. }
    4. impl A {
    5. pub fn show(self) {
    6. println!("{}", self.a);
    7. }
    8. }
    9. fn main() {
    10. let ast = A{a: 12i32};
    11. ast.show();
    12. println!("{}", ast.a);
    13. }

    错误:

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

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

    那么如何避免这种情况呢?答案是 CopyClone

    1. #[derive(Copy, Clone)]
    2. struct A {
    3. a: i32,
    4. }

    这么写的话,会使编译通过。但是这么写实际上也是有其缺陷的。其缺陷就是: Copy 或者 Clone ,都会带来一定的运行时开销!事实上,被move的 self 其实是相对少用的一种情况,更多的时候,我们需要的是 refref mut

    ref 和 ref mut

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

    需要注意的一点是,你不能在一个 &self 的方法里调用一个 &mut ref ,任何情况下都不行!

    但是,反过来是可以的。代码如下:

    1. #[derive(Copy, Clone)]
    2. struct A {
    3. a: i32,
    4. }
    5. impl A {
    6. pub fn show(&self) {
    7. println!("{}", self.a);
    8. // compile error: cannot borrow immutable borrowed content `*self` as mutable
    9. // self.add_one();
    10. }
    11. pub fn add_two(&mut self) {
    12. self.add_one();
    13. self.add_one();
    14. self.show();
    15. }
    16. pub fn add_one(&mut self) {
    17. self.a += 1;
    18. }
    19. }
    20. fn main() {
    21. let mut ast = A{a: 12i32};
    22. ast.show();
    23. ast.add_two();
    24. }

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

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

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

    枚举类型 enum

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

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

    1. enum Direction {
    2. West,
    3. North,
    4. Sourth,
    5. East,
    6. }

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

    1. enum SpecialPoint {
    2. Point(i32, i32),
    3. Special(String),
    4. }

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

    1. enum SpecialPoint {
    2. Point {
    3. x: i32,
    4. y: i32,
    5. },
    6. Special(String),
    7. }

    使用枚举

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

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

    1. enum SpecialPoint {
    2. Point(i32, i32),
    3. Special(String),
    4. }
    5. fn main() {
    6. let sp = SpecialPoint::Point(0, 0);
    7. match sp {
    8. SpecialPoint::Point(x, y) => {
    9. println!("I'am SpecialPoint(x={}, y={})", x, y);
    10. }
    11. SpecialPoint::Special(why) => {
    12. println!("I'am Special because I am {}", why);
    13. }
    14. }
    15. }

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

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

    1. match sp {
    2. SpecialPoint::Point { x: x, y: y } => {
    3. // ...
    4. },
    5. SpecialPoint::Special(why) => {}
    6. }

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

    1. struct Point {
    2. x: i32,
    3. y: i32,
    4. }
    5. let point = Point { x: 1, y: 2 };
    6. let Point { x: x, y: y } = point;
    7. // 或
    8. let Point { x, y } = point;
    9. // 或
    10. let Point { x: x, .. } = point;

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