良许Linux教程网 干货合集 详解Rust的泛型和特性

详解Rust的泛型和特性

Rust是 Mozilla 的一个新的编程语言,专注于安全,尤其是并发安全,支持函数式和命令式以及泛型等编程范式的多范式语言。由web语言的领军人物Brendan Eich(js之父),Dave Herman以及Mozilla公司的Graydon Hoare 合力开发,下面为大家详细讲解一下 Rust的泛型和特性。

详解Rust的泛型和特性

在函数中定义泛型

这是一个对整型数字选择排序的方法: 实例

fn max(array: &[i32]) -> i32 {
   let mut max_index = 0;
   let mut i = 1;
   while i len() {
       if array[i] > array[max_index] {
           max_index = i;
       }
       i += 1;
   }
   array[max_index]
}

fn main() {
   let a = [2, 4, 6, 3, 1];
   println!("max = {}", max(&a));
}

运行结果:

max = 6

这是一个简单的取最大值程序,可以用于处理 i32 数字类型的数据,但无法用于 f64 类型的数据。通过使用泛型我们可以使这个函数可以利用到各个类型中去。但实际上并不是所有的数据类型都可以比大小,所以接下来一段代码并不是用来运行的,而是用来描述一下函数泛型的语法格式:

实例

fn max(array: &[T]) -> T {
   let mut max_index = 0;
   let mut i = 1;
   while i len() {
       if array[i] > array[max_index] {
           max_index = i;
       }
       i += 1;
   }
   array[max_index]
}

结构体与枚举类中的泛型

在之前我们学习的 Option 和 Result 枚举类就是泛型的。

Rust 中的结构体和枚举类都可以实现泛型机制。

struct Point {
   x: T,
   y: T
}

这是一个点坐标结构体,T 表示描述点坐标的数字类型。我们可以这样使用:

let p1 = Point {x: 1, y: 2};
let p2 = Point {x: 1.0, y: 2.0};

使用时并没有声明类型,这里使用的是自动类型机制,但不允许出现类型不匹配的情况如下:

let p = Point {x: 1, y: 2.0};

x 与 1 绑定时就已经将 T 设定为 i32,所以不允许再出现 f64 的类型。如果我们想让 x 与 y 用不同的数据类型表示,可以使用两个泛型标识符:

struct Point {
   x: T1,
   y: T2
}

在枚举类中表示泛型的方法诸如 Option 和 Result:

enum Option {
   Some(T),
   None,
}

enum Result {
   Ok(T),
   Err(E),
}

结构体与枚举类都可以定义方法,那么方法也应该实现泛型的机制,否则泛型的类将无法被有效的方法操作。

实例

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

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

fn main() {
   let p = Point { x: 1, y: 2 };
   println!("p.x = {}", p.x());
}

运行结果:

p.x = 1

注意,impl 关键字的后方必须有 ,因为它后面的 T 是以之为榜样的。但我们也可以为其中的一种泛型添加方法:

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

impl 块本身的泛型并没有阻碍其内部方法具有泛型的能力:

impl Point {
   fn mixup(self, other: Point) -> Point {
       Point {
           x: self.x,
           y: other.y,
       }
   }
}

方法 mixup 将一个 Point 点的 x 与 Point 点的 y 融合成一个类型为 Point 的新点。

特性

特性(trait)概念接近于 Java 中的接口(Interface),但两者不完全相同。特性与接口相同的地方在于它们都是一种行为规范,可以用于标识哪些类有哪些方法。

特性在 Rust 中用 trait 表示:

trait Descriptive {
   fn describe(&self) -> String;
}

Descriptive 规定了实现者必须有是 describe(&self) -> String 方法。

我们用它实现一个结构体:

实例

struct Person {
   name: String,
   age: u8
}

impl Descriptive for Person {
   fn describe(&self) -> String {
       format!("{} {}", self.name, self.age)
   }
}

格式是:

impl  for 

Rust 同一个类可以实现多个特性,每个 impl 块只能实现一个。

默认特性

这是特性与接口的不同点:接口只能规范方法而不能定义方法,但特性可以定义方法作为默认方法,因为是”默认”,所以对象既可以重新定义方法,也可以不重新定义方法使用默认的方法:

实例

trait Descriptive {
   fn describe(&self) -> String {
       String::from("[Object]")
   }
}

struct Person {
   name: String,
   age: u8
}

impl Descriptive for Person {
   fn describe(&self) -> String {
       format!("{} {}", self.name, self.age)
   }
}

fn main() {
   let cali = Person {
       name: String::from("Cali"),
       age: 24
   };
   println!("{}", cali.describe());
}

运行结果:

Cali 24

如果我们将 impl Descriptive for Person 块中的内容去掉,那么运行结果就是:

[Object]

特性做参数

很多情况下我们需要传递一个函数做参数,例如回调函数、设置按钮事件等。在 Java 中函数必须以接口实现的类实例来传递,在 Rust 中可以通过传递特性参数来实现:

fn output(object: impl Descriptive) {
   println!("{}", object.describe());
}

任何实现了 Descriptive 特性的对象都可以作为这个函数的参数,这个函数没必要了解传入对象有没有其他属性或方法,只需要了解它一定有 Descriptive 特性规范的方法就可以了。当然,此函数内也无法使用其他的属性与方法。

特性参数还可以用这种等效语法实现:

fn output(object: T) {
   println!("{}", object.describe());
}

这是一种风格类似泛型的语法糖,这种语法糖在有多个参数类型均是特性的情况下十分实用:

fn output_two(arg1: T, arg2: T) {
   println!("{}", arg1.describe());
   println!("{}", arg2.describe());
}

特性作类型表示时如果涉及多个特性,可以用 + 符号表示,例如:

fn notify(item: impl Summary + Display)
fn notify(item: T)

注意:仅用于表示类型的时候,并不意味着可以在 impl 块中使用。

复杂的实现关系可以使用 where 关键字简化,例如:

fn some_function(t: T, u: U)

可以简化成:

fn some_function(t: T, u: U) -> i32
   where T: Display + Clone,
         U: Clone + Debug

在了解这个语法之后,泛型章节中的”取最大值”案例就可以真正实现了:

实例

trait Comparable {
   fn compare(&self, object: &Self) -> i8;
}

fn max(array: &[T]) -> &T {
   let mut max_index = 0;
   let mut i = 1;
   while i len() {
       if array[i].compare(&array[max_index]) > 0 {
           max_index = i;
       }
       i += 1;
   }
   &array[max_index]
}

impl Comparable for f64 {
   fn compare(&self, object: &f64) -> i8 {
       if &self > &object { 1 }
       else if &self == &object { 0 }
       else { -1 }
   }
}

fn main() {
   let arr = [1.0, 3.0, 5.0, 4.0, 2.0];
   println!("maximum of arr is {}", max(&arr));
}

运行结果:

maximum of arr is 5

Tip: 由于需要声明 compare 函数的第二参数必须与实现该特性的类型相同,所以 Self (注意大小写)关键字就代表了当前类型(不是实例)本身。

特性做返回值

特性做返回值格式如下:

实例

fn person() -> impl Descriptive {
   Person {
       name: String::from("Cali"),
       age: 24
   }
}

但是有一点,特性做返回值只接受实现了该特性的对象做返回值且在同一个函数中所有可能的返回值类型必须完全一样。比如结构体 A 与结构体 B 都实现了特性 Trait,下面这个函数就是错误的:

实例

fn some_function(bool bl) -> impl Descriptive {
   if bl {
       return A {};
   } else {
       return B {};
   }
}

有条件实现方法

impl 功能十分强大,我们可以用它实现类的方法。但对于泛型类来说,有时我们需要区分一下它所属的泛型已经实现的方法来决定它接下来该实现的方法:

struct A {}

impl A {
   fn d(&self) {}
}

这段代码声明了 A 类型必须在 T 已经实现 B 和 C 特性的前提下才能有效实现此 impl 块。

以上就是良许教程网为各位朋友分享的Linux系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !

img
本文由 良许Linux教程网 发布,可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
良许

作者: 良许

良许,世界500强企业Linux开发工程师,公众号【良许Linux】的作者,全网拥有超30W粉丝。个人标签:创业者,CSDN学院讲师,副业达人,流量玩家,摄影爱好者。
上一篇
下一篇

发表评论

邮箱地址不会被公开。 必填项已用*标注

联系我们

联系我们

公众号:良许Linux

在线咨询: QQ交谈

邮箱: yychuyu@163.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部