暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

写给 Web 开发者的 Rust 语言入门教程(11.所有权)

最后的叶子的奇妙小屋 2021-07-27
329

之前的文章:

0.引言

1.模块基础

2.变量

3.字符串

4.分支循环

5.数据表示

6.模块化

7.接口抽象

8.泛型

9.错误处理

10.内存布局

本章主要介绍 rust 的特色概念“所有权”,包括所有权的归属、转移、丢弃,以及在 rust 中“复制”的概念与应用。

所有权归属

所有权( ownership )是 rust 独创的重要概念。借助所有权,编译器可以在编译过程中就推断出变量值所占据的内存片段应在何时释放,尽可能提升内存性能。

任何数据类型值被赋值给某个变量的时候,这个变量就成为了它的“所有者”。例如:

    struct Product {
    price: u32,
    kind: ProductKind,
    }


    enum ProductKind {
    Fruit {
    name: String,
    },
    Book {
    title: String,
    author: String,
    },
    }


    fn main() {
        let my_product = Product {
    price: 23,
    kind: ProductKind::Fruit {
    name: format!("Apple"),
    },
    };
        // my_product 有上述 Product 数据的所有权
    }

    上述这段代码中, my_product 拥有了所创建的 Product 数据的所有权,也间接拥有了其下 ProductKind 数据的所有权。

    所有权转移

    当把一个变量赋值给另一个变量时,其实是将其值的所有权转移( move )给了另一个变量。失去所有权之后,变量就不可用了。例如:

      let my_product = Product {
      price: 23,
      kind: ProductKind::Fruit {
      name: format!("Apple"),
      },
      };
      let another_product = my_product;
      // 所有权转移:
      // my_product 失去了数据的所有权;
      // another_product 获得了数据的所有权。


      println!("{}", my_product.price);
      // 编译失败:因为 my_product 已经失去了所有权,不能使用了
      还有一些其他类似于直接赋值的过程也会产生所有权转移,例如将一个变量作为另一个变量值的一部分,实际上也是一种间接赋值:
        let my_product_kind = ProductKind::Fruit {
        name: format!("Apple"),
        };
        let my_product = Product {
        price: 23,
        kind: my_product_kind,
        };
        // 所有权转移:
        // my_product_kind 失去了数据的所有权;
        // my_product 获得了数据的所有权(间接地)。


        println!("{:?}", my_product_kind);
        // 编译失败:因为 my_product 已经失去了所有权,不能使用了

        丢弃

        变量作用域结束时(它所在的花括号包裹的语句块末尾),如果它还拥有一个值的所有权,那么此时这个值会被“丢弃”(不再有变量拥有它的所有权)。

        rust 有一个特殊的 trait Drop ,如果数据类型实现了这个 trait ,那这个数据类型的值在被丢弃时会触发 Drop 中的一个函数 drop 。这个特性类似于一些面向对象语言中“析构函数”的概念。它的一个用途是在调试时追踪某些值是在什么时候被丢弃的,以便优化内存占用,例如:

          struct Product {
          price: u32,
          kind: ProductKind,
          }


          enum ProductKind {
          Fruit {
          name: String,
          },
          Book {
          title: String,
          author: String,
          },
          }


          impl Drop for Product {
          // 这个函数在一个 Product 数据丢弃时被调用
          fn drop(&mut self) {
          println!("A product is dropped");
          }
          }


          fn main() {
          let my_product = Product {
          price: 23,
          kind: ProductKind::Fruit {
          name: format!("Apple"),
          },
          };
          println!("fn main ended");
          }

          上例中, drop 函数中的输出在 main 函数的所有输出之后,因为 my_product 的值被丢弃是在 main 函数结束的时刻。如果想要提前丢弃一个 Product ,最常用的方法是把它单独放在一个较小的语句块内:

            fn main() {
            {
            let my_product = Product {
            price: 23,
            kind: ProductKind::Fruit {
            name: format!("Apple"),
            },
            };
            }
            println!("fn main ended");
            }

            像上面这样写, my_product 的值丢弃就可以早于 main 函数结束了。

            注意,如果花括号包裹的语句块返回了一个值,这个值不会被丢弃,而是它的所有权转移到花括号以外,例如:

              fn main() {
              let another_product = {
              let my_product = Product {
              price: 23,
              kind: ProductKind::Fruit {
              name: format!("Apple"),
              },
              };
              my_product // 这个值不会被立刻丢弃,而是转移给 another_product
              };
              println!("fn main ended");
              }

              上例中, my_product 的值并没有在它所在的花括号末尾被丢弃,而是转移给了 another_product ;最终是在 main 函数结束的时刻被丢弃的。

              借用的限制

              花括号包裹的语句块内变量所有的值,不能以引用的形式返回到花括号外。即,借用的范围不能超过被丢弃的时刻。例如:

                let another_product = {
                let my_product = Product {
                price: 23,
                kind: ProductKind::Fruit {
                name: format!("Apple"),
                },
                };
                &my_product
                // 编译失败:
                // 因为 my_product 在此时被丢弃;
                // 它的引用不能被返回到花括号外。
                };
                println!("fn main ended");

                一个值的所有权从创建、转移到丢弃的过程,也被称为它的生命周期( lifetime )。借用范围不能超过它的生命周期。

                Copy

                一些常见数据类型的值,如 u32 f32 等数值、元组、数组,它们在被赋值时,并不会转移所有权,而是“复制”一份。例如:

                  fn main() {
                  let a = 23;
                  let b = a;
                  // 此时并不是 a 所有权转移给 b ,而是将 a 的值复制一份给 b
                  // 因此 a 仍然可以使用
                  println!("{}", a);
                  }

                  rust 有一个特殊的 trait Copy ,所有实现了 Copy 的数据类型值都是以复制的形式赋值,而不是所有权转移。 rust 内部已经为很多基本数据类型实现了 Copy ,但常见数据类型中的 Box 、 Vec 、 String 并没有实现 Copy 。准确地说,所有需要堆内存分配的数据类型没有实现 Copy

                  使用 struct 或 enum 定义的数据类型,只要它包含的所有数据字段都实现了 Copy ,那也可以为它简单地实现 Copy

                    #[derive(Clone, Copy)]
                    struct SimpleProduct {
                    price: u32,
                    }

                    但只要它有一个字段没有实现 Copy ,那它就不能实现 Copy 。例如, ProductKind 中包含了 String 类型字段,那它就不能实现 Copy ;而 Product 中包含了 ProductKind 类型字段,进而 Product 也不能实现 Copy ;否则会编译失败。

                      #[derive(Clone, Copy)]
                      struct Product {
                      price: u32,
                      kind: ProductKind,
                      }


                      #[derive(Clone, Copy)]
                      enum ProductKind {
                      Fruit {
                      name: String,
                      },
                      Book {
                      title: String,
                      author: String,
                      },
                      }


                      // 编译失败!这两个类型都不能实现 Copy

                      Clone

                      如果一个类型不能实现 Copy ,但又希望它能够在赋值时被复制,那可以转而为它实现 Clone Clone Copy 的区别是, Clone 一定需要在赋值时明确使用 .clone() 来复制,例如:

                        #[derive(Clone)]  为数据类型实现 Clone
                        struct Product {
                        price: u32,
                        kind: ProductKind,
                        }


                        #[derive(Clone)] // 为数据类型实现 Clone
                        enum ProductKind {
                        Fruit {
                        name: String,
                        },
                        Book {
                        title: String,
                        author: String,
                        },
                        }


                        fn main() {
                        let my_product = Product {
                        price: 23,
                        kind: ProductKind::Fruit {
                        name: format!("Apple"),
                        },
                        };
                            let another_product = my_product.clone();
                        // my_product 是以复制的形式赋值的,并不是所有权转移
                        // 因此 my_product 仍然可用
                        println!("{}", my_product.price);
                        }

                        实际上, Clone 相比于 Copy 能用于更多数据类型上(所有实现了 Copy 的数据类型同时也实现了 Clone )。但是 Clone 可能有更大开销,比如会将 String 类型的字符串内容完整复制一次。因此,编译器也要求 Clone 进行复制时必须要明确写上 .clone()

                        此外, Clone 的具体复制过程也可以被自定义,使用 impl Clone 代替 derive(Clone) 即可:

                          struct Product {
                          price: u32,
                          kind: ProductKind,
                          }


                          impl Clone for Product {
                          fn clone(&self) -> Self {
                          Self {
                          price: self.price,
                          kind: self.kind.clone(),
                          }
                          }
                          }

                          相比于变量值的生命周期,引用的生命周期更为复杂,这将在之后的文章中介绍。

                          文章转载自最后的叶子的奇妙小屋,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                          评论