泛型

我们可以使用泛型定义函数,结构体,枚举和方法

  1. fn foo<T>(x: T)
  2. fn bar<T>(x: T, y: T) // 兩個以上同型別參數
  3. fn baz<T, U>(x: T, y: U) //兩個以上同型別參數
  4. struct Foo<T> {
  5. x: T,
  6. y: T
  7. }
  8. impl<T> Foo<T> {
  9. fn do_something(x: T, ...) -> ... {
  10. // Implement method here
  11. }
  12. }
  13. impl<T> Bar for Foo<T> {
  14. fn method_from_bar(x: T, ...) -> ... {
  15. // Implement method here
  16. }
  17. }
  18. enum Option<T> {
  19. Some(T),
  20. None,
  21. }

泛型结构体

  1. struct Point<T, U> {
  2. x: T,
  3. y: U,
  4. }
  5. fn main() {
  6. let both_integer = Point { x: 5, y: 10 };
  7. let both_float = Point { x: 1.0, y: 4.0 };
  8. let integer_and_float = Point { x: 5, y: 4.0 };
  9. }

泛型枚举

  1. enum Result<T, E> {
  2. Ok(T),
  3. Err(E),
  4. }

泛型方法

  1. struct Point<T, U> {
  2. x: T,
  3. y: U,
  4. }
  5. impl<T, U> Point<T, U> {
  6. fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
  7. Point {
  8. x: self.x,
  9. y: other.y,
  10. }
  11. }
  12. }
  13. fn main() {
  14. let p1 = Point { x: 5, y: 10.4 };
  15. let p2 = Point { x: "Hello", y: 'c'};
  16. let p3 = p1.mixup(p2);
  17. println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
  18. }

泛型函数

  1. use std::ops::Add;
  2. #[derive(Debug)]
  3. struct Point {
  4. x: i32,
  5. y: i32,
  6. }
  7. // 为Point实现Add trait
  8. impl Add for Point {
  9. type Output = Point; //执行返回值类型为Point
  10. fn add(self, p: Point) -> Point {
  11. Point{
  12. x: self.x + p.x,
  13. y: self.y + p.y,
  14. }
  15. }
  16. }
  17. fn add<T: Add<T, Output=T>>(a:T, b:T) -> T {
  18. a + b
  19. }
  20. fn main() {
  21. println!("{}", add(100i32, 1i32));
  22. println!("{}", add(100.11f32, 100.22f32));
  23. let p1 = Point{x: 1, y: 1};
  24. let p2 = Point{x: 2, y: 2};
  25. println!("{:?}", add(p1, p2));
  26. }

输出: 101 200.33 Point { x: 3, y: 3 }

我们增加了自定义的类型,然后让add函数依然可以在上面工作。

  1. use std::ops::Add;
  2. #[derive(Debug)]
  3. struct Point<T: Add<T, Output = T>> { //限制类型T必须实现了Add trait,否则无法进行+操作。
  4. x: T,
  5. y: T,
  6. }
  7. impl<T: Add<T, Output = T>> Add for Point<T> {
  8. type Output = Point<T>;
  9. fn add(self, p: Point<T>) -> Point<T> {
  10. Point{
  11. x: self.x + p.x,
  12. y: self.y + p.y,
  13. }
  14. }
  15. }
  16. fn add<T: Add<T, Output=T>>(a:T, b:T) -> T {
  17. a + b
  18. }
  19. fn main() {
  20. let p1 = Point{x: 1.1f32, y: 1.1f32};
  21. let p2 = Point{x: 2.1f32, y: 2.1f32};
  22. println!("{:?}", add(p1, p2));
  23. let p3 = Point{x: 1i32, y: 1i32};
  24. let p4 = Point{x: 2i32, y: 2i32};
  25. println!("{:?}", add(p3, p4));
  26. }

输出: Point { x: 3.2, y: 3.2 } Point { x: 3, y: 3 }

我们不仅让自定义的Point类型支持了add操作,同时我们也为Point做了泛型化。

实际撰写泛型程式时,设定相关的trait相当重要,Rust需要足够的资讯来判断泛型中的变数是否能够执行特定的行为,而这个资讯是透过trait来指定

  1. use std::fmt;
  2. pub struct Point<T> where T: Copy + fmt::Display {
  3. x: T,
  4. y: T
  5. }
  6. impl<T> Point<T> where T: Copy + fmt::Display {
  7. pub fn new(x: T, y: T) -> Point<T> {
  8. Point::<T>{ x: x, y: y }
  9. }
  10. }
  11. impl<T> Point<T> where T: Copy + fmt::Display {
  12. pub fn x(&self) -> T {
  13. self.x
  14. }
  15. }
  16. impl<T> Point<T> where T: Copy + fmt::Display {
  17. pub fn y(&self) -> T {
  18. self.y
  19. }
  20. }
  21. impl<T> fmt::Display for Point<T> where T: Copy + fmt::Display {
  22. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  23. write!(f, "({}, {})", self.x(), self.y())
  24. }
  25. }
  26. fn main() {
  27. let p1 = Point::<i32>::new(3, 4);
  28. println!("{}", p1);
  29. let p2 = Point::<f64>::new(2.4, 3.6);
  30. println!("{}", p2);
  31. }

实例:实作向量运算

我们用一个比较长的例子展示如何实作泛型程式在我们这个例子中,我们实作向量类别,这个类别可以进行向量运算;为了简化范例,我们仅实作向量加法首先,建立Vector类别,内部使用Rust内建的vector来储存资料,在这里一并呼叫相关的特征:

  1. extern crate num;
  2. use std::fmt;
  3. use std::ops::Add;
  4. pub struct Vector<T> where T: Copy + fmt::Display + num::Num {
  5. vec: Vec<T>,
  6. }
  7. //接著,實作 Clone trait,使得本向量類別可以像基礎型別般,在計算時拷貝向量,由於 Rust 的限制,目前不能實作 Copy trait。
  8. impl<T> Clone for Vector<T> where T: Copy + fmt::Display + num::Num {
  9. fn clone(&self) -> Vector<T> {
  10. let mut vec: Vec<T> = Vec::new();
  11. for i in 0..(self.vec.len()) {
  12. vec.push(self.vec[i]);
  13. }
  14. Vector::<T>{ vec: vec }
  15. }
  16. }
  17. // 我們的建構子可接受 slice,簡化建立物件的流程:
  18. // Constructor
  19. impl<T> Vector<T> where T: Copy + fmt::Display + num::Num {
  20. pub fn from_slice(v: &[T]) -> Vector<T> {
  21. let mut vec: Vec<T> = Vec::new();
  22. for i in 0..(v.len()) {
  23. vec.push(v[i])
  24. }
  25. Vector::<T>{ vec: vec }
  26. }
  27. }
  28. // 實作 fmt::Debug trait,之後可直接從 console 印出本類別的內容。這裡實作的方式參考 Rust 的 vector 在終端機印出的形式。
  29. // Overloaded debug string
  30. impl<T> fmt::Debug for Vector<T> where T: Copy + fmt::Display + num::Num {
  31. fn fmt(&self, f:&mut fmt::Formatter) -> fmt::Result {
  32. let mut s = String::new();
  33. s += "[";
  34. for i in 0..(self.vec.len()) {
  35. s += &format!("{}", self.vec[i]);
  36. if i < self.vec.len() - 1 {
  37. s += ", ";
  38. }
  39. }
  40. s += "]";
  41. // Write string to formatter
  42. write!(f, "{}", s)
  43. }
  44. }
  45. // 实作加法运算子,需实作std::ops::Add性状。向量加法的方式是两向量间同位置元素相加,相加前应检查两向量是否等长。
  46. // Overloaded binary '+' operator
  47. impl<T> Add for Vector<T> where T: Copy + fmt::Display + num::Num {
  48. type Output = Vector<T>;
  49. fn add(self: Vector<T>, other: Vector<T>) -> Vector<T> {
  50. if self.vec.len() != other.vec.len() {
  51. panic!("The length of the two vectors are unequal");
  52. }
  53. let mut v: Vec<T> = Vec::new();
  54. for i in 0..(self.vec.len()) {
  55. v.push(self.vec[i] + other.vec[i]);
  56. }
  57. Vector{ vec: v }
  58. }
  59. }
  60. // 最後,從外部程式呼叫此類別:
  61. fn main() {
  62. let v1 = vec![1, 2, 3];
  63. let v2 = vec![2, 3, 4];
  64. let vec1 = Vector::<i32>::from_slice(&v1);
  65. let vec2 = Vector::<i32>::from_slice(&v2);
  66. // We have to explictly clone our Vector object at present.
  67. let vec3 = vec1.clone() + vec2.clone();
  68. println!("{:?}", vec1);
  69. println!("{:?}", vec2);
  70. println!("{:?}", vec3);
  71. }

由于Rust为了保持函数库的相容性,现阶段不允许对非copy data实作复制trait,像是本例的向量类别内部使用的vector,所以,我们必须在外部程序中明确地拷贝向量类别。经笔者实测,对于有解构子的类别也不能使用Copy trait,所以,即使我们用C风格的阵列重新实作vector,同样也不能用Copy trait。

另外,我们在这里用了一个外部函数库提供Num trait,这个trait代表该型别符合数字,透过使用这个trait,不需要重新实现代表数字的trait,简化我们的程式。

刚开始写Rust泛型程式时,会遭到许多错误而无法顺利编译,让初学者感到挫折。解决这个问题的关键在于Rust的trait系统。撰写泛型程式时,若没有对泛型变数T加上任何的trait限制,Rust没有足够的信息是否能对T呼叫相对应的内建trait,因而引发错误讯息。即使是使用运算子,Rust也会呼叫相对应的trait;因此,熟悉trait的运作,对撰写泛型程式有相当的帮助。

(案例选读)模拟方法重载

Rust不支持方法重载,不过,可以利用泛型加上多型达到类似的效果。由于呼叫泛型函数时,不需要明确指定参数的型别,使得外部程式在呼叫该函式时,看起来像是方法重载般。接下来,我们以一个范例来展示如何模拟方法重载。首先,定义公开的特质:

  1. use std::fmt;
  2. // An holder for arbitrary type
  3. pub trait Data: fmt::Display {
  4. // Omit interface
  5. // You may declare more methods later.
  6. }
  7. pub trait IntoData {
  8. type OutData: Data;
  9. fn into_data(&self) -> Self::OutData;
  10. }
  11. // 接着,实作Reader类别,在这个类别中,实作了一个泛型函数,搭配先前的特征类别来模拟方法重载:
  12. pub struct Reader {}
  13. // Use generic method to mimic functional overloading
  14. impl<'a> Reader {
  15. pub fn get_data<I>(& self, data: I) -> Box<Data + 'a>
  16. where I: IntoData<'a> + 'a {
  17. Box::new(data.into_data())
  18. }
  19. }
  20. // 接着,实作StrData类别,这个类别会实作Data和IntoData这两个trait,以满足前述介面所定义的行为:
  21. pub struct StrData<'a> {
  22. str: &'a str
  23. }
  24. impl<'a> StrData<'a> {
  25. pub fn new(s: &'a str) -> StrData<'a> {
  26. StrData{ str: s }
  27. }
  28. }
  29. impl<'a> fmt::Display for StrData<'a> {
  30. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  31. write!(f, "{}", self.str)
  32. }
  33. }
  34. impl<'a> Data for StrData<'a> {
  35. // Omit implementation
  36. }
  37. impl<'a> IntoData<'a> for StrData<'a> {
  38. type OutData = &'a str;
  39. fn into_data(&self) -> &'a str {
  40. self.str
  41. }
  42. }
  43. /* Even Data trait is empty, it is necessary to
  44. explictly implement it. */
  45. impl<'a> Data for &'a str {
  46. // Omit implementation
  47. }
  48. // 接着,类似以StrData的方式实动词} IntData:
  49. pub struct IntData{
  50. int: i32
  51. }
  52. impl IntData {
  53. pub fn new(i: i32) -> IntData {
  54. IntData{ int: i }
  55. }
  56. }
  57. impl fmt::Display for IntData {
  58. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  59. write!(f, "{}", self.int)
  60. }
  61. }
  62. impl Data for IntData {
  63. // Omit implementation
  64. }
  65. impl IntoData for IntData {
  66. type OutData = i32;
  67. fn into_data(&self) -> i32 {
  68. self.int
  69. }
  70. }
  71. /* Even Data trait is empty, it is necessary to
  72. explictly implement it. */
  73. impl<'a> Data for i32 {
  74. // Omit implementation
  75. }
  76. // 最后,从外部程式呼叫:
  77. fn main() {
  78. let reader = Reader{};
  79. let str_data = StrData::new("string data");
  80. let int_data = IntData::new(10);
  81. // Call hidden generic method to minic functional overloading
  82. let str = reader.get_data(str_data);
  83. let int = reader.get_data(int_data);
  84. println!("Data from StrData: {}", str);
  85. println!("Data form IntData: {}", int);
  86. }

在我们这个范例中,除了用泛型的机制模拟出方法重载以外,一个另外重点在于get_data函式隐藏了一些内部的操作,对于程式设计者来说,实只要动词} Data状语从句:IntoData后,从外部程式呼叫时,不需要在意其中操作的细节,这也是物件导向的优点之一。