为什么rust是安全的?从泛型的角度进一步解析

Table of Contents

如果我没有读过之前的这篇介绍,那么最好还是先学习一下所有权(ownership)吧!

1. 泛型——和C++没有区别

如果我忘记C++的了,推荐阅读: C++的泛型笔记

让我们先来简单过一下rust写泛型的语法,很快的过一遍。

              struct Point<T> {
                  x: T,
                  y: T,
              }

            fn main() {
                let integer = Point { x: 5, y: 10 };
                let float = Point { x: 1.0, y: 4.0 };
            }

          //============================================

        struct Point<T, U> {
            x: T,
            y: U,
        }

        fn main() {
            let both_integer = Point { x: 5, y: 10 };
            let both_float = Point { x: 1.0, y: 4.0 };
            let integer_and_float = Point { x: 5, y: 4.0 };
        }

          //============================================

      enum Option<T> {
          Some(T),
          None,
      }

          //============================================

    enum Result<T, E> {
        Ok(T),
        Err(E),
    }
          //============================================

  struct Point<T> {
      x: T,
      y: T,
  }

  impl<T> Point<T> {
      fn x(&self) -> &T {
          &self.x
      }
  }

  fn main() {
      let p = Point { x: 5, y: 10 };

      println!("p.x = {}", p.x());
  }
  //==========最复杂的例子=====================================
struct Point<T, U> {
    x: T,
    y: U,
}

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c'};

    let p3 = p1.mixup(p2);

    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}

总结一句话就是:我们认为,泛型可以对类型进行更好地抽象。

2. trait-直译就是:个性

2.1. 我们为什么需要trait?

泛型可能看起来是一个很好的东西,但是这个东西有一个问题: 如果我实例化的类型符合我的泛型,那么泛型代码就可以很好地被执行。如果我实例化的类型里,缺少了某些操作,泛型就会出问题。 上述问题最可怕的地方是: 编译时:我们并不知道我们的泛型适不适合这个类型 。 trait就是为了解决以上问题而生的。

trait就是定义和规定了:这么一些类型可以进行什么样子的操作,有点类似于接口,但是更加强调的是类型的特点,也就是类型支持哪些函数。虽然trait在设计时:一个trait可以包括多个函数,但是一般而言一个trait里可能只有一个函数。

另外一个理解trait的视角是:泛型是一种抽象,而抽象就需要有共性。而trait,就是实现这种共性的基础,也就是基础的共性。比如,加入我要写一个泛型函数,能够对一个向量排序,不管这个向量是整数向量还是字符串向量。那么我们注意到,实现排序需要一个基础操作:比较两个数的大小。如果我没有对整数、字符串中的某一个实现这个基础操作,那么泛型就无法通用。trait捕捉到了这种共同的基础。

我的另外一个理解是:trait挖掘到了实现抽象中的差异。也就是说:trait所要求各个类型实现的,恰恰就是各个类型的特点(你看,这也是trait的意思),也就是各个类型必须自我实现的特点,trait强迫各个类型实现。 再以上面的例子为例,对于实现比较性,整数和字符串有先天的不同,所以我必须实现一个字符串比较的函数,以满足这个trait。

那么可能有一个问题:我能不能不使用trait,直接写泛型?其实是可以的。trait实现了解耦,让泛型写得更加抽象简洁。你可以在泛型里写枚举,实现同样的功能——代码量没有变化。比如上面的例子,我可以判断泛型的类型,如果是字符串,我把字符串排序的方式写上去,就可以了。当然,这在rust里可能行不通。

现在你应该了解了trait了,我们现在来看看怎么用trait约束一个类型。

2.2. 使用trait约束函数或结构体

先定义一个trait。

pub trait Summary {
    fn summarize(&self) -> String;
}

之后,我们定义两个类型,并分别为他们实现这个trait。

#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}
}

这样我们就可以为这两个类型的对象使用这个trait了。

let tweet = Tweet {
    username: String::from("horse_ebooks"),
    content: String::from("of course, as you probably already know, people"),
    reply: false,
    retweet: false,
};

println!("1 new tweet: {}", tweet.summarize());

当然,我们也可以为trait写一个默认的实现。

#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}
}

2.3. 把trait当作一种高级类型,以约束函数和泛型

2.3.1. 用trait约束函数

我们的函数过去通过数值的类型去判断输入是否合适,这当然是为了编译的检查。 还有一种方法也可以为编译提供检查,那就是提供trait,因为trait,本身就提供了一个类型空间。从某种程度上来讲,提供trait可以产生更加细粒度的编译检查,同时提供更加广义的函数使用空间。

先看一个例子:

pub fn notify(item: impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

推荐使用以上格式,如果能用,它实质上是下面的一种简化:

pub fn notify<T: Summary>(item: T) {
    println!("Breaking news! {}", item.summarize());
}

但如果较为复杂的泛型就简化不了了,如多个类型:

pub fn notify<T: Summary>(item1: T, item2: T) {
}

多个trait的话,同样地,也有两种形式:

pub fn notify(item: impl Summary + Display) {}
pub fn notify<T: Summary + Display>(item: T) {}

如果是不同类型的,就不能简化了:

      fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
}
//第二种写法
fn some_function<T, U>(t: T, u: U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{

返回值也一样可以用trait约束:

fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    }
}


Author: Zi Liang (liangzid@stu.xjtu.edu.cn) Create Date: Thu Nov 18 21:36:05 2021 Last modified: 2024-03-09 Sat 20:56 Creator: Emacs 28.1 (Org mode 9.5.2)