跳到主要内容

泛型

泛型是将类型和功能概括到更广泛的情况的主题。这对于以多种方式减少代码重复非常有用,但可能需要相当复杂的语法。也就是说,泛型需要非常小心地指定泛型类型实际上被认为是有效的类型。泛型最简单且最常见的用途是用于类型参数。

通过使用尖括号和大驼峰式大小写将类型参数指定为泛型: <Aaa, Bbb, ...> 。 “通用类型参数”通常表示为 <T> 。在 Rust 中,“泛型”还描述了任何接受一个或多个泛型类型参数 <T> 的事物。指定为泛型类型参数的任何类型都是泛型,其他所有类型都是具体的(非泛型)。

例如,定义一个名为 foo 的泛型函​​数,它接受任何类型的参数 T :

fn foo<T>(arg: T) { ... }

由于 T 已使用 <T> 指定为泛型类型参数,因此在此处用作 (arg: T) 时,它被视为泛型。即使 T 之前已被定义为 struct ,情况也是如此。

此示例显示了一些实际语法:

// A concrete type `A`.
struct A;

// In defining the type `Single`, the first use of `A` is not preceded by `<A>`.
// Therefore, `Single` is a concrete type, and `A` is defined as above.
struct Single(A);
// ^ Here is `Single`s first use of the type `A`.

// Here, `<T>` precedes the first use of `T`, so `SingleGen` is a generic type.
// Because the type parameter `T` is generic, it could be anything, including
// the concrete type `A` defined at the top.
struct SingleGen<T>(T);

fn main() {
// `Single` is concrete and explicitly takes `A`.
let _s = Single(A);

// Create a variable `_char` of type `SingleGen<char>`
// and give it the value `SingleGen('a')`.
// Here, `SingleGen` has a type parameter explicitly specified.
let _char: SingleGen<char> = SingleGen('a');

// `SingleGen` can also have a type parameter implicitly specified:
let _t = SingleGen(A); // Uses `A` defined at the top.
let _i32 = SingleGen(6); // Uses `i32`.
let _char = SingleGen('a'); // Uses `char`.
}
fn add<T>(a:T, b:T)->T{
a+b // 编译会报错,因为不是所有的类型都能相加,需要使用trait对T进行限制,我们称之为特征约束
}

struct Point<T,U> { // struct中使用泛型
x: T,
y: U,
}
impl<T,U> Point<T,U> { // 给泛型struct添加method
fn new(x: T, y: U) -> Point<T,U> {
Point {
x,
y
}
}
}
fn main() {
let res = add(1,2); // 对a赋值时,T就被确定为整数类型
print!("a+b={}", res);
}

性能

Rust 是在编译期为泛型对应的多个类型,生成各自的代码,(相当于编译器帮你写了多份代码),因此损失了编译速度和增大了最终生成文件的大小,但是对性能不影响。

函数

相同的规则集可以应用于函数:类型 T 当前面有 <T>时变得通用。

使用泛型函数有时需要显式指定类型参数。如果在返回类型为泛型的情况下调用函数,或者编译器没有足够的信息来推断必要的类型参数,则可能会出现这种情况。

具有显式指定类型参数的函数调用如下所示: fun::<A, B, ...>()

struct A;          // Concrete type `A`.
struct S(A); // Concrete type `S`.
struct SGen<T>(T); // Generic type `SGen`.

// The following functions all take ownership of the variable passed into
// them and immediately go out of scope, freeing the variable.

// Define a function `reg_fn` that takes an argument `_s` of type `S`.
// This has no `<T>` so this is not a generic function.
fn reg_fn(_s: S) {}

// Define a function `gen_spec_t` that takes an argument `_s` of type `SGen<T>`.
// It has been explicitly given the type parameter `A`, but because `A` has not
// been specified as a generic type parameter for `gen_spec_t`, it is not generic.
fn gen_spec_t(_s: SGen<A>) {}

// Define a function `gen_spec_i32` that takes an argument `_s` of type `SGen<i32>`.
// It has been explicitly given the type parameter `i32`, which is a specific type.
// Because `i32` is not a generic type, this function is also not generic.
fn gen_spec_i32(_s: SGen<i32>) {}

// Define a function `generic` that takes an argument `_s` of type `SGen<T>`.
// Because `SGen<T>` is preceded by `<T>`, this function is generic over `T`.
fn generic<T>(_s: SGen<T>) {}

fn main() {
// Using the non-generic functions
reg_fn(S(A)); // Concrete type.
gen_spec_t(SGen(A)); // Implicitly specified type parameter `A`.
gen_spec_i32(SGen(6)); // Implicitly specified type parameter `i32`.

// Explicitly specified type parameter `char` to `generic()`.
generic::<char>(SGen('a'));

// Implicitly specified type parameter `char` to `generic()`.
generic(SGen('c'));
}

implementation

与函数类似,实现需要注意保持通用性。

struct S; // Concrete type `S`
struct GenericVal<T>(T); // Generic type `GenericVal`

// impl of GenericVal where we explicitly specify type parameters:
impl GenericVal<f32> {} // Specify `f32`
impl GenericVal<S> {} // Specify `S` as defined above

// `<T>` Must precede the type to remain generic
impl<T> GenericVal<T> {}
struct Val {
val: f64,
}

struct GenVal<T> {
gen_val: T,
}

// impl of Val
impl Val {
fn value(&self) -> &f64 {
&self.val
}
}

// impl of GenVal for a generic type `T`
impl<T> GenVal<T> {
fn value(&self) -> &T {
&self.gen_val
}
}

fn main() {
let x = Val { val: 3.0 };
let y = GenVal { gen_val: 3i32 };

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

trait

当然 trait 也可以是通用的。这里我们定义一个将 Drop trait 重新实现为 drop 本身和输入的通用方法。

// Non-copyable types.
struct Empty;
struct Null;

// A trait generic over `T`.
trait DoubleDrop<T> {
// Define a method on the caller type which takes an
// additional single parameter `T` and does nothing with it.
fn double_drop(self, _: T);
}

// Implement `DoubleDrop<T>` for any generic parameter `T` and
// caller `U`.
impl<T, U> DoubleDrop<T> for U {
// This method takes ownership of both passed arguments,
// deallocating both.
fn double_drop(self, _: T) {}
}

fn main() {
let empty = Empty;
let null = Null;

// Deallocate `empty` and `null`.
empty.double_drop(null);

//empty;
//null;
// ^ TODO: Try uncommenting these lines.
}

bounds

使用泛型时,类型参数通常必须使用特征作为边界来规定类型实现的功能。例如,以下示例使用特征 Display 进行打印,因此它需要 T 与 Display 绑定;也就是说, T 必须实现 Display 。

// Define a function `printer` that takes a generic type `T` which
// must implement trait `Display`.
fn printer<T: Display>(t: T) {
println!("{}", t);
}

边界将泛型限制为符合边界的类型。那是:

struct S<T: Display>(T);

// Error! `Vec<T>` does not implement `Display`. This
// specialization will fail.
let s = S(vec![1]);

边界的另一个作用是允许泛型实例访问边界中指定的特征的方法。例如:

// A trait which implements the print marker: `{:?}`.
use std::fmt::Debug;

trait HasArea {
fn area(&self) -> f64;
}

impl HasArea for Rectangle {
fn area(&self) -> f64 { self.length * self.height }
}

#[derive(Debug)]
struct Rectangle { length: f64, height: f64 }
#[allow(dead_code)]
struct Triangle { length: f64, height: f64 }

// The generic `T` must implement `Debug`. Regardless
// of the type, this will work properly.
fn print_debug<T: Debug>(t: &T) {
println!("{:?}", t);
}

// `T` must implement `HasArea`. Any type which meets
// the bound can access `HasArea`'s function `area`.
fn area<T: HasArea>(t: &T) -> f64 { t.area() }

fn main() {
let rectangle = Rectangle { length: 3.0, height: 4.0 };
let _triangle = Triangle { length: 3.0, height: 4.0 };

print_debug(&rectangle);
println!("Area: {}", area(&rectangle));

//print_debug(&_triangle);
//println!("Area: {}", area(&_triangle));
// ^ TODO: Try uncommenting these.
// | Error: Does not implement either `Debug` or `HasArea`.
}

作为补充说明,在某些情况下, where 子句也可用于应用边界,以更具表现力。

边界工作方式的结果是,即使 trait 不包含任何功能,您仍然可以将其用作边界。 Eq 和 Copy 是 std 库中此类 trait 的示例。

struct Cardinal;
struct BlueJay;
struct Turkey;

trait Red {}
trait Blue {}

impl Red for Cardinal {}
impl Blue for BlueJay {}

// These functions are only valid for types which implement these
// traits. The fact that the traits are empty is irrelevant.
fn red<T: Red>(_: &T) -> &'static str { "red" }
fn blue<T: Blue>(_: &T) -> &'static str { "blue" }

fn main() {
let cardinal = Cardinal;
let blue_jay = BlueJay;
let _turkey = Turkey;

// `red()` won't work on a blue jay nor vice versa
// because of the bounds.
println!("A cardinal is {}", red(&cardinal));
println!("A blue jay is {}", blue(&blue_jay));
//println!("A turkey is {}", red(&_turkey));
// ^ TODO: Try uncommenting this line.
}

MultiBounds

边界工作方式的结果是,即使 trait 不包含任何功能,您仍然可以将其用作边界。 Eq 和 Copy 是 std 库中此类 trait 的示例。

use std::fmt::{Debug, Display};

fn compare_prints<T: Debug + Display>(t: &T) {
println!("Debug: `{:?}`", t);
println!("Display: `{}`", t);
}

fn compare_types<T: Debug, U: Debug>(t: &T, u: &U) {
println!("t: `{:?}`", t);
println!("u: `{:?}`", u);
}

fn main() {
let string = "words";
let array = [1, 2, 3];
let vec = vec![1, 2, 3];

compare_prints(&string);
//compare_prints(&array);
// TODO ^ Try uncommenting this.

compare_types(&array, &vec);
}

where子句

边界也可以在开始 { 之前使用 where 子句来表达,而不是在类型第一次提及时。此外, where 子句可以将边界应用于任意类型,而不仅仅是类型参数。

在某些情况下 where 子句很有用:

当单独指定泛型类型和界限时会更清晰:

impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {}

// Expressing bounds with a `where` clause
impl <A, D> MyTrait<A, D> for YourType where
A: TraitB + TraitC,
D: TraitE + TraitF {}

使用 where 子句比使用普通语法更具表现力。此示例中的 impl 不能在没有 where 子句的情况下直接表达:

use std::fmt::Debug;

trait PrintInOption {
fn print_in_option(self);
}

// Because we would otherwise have to express this as `T: Debug` or
// use another method of indirect approach, this requires a `where` clause:
impl<T> PrintInOption for T where
Option<T>: Debug {
// We want `Option<T>: Debug` as our bound because that is what's
// being printed. Doing otherwise would be using the wrong bound.
fn print_in_option(self) {
println!("{:?}", Some(self));
}
}

fn main() {
let vec = vec![1, 2, 3];

vec.print_in_option();
}

New type idiom

newtype 习惯用法在编译时保证向程序提供正确类型的值。

例如,检查年龄的年龄验证函数必须指定 Years 类型的值。

struct Years(i64);

struct Days(i64);

impl Years {
pub fn to_days(&self) -> Days {
Days(self.0 * 365)
}
}


impl Days {
/// truncates partial years
pub fn to_years(&self) -> Years {
Years(self.0 / 365)
}
}

fn old_enough(age: &Years) -> bool {
age.0 >= 18
}

fn main() {
let age = Years(5);
let age_days = age.to_days();
println!("Old enough {}", old_enough(&age));
println!("Old enough {}", old_enough(&age_days.to_years()));
// println!("Old enough {}", old_enough(&age_days));
}

取消注释最后一个打印语句以观察提供的类型必须是 Years 。

要获取 newtype 的值作为基本类型,您可以使用元组或解构语法,如下所示:

struct Years(i64);

fn main() {
let years = Years(42);
let years_as_primitive_1: i64 = years.0; // Tuple
let Years(years_as_primitive_2) = years; // Destructuring
}

Associated items

“关联项”是指与各种类型的 item 有关的一组规则。它是 trait 泛型的扩展,并允许 trait 在内部定义新项目。

其中一项称为关联类型,当 trait 对其容器类型通用时,它提供更简单的使用模式。

对其容器类型通用的 trait 具有类型规范要求 - trait 的用户必须指定其所有通用类型。

在下面的示例中, Contains trait 允许使用泛型类型 A 和 B 。然后为 Container 类型实现该特征,为 A 和 B 指定 i32 ,以便它可以与 fn difference() 。

由于 Contains 是通用的,因此我们被迫显式声明 fn difference() 的所有通用类型。在实践中,我们想要一种方式来表达 A 和 B 由输入 C 确定。正如您将在下一节中看到的,关联类型恰恰提供了该功能。

struct Container(i32, i32);

// A trait which checks if 2 items are stored inside of container.
// Also retrieves first or last value.
trait Contains<A, B> {
fn contains(&self, _: &A, _: &B) -> bool; // Explicitly requires `A` and `B`.
fn first(&self) -> i32; // Doesn't explicitly require `A` or `B`.
fn last(&self) -> i32; // Doesn't explicitly require `A` or `B`.
}

impl Contains<i32, i32> for Container {
// True if the numbers stored are equal.
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 }
}

// `C` contains `A` and `B`. In light of that, having to express `A` and
// `B` again is a nuisance.
fn difference<A, B, C>(container: &C) -> i32 where
C: Contains<A, B> {
container.last() - container.first()
}

fn main() {
let number_1 = 3;
let number_2 = 10;

let container = Container(number_1, number_2);

println!("Does container contain {} and {}: {}",
&number_1, &number_2,
container.contains(&number_1, &number_2));
println!("First number: {}", container.first());
println!("Last number: {}", container.last());

println!("The difference is: {}", difference(&container));
}

关联类型

“关联类型”的使用通过将内部类型本地移动到特征中作为输出类型来提高代码的整体可读性。 trait 定义的语法如下:

// `A` and `B` are defined in the trait via the `type` keyword.
// (Note: `type` in this context is different from `type` when used for
// aliases).
trait Contains {
type A;
type B;

// Updated syntax to refer to these new types generically.
fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
}

请注意,使用 trait Contains 的函数不再需要表达 A 或 B :

// Without using associated types
fn difference<A, B, C>(container: &C) -> i32 where
C: Contains<A, B> { ... }

// Using associated types
fn difference<C: Contains>(container: &C) -> i32 { ... }

让我们使用关联类型重写上一节中的示例:

struct Container(i32, i32);

// A trait which checks if 2 items are stored inside of container.
// Also retrieves first or last value.
trait Contains {
// Define generic types here which methods will be able to utilize.
type A;
type B;

fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
fn first(&self) -> i32;
fn last(&self) -> i32;
}

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 }
}

fn difference<C: Contains>(container: &C) -> i32 {
container.last() - container.first()
}

fn main() {
let number_1 = 3;
let number_2 = 10;

let container = Container(number_1, number_2);

println!("Does container contain {} and {}: {}",
&number_1, &number_2,
container.contains(&number_1, &number_2));
println!("First number: {}", container.first());
println!("Last number: {}", container.last());

println!("The difference is: {}", difference(&container));
}

幻想类型参数

幻像类型参数是一种在运行时不显示的参数,但在编译时(且仅)静态检查。

数据类型可以使用额外的泛型类型参数来充当标记或在编译时执行类型检查。这些额外参数不保存存储值,并且没有运行时行为。

在下面的示例中,我们将 std::marker::PhantomData 与幻像类型参数概念结合起来,创建包含不同数据类型的元组。

use std::marker::PhantomData;

// A phantom tuple struct which is generic over `A` with hidden parameter `B`.
#[derive(PartialEq)] // Allow equality test for this type.
struct PhantomTuple<A, B>(A, PhantomData<B>);

// A phantom type struct which is generic over `A` with hidden parameter `B`.
#[derive(PartialEq)] // Allow equality test for this type.
struct PhantomStruct<A, B> { first: A, phantom: PhantomData<B> }

// Note: Storage is allocated for generic type `A`, but not for `B`.
// Therefore, `B` cannot be used in computations.

fn main() {
// Here, `f32` and `f64` are the hidden parameters.
// PhantomTuple type specified as `<char, f32>`.
let _tuple1: PhantomTuple<char, f32> = PhantomTuple('Q', PhantomData);
// PhantomTuple type specified as `<char, f64>`.
let _tuple2: PhantomTuple<char, f64> = PhantomTuple('Q', PhantomData);

// Type specified as `<char, f32>`.
let _struct1: PhantomStruct<char, f32> = PhantomStruct {
first: 'Q',
phantom: PhantomData,
};
// Type specified as `<char, f64>`.
let _struct2: PhantomStruct<char, f64> = PhantomStruct {
first: 'Q',
phantom: PhantomData,
};

// Compile-time Error! Type mismatch so these cannot be compared:
// println!("_tuple1 == _tuple2 yields: {}",
// _tuple1 == _tuple2);

// Compile-time Error! Type mismatch so these cannot be compared:
// println!("_struct1 == _struct2 yields: {}",
// _struct1 == _struct2);
}

可以通过使用幻像类型参数实现 Add 来检查有用的单位转换方法。下面检查 Add trait :

// This construction would impose: `Self + RHS = Output`
// where RHS defaults to Self if not specified in the implementation.
pub trait Add<RHS = Self> {
type Output;

fn add(self, rhs: RHS) -> Self::Output;
}

// `Output` must be `T<U>` so that `T<U> + T<U> = T<U>`.
impl<U> Add for T<U> {
type Output = T<U>;
...
}

整个实现

use std::ops::Add;
use std::marker::PhantomData;

/// Create void enumerations to define unit types.
#[derive(Debug, Clone, Copy)]
enum Inch {}
#[derive(Debug, Clone, Copy)]
enum Mm {}

/// `Length` is a type with phantom type parameter `Unit`,
/// and is not generic over the length type (that is `f64`).
///
/// `f64` already implements the `Clone` and `Copy` traits.
#[derive(Debug, Clone, Copy)]
struct Length<Unit>(f64, PhantomData<Unit>);

/// The `Add` trait defines the behavior of the `+` operator.
impl<Unit> Add for Length<Unit> {
type Output = Length<Unit>;

// add() returns a new `Length` struct containing the sum.
fn add(self, rhs: Length<Unit>) -> Length<Unit> {
// `+` calls the `Add` implementation for `f64`.
Length(self.0 + rhs.0, PhantomData)
}
}

fn main() {
// Specifies `one_foot` to have phantom type parameter `Inch`.
let one_foot: Length<Inch> = Length(12.0, PhantomData);
// `one_meter` has phantom type parameter `Mm`.
let one_meter: Length<Mm> = Length(1000.0, PhantomData);

// `+` calls the `add()` method we implemented for `Length<Unit>`.
//
// Since `Length` implements `Copy`, `add()` does not consume
// `one_foot` and `one_meter` but copies them into `self` and `rhs`.
let two_feet = one_foot + one_foot;
let two_meters = one_meter + one_meter;

// Addition works.
println!("one foot + one_foot = {:?} in", two_feet.0);
println!("one meter + one_meter = {:?} mm", two_meters.0);

// Nonsensical operations fail as they should:
// Compile-time Error: type mismatch.
//let one_feter = one_foot + one_meter;
}
Loading Comments...