rust 泛型和特征(二)

简介: rust 泛型和特征

rust 泛型和特征(一)https://developer.aliyun.com/article/1391933


函数返回中的 impl Trait

可以通过 impl Trait 来说明一个函数返回了一个类型,该类型实现了某个特征:

fn returns_summarizable() -> impl Summary {
    Weibo {
        username: String::from("sunface"),
        content: String::from(
            "m1 max太厉害了,电脑再也不会卡",
        )
    }
}

因为 Weibo 实现了 Summary,因此这里可以用它来作为返回值。要注意的是,虽然我们知道这里是一个 Weibo 类型,但是对于 returns_summarizable 的调用者而言,他只知道返回了一个实现了 Summary 特征的对象,但是并不知道返回了一个 Weibo 类型。

这种 impl Trait 形式的返回值,在一种场景下非常非常有用,那就是返回的真实类型非常复杂,你不知道该怎么声明时(毕竟 Rust 要求你必须标出所有的类型),此时就可以用 impl Trait 的方式简单返回。例如,闭包和迭代器就是很复杂,只有编译器才知道那玩意的真实类型,如果让你写出来它们的具体类型,估计内心有一万只草泥马奔腾,好在你可以用 impl Iterator 来告诉调用者,返回了一个迭代器,因为所有迭代器都会实现 Iterator 特征。

但是这种返回值方式有一个很大的限制:只能有一个具体的类型,例如:

fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        Post {
            title: String::from(
                "Penguins win the Stanley Cup Championship!",
            ),
            author: String::from("Iceburgh"),
            content: String::from(
                "The Pittsburgh Penguins once again are the best \
                 hockey team in the NHL.",
            ),
        }
    } else {
        Weibo {
            username: String::from("horse_ebooks"),
            content: String::from(
                "of course, as you probably already know, people",
            ),
        }
    }
}
通过 derive 派生特征

形如 #[derive(Debug)] 的代码已经出现了很多次,这种是一种特征派生语法,被 derive 标记的对象会自动实现对应的默认特征代码,继承相应的功能。

例如 Debug 特征,它有一套自动实现的默认代码,当你给一个结构体标记后,就可以使用 println!(“{:?}”, s) 的形式打印该结构体的对象。

再如 Copy 特征,它也有一套自动实现的默认代码,当标记到一个类型上时,可以让这个类型自动实现 Copy 特征,进而可以调用 copy 方法,进行自我复制。

总之,derive 派生出来的是 Rust 默认给我们提供的特征,在开发过程中极大的简化了自己手动实现相应特征的需求,当然,如果你有特殊的需求,还可以自己手动重载该实现。

调用方法需要引入特征

在一些场景中,使用 as 关键字做类型转换会有比较大的限制,因为你想要在类型转换上拥有完全的控制,例如处理转换错误,那么你将需要 TryInto:

use std::convert::TryInto;
fn main() {
  let a: i32 = 10;
  let b: u16 = 100;
  let b_ = b.try_into()
            .unwrap();
  if a < b_ {
    println!("Ten is less than one hundred.");
  }
}

特征对象

pub struct Button {
    pub width: u32,
    pub height: u32,
    pub label: String,
}
impl Draw for Button {
    fn draw(&self) {
        // 绘制按钮的代码
    }
}
struct SelectBox {
    width: u32,
    height: u32,
    options: Vec<String>,
}
impl Draw for SelectBox {
    fn draw(&self) {
        // 绘制SelectBox的代码
    }
}

深入了解特征

关联类型

关联类型是在特征定义的语句块中,申明一个自定义类型,这样就可以在特征的方法签名中使用该类型:

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

以上是标准库中的迭代器特征 Iterator,它有一个 Item 关联类型,用于替代遍历的值的类型。

同时,next 方法也返回了一个 Item 类型,不过使用 Option 枚举进行了包裹,假如迭代器中的值是 i32 类型,那么调用 next 方法就将获取一个 Option 的值。

impl Iterator for Counter {
    type Item = u32;
    fn next(&mut self) -> Option<Self::Item> {
        // --snip--
    }
}
fn main() {
    let c = Counter{..}
    c.next()
}

在上述代码中,我们为 Counter 类型实现了 Iterator 特征,变量 c 是特征 Iterator 的实例,也是 next 方法的调用者。 结合之前的黑体内容可以得出:对于 next 方法而言,Self 是调用者 c 的具体类型: Counter,而 Self::Item 是 Counter 中定义的 Item 类型: u32。

pub trait Iterator<Item> {
    fn next(&mut self) -> Option<Item>;
}

答案其实很简单,为了代码的可读性,当你使用了泛型后,你需要在所有地方都写 Iterator,而使用了关联类型,你只需要写 Iterator,当类型定义复杂时,这种写法可以极大的增加可读性:

pub trait CacheableItem: Clone + Default + fmt::Debug + Decodable + Encodable {
  type Address: AsRef<[u8]> + Clone + fmt::Debug + Eq + Hash;
  fn is_null(&self) -> bool;
}

例如上面的代码,Address 的写法自然远比 AsRef<[u8]> + Clone + fmt::Debug + Eq + Hash 要简单的多,而且含义清晰。

再例如,如果使用泛型,你将得到以下的代码:

trait Container<A,B> {
    fn contains(&self,a: A,b: B) -> bool;
}
fn difference<A,B,C>(container: &C) -> i32
  where
    C : Container<A,B> {...}

可以看到,由于使用了泛型,导致函数头部也必须增加泛型的声明,而使用关联类型,将得到可读性好得多的代码:

trait Container{
    type A;
    type B;
    fn contains(&self, a: &Self::A, b: &Self::B) -> bool;
}
fn difference<C: Container>(container: &C) {}
默认泛型类型参数

当使用泛型类型参数时,可以为其指定一个默认的具体类型,例如标准库中的 std::ops::Add 特征:

trait Add<RHS=Self> {
    type Output;
    fn add(self, rhs: RHS) -> Self::Output;
}

它有一个泛型参数 RHS,但是与我们以往的用法不同,这里它给 RHS 一个默认值,也就是当用户不指定 RHS 时,默认使用两个同样类型的值进行相加,然后返回一个关联类型 Output。

可能上面那段不太好理解,下面我们用代码来举例:

use std::ops::Add;
#[derive(Debug, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}
impl Add for Point {
    type Output = Point;
    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}
fn main() {
    assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
               Point { x: 3, y: 3 });
}

上面的代码主要干了一件事,就是为 Point 结构体提供 + 的能力,这就是运算符重载,不过 Rust 并不支持创建自定义运算符,你也无法为所有运算符进行重载,目前来说,只有定义在 std::ops 中的运算符才能进行重载。

跟 + 对应的特征是 std::ops::Add,我们在之前也看过它的定义 trait Add,但是上面的例子中并没有为 Point 实现 Add 特征,而是实现了 Add 特征(没有默认泛型类型参数),这意味着我们使用了 RHS 的默认类型,也就是 Self。换句话说,我们这里定义的是两个相同的 Point 类型相加,因此无需指定 RHS。

与上面的例子相反,下面的例子,我们来创建两个不同类型的相加:

use std::ops::Add;
struct Millimeters(u32);
struct Meters(u32);
impl Add<Meters> for Millimeters {
    type Output = Millimeters;
    fn add(self, other: Meters) -> Millimeters {
        Millimeters(self.0 + (other.0 * 1000))
    }
}

这里,是进行 Millimeters + Meters 两种数据类型的 + 操作,因此此时不能再使用默认的 RHS,否则就会变成 Millimeters + Millimeters 的形式。使用 Add 可以将 RHS 指定为 Meters,那么 fn add(self, rhs: RHS) 自然而言的变成了 Millimeters 和 Meters 的相加。

默认类型参数主要用于两个方面:

  1. 减少实现的样板代码
  2. 扩展类型但是无需大幅修改现有的代码


rust 泛型和特征(三)https://developer.aliyun.com/article/1391936

相关文章
|
6月前
|
Rust 编译器
Rust 泛型
Rust 泛型
56 1
|
6月前
|
存储 Rust 程序员
【一起学Rust | 基础篇 | rust新特性】Rust 1.65.0——泛型关联类型、let-else语句
【一起学Rust | 基础篇 | rust新特性】Rust 1.65.0——泛型关联类型、let-else语句
102 0
|
2月前
|
Rust 编译器 容器
30天拿下Rust之泛型
30天拿下Rust之泛型
25 0
|
6月前
|
Rust 编译器
rust 泛型和特征(三)
rust 泛型和特征
73 0
|
6月前
|
Rust
rust 泛型和特征(一)
rust 泛型和特征
76 0
|
Rust 编译器
Rust之泛型特化
Rust之泛型特化
|
Rust 安全 Java
Rust学习笔记之泛型、trait 与生命周期
1. 泛型大补汤 推荐阅读指数 ⭐️⭐️⭐️⭐️ 2. 泛型数据类型 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️ 3. trait:定义共享的行为 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️ 4. 生命周期与引用有效性 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️
107 0
Rust学习笔记之泛型、trait 与生命周期
|
存储 Rust 安全
【Rust 中级教程】 01 泛型
【Rust 中级教程】 01 泛型
【Rust 中级教程】 01 泛型
|
存储 Rust 编译器
【Rust 中级教程】 02 结构体与泛型
【Rust 中级教程】 02 结构体与泛型
|
Rust 编译器
Rust 泛型类型
本文介绍了 Rust 中泛型的概念:在定义时不确定类型,再使用时再传入具体的类型。通过泛型能大大简化代码量,写出更优雅,更简洁的代码。在定义泛型时为泛型指定一个约束,可以提高安全性和可靠性。
160 0
下一篇
无影云桌面