Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

1.16 泛型trait:泛型(类型参数)trait、关联类型trait

这篇文章以概念性和建议性的文字偏多,需要你对泛型类型参数和关联类型有一定了解。

1.16.1. trait的泛型方式

trait的泛型方式有两种:

  • 泛型类型参数。例:trait Foo<T>
  • 关联类型。例:trait Foo{type Bar;}

两者的区别在于:

  • 使用关联类型这种形式的效果就是对于指定类型的trait只有一个实现
  • 使用泛型参数类型则可以有多个实现

这里有一个简单的建议:可以的话尽量使用关联类型。

1.16.2. 泛型(类型参数)trait

泛型trait要求必须指定所有的泛型类型参数,并重复写这些参数的约束(bounds)

这么写维护起来会难一些。比如说如果添加泛型类型参数到某个trait,该trait的所有实现者都必须更新代码。

这种形式还可能会导致针对给定的类型,一个trait有多重实现的问题。编译器会更难推断你想要的到底是trait的哪个实例。有时候不得不调用类似FromIterator::<u32>::from_iter这样可以消除歧义的函数。

这个特性有的时候也会是一个优点,例如:

  • impl PatialEq<BookFormat> for Book,其中BookFormat就可以是不同的类型
  • 可以同时实现FromIterator<T>FromIterator<&T> where T:Clone

1.16.3. 关联类型trait

我们以一段代码为例:

#![allow(unused)]
fn main() {
trait Contains {
	type A;
	type B;
	
	//Updates syntax to refer to these new types generically
	fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
}
}

使用关联类型:

  • 编译器只需要知道实现trait的类型
  • 约束(bound) 可以完全位于trait本身,不必重复使用
  • 未来再添加关联类型也不影响用户使用
  • 具体的类型会决定trait内关联类型的类型,无需使用消除歧义的函数,看个例子:
#![allow(unused)]
fn main() {
impl Contains for Container {
    // Specify what types `A` and `B` are. If the `input` type
    // is `Container(i32, i32)`, the `output` types are determined
    // as `i32` and `i32`.
    type A = i32;
    type B = i32;

    // `&Self::A` and `&Self::B` are also valid here.
    fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
        (&self.0 == number_1) && (&self.1 == number_2)
    }

    // Grab the first number.
    fn first(&self) -> i32 { self.0 }

    // Grab the last number.
    fn last(&self) -> i32 { self.1 }
}
}
  • 这个例子写了为Container类型实现Contains trait
  • Container类型的Contains trait中的关联类型通过type A = i32;type B = i32;这两行代码决定

不可以对多个目标(Target)类型来实现Deref trait

看一下Deref trait的源代码:

#![allow(unused)]
fn main() {
pub trait Deref {
	type Target: ?Sized;

	fn deref(&self) -> &Self::Target;
}
}
  • type Target: ?Sized;中的Target就是我们说的目标(Target)类型

我们随便写一个Deref trait的实现来说明一下:

#![allow(unused)]
fn main() {
use std::ops::Deref;

struct Wrapper {
    value: String,
}

impl Deref for Wrapper {
    type Target = String;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}
}

上面代码中,Wrapper只能解引用为String。但如果你想让Wrapper同时解引用为Stringstr,Rust不允许你再实现Deref,因为Target只能有一个具体类型。

也就是说,这么写是非法的:

#![allow(unused)]
fn main() {
use std::ops::Deref;

struct Wrapper {
    value: String,
}

// 第一次实现 Deref,Target = String
impl Deref for Wrapper {
    type Target = String;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

// 这是非法的,Rust 不允许对同一个类型 `Wrapper` 进行第二次 `Deref` 实现
impl Deref for Wrapper {
    type Target = str;  // 冲突,Rust 不能推断哪个 `Target` 生效

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}
}

不可以使用多个Item来实现Iterator trait

其原因与不可以对多个目标(Target)类型来实现Deref trait一样,主要涉及关联类型的唯一性Rust 编译器的推断规则