当前位置: 首页 > 文档资料 > RustPrimer 中文版 >

Rust快速入门 - 特性

优质
小牛编辑
130浏览
2023-12-01

特性与接口

为了描述类型可以实现的抽象接口 (abstract interface),
Rust引入了特性 (trait) 来定义函数类型签名 (function type signature):

  1. trait HasArea {
  2. fn area(&self) -> f64;
  3. }
  4. struct Circle {
  5. x: f64,
  6. y: f64,
  7. radius: f64,
  8. }
  9. impl HasArea for Circle {
  10. fn area(&self) -> f64 {
  11. std::f64::consts::PI * (self.radius * self.radius)
  12. }
  13. }
  14. struct Square {
  15. x: f64,
  16. y: f64,
  17. side: f64,
  18. }
  19. impl HasArea for Square {
  20. fn area(&self) -> f64 {
  21. self.side * self.side
  22. }
  23. }
  24. fn print_area<T: HasArea>(shape: T) {
  25. println!("This shape has an area of {}", shape.area());
  26. }

其中函数print_area()中的泛型参数T被添加了一个名为HasArea的特性约束 (trait constraint),
用以确保任何实现了HasArea的类型将拥有一个.area()方法。
如果需要多个特性限定 (multiple trait bounds),可以使用+

  1. use std::fmt::Debug;
  2. fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
  3. x.clone();
  4. y.clone();
  5. println!("{:?}", y);
  6. }
  7. fn bar<T, K>(x: T, y: K)
  8. where T: Clone,
  9. K: Clone + Debug
  10. {
  11. x.clone();
  12. y.clone();
  13. println!("{:?}", y);
  14. }

其中第二个例子使用了更灵活的where从句,它还允许限定的左侧可以是任意类型,
而不仅仅是类型参数。

定义在特性中的方法称为默认方法 (default method),可以被该特性的实现覆盖。
此外,特性之间也可以存在继承 (inheritance):

  1. trait Foo {
  2. fn foo(&self);
  3. // default method
  4. fn bar(&self) { println!("We called bar."); }
  5. }
  6. // inheritance
  7. trait FooBar : Foo {
  8. fn foobar(&self);
  9. }
  10. struct Baz;
  11. impl Foo for Baz {
  12. fn foo(&self) { println!("foo"); }
  13. }
  14. impl FooBar for Baz {
  15. fn foobar(&self) { println!("foobar"); }
  16. }

如果两个不同特性的方法具有相同的名称,可以使用通用函数调用语法 (universal function call syntax):

  1. // short-hand form
  2. Trait::method(args);
  3. // expanded form
  4. <Type as Trait>::method(args);

关于实现特性的几条限制:

  • 如果一个特性不在当前作用域内,它就不能被实现。
  • 不管是特性还是impl,都只能在当前的包装箱内起作用。
  • 带有特性约束的泛型函数使用单态化实现 (monomorphization),
    所以它是静态派分的 (statically dispatched)。

下面列举几个非常有用的标准库特性:

  • Drop提供了当一个值退出作用域后执行代码的功能,它只有一个drop(&mut self)方法。
  • Borrow用于创建一个数据结构时把拥有和借用的值看作等同。
  • AsRef用于在泛型中把一个值转换为引用。
  • Deref<Target=T>用于把&U类型的值自动转换为&T类型。
  • Iterator用于在集合 (collection) 和惰性值生成器 (lazy value generator) 上实现迭代器。
  • Sized用于标记运行时长度固定的类型,而不定长的切片和特性必须放在指针后面使其运行时长度已知,
    比如&[T]Box<Trait>

泛型和多态

泛型 (generics) 在类型理论中称作参数多态 (parametric polymorphism),
意为对于给定参数可以有多种形式的函数或类型。先看Rust中的一个泛型例子:

Option在rust标准库中的定义:

  1. enum Option<T> {
  2. Some(T),
  3. None,
  4. }

Option的典型用法:

  1. let x: Option<i32> = Some(5);
  2. let y: Option<f64> = Some(5.0f64);

其中<T>部分表明它是一个泛型数据类型。当然,泛型参数也可以用于函数参数和结构体域:

  1. // generic functions
  2. fn make_pair<T, U>(a: T, b: U) -> (T, U) {
  3. (a, b)
  4. }
  5. let couple = make_pair("man", "female");
  6. // generic structs
  7. struct Point<T> {
  8. x: T,
  9. y: T,
  10. }
  11. let int_origin = Point { x: 0, y: 0 };
  12. let float_origin = Point { x: 0.0, y: 0.0 };

对于多态函数,存在两种派分 (dispatch) 机制:静态派分和动态派分。
前者类似于C++的模板,Rust会生成适用于指定类型的特殊函数,然后在被调用的位置进行替换,
好处是允许函数被内联调用,运行比较快,但是会导致代码膨胀 (code bloat);
后者类似于Java或Go的interface,Rust通过引入特性对象 (trait object) 来实现,
在运行期查找虚表 (vtable) 来选择执行的方法。特性对象&Foo具有和特性Foo相同的名称,
通过转换 (casting) 或者强制多态化 (coercing) 一个指向具体类型的指针来创建。

当然,特性也可以接受泛型参数。但是,往往更好的处理方式是使用关联类型 (associated type):

  1. // use generic parameters
  2. trait Graph<N, E> {
  3. fn has_edge(&self, &N, &N) -> bool;
  4. fn edges(&self, &N) -> Vec<E>;
  5. }
  6. fn distance<N, E, G: Graph<N, E>>(graph: &G, start: &N, end: &N) -> u32 {
  7. }
  8. // use associated types
  9. trait Graph {
  10. type N;
  11. type E;
  12. fn has_edge(&self, &Self::N, &Self::N) -> bool;
  13. fn edges(&self, &Self::N) -> Vec<Self::E>;
  14. }
  15. fn distance<G: Graph>(graph: &G, start: &G::N, end: &G::N) -> uint {
  16. }
  17. struct Node;
  18. struct Edge;
  19. struct SimpleGraph;
  20. impl Graph for SimpleGraph {
  21. type N = Node;
  22. type E = Edge;
  23. fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
  24. }
  25. fn edges(&self, n: &Node) -> Vec<Edge> {
  26. }
  27. }
  28. let graph = SimpleGraph;
  29. let object = Box::new(graph) as Box<Graph<N=Node, E=Edge>>;