大家好,
欢迎来到学习 Rust 的第16天。今天,我们来看看 Rust 中的泛型类型,泛型类型帮助我们以类型安全的方式减少代码重复。
让我们快速了解一下。
引言
Rust 中的泛型类型允许你编写可以操作多种数据类型的函数、结构体和枚举,而不牺牲类型安全,从而实现代码复用和灵活性。来看一个用例。
fn find_max(x: i32, y: i32) -> i32{  
  if x > y {  
    x  
  }else{  
    y  
  }  
}  
  
fn main(){  
  let result: i32 = find_max(9,10);  
  println!("最大值是:{}", result);  
}
 
输出:
最大值是:10
 
如果我想使用相同的函数处理字符或浮点数,我必须为特定的类型创建一个新函数,逻辑基本相同。泛型类型有助于减少这种代码重复。
我们可以为函数/结构体声明多个泛型类型。单个泛型类型通常用 T 表示,代表 Type。
函数中的泛型类型用 <> 声明。
示例:
fn find_max<T: PartialOrd>(x: T, y: T) -> T {  
    if x > y {  
        x  
    } else {  
        y  
    }  
}  
  
fn main() {  
    let result = find_max(10, 5);  
    println!("最大值是:{}", result);  
  
    let result_float = find_max(4.5, 3.2);  
    println!("最大值是:{}", result_float);  
  
    let result_char = find_max('x','a');  
    println!("最大值是:{}", result_char);  
}
 
输出:
最大值是:10  
最大值是:4.5  
最大值是:x
 
解释:
find_max函数用泛型类型参数T定义。<T: PartialOrd>语法表明T必须实现PartialOrd特性,这允许对T类型的值进行比较。PartialOrd是 Rust 中定义值之间部分排序关系(小于、小于等于、大于、大于等于)的特性。- 实现 
PartialOrd的类型可以使用比较运算符(<、<=、>、>=)进行比较,并可用于依赖排序的函数,比如排序算法或在此例中,寻找最大值。 - 这使得该函数能够处理各种类型,只要它们支持使用 
PartialOrd特性的比较。 - 通过使用泛型,函数在保持类型安全的同时获得了灵活性和可重用性。
 - 该函数返回一个 
T类型的值,确保最大值与输入值类型相同。 
结构体中的泛型类型
假设我想存储一个点的坐标。这是我通常使用的结构体
struct Point{  
  x: i32,  
  y: i32,  
}  
  
fn main(){  
  let point = Point{x: 3, y: 10};  
  
  //这行代码不会工作,因为我们的结构体只能存储有符号的32位整数  
  let point2 = Point{x:3.4, y: 6.9};  
}
 
让我们解决这个问题:
struct Point<T>{  
  x: T,  
  y: T,  
}  
  
fn main(){  
  let point = Point{x: 3, y: 10};  
  let point2 = Point{x:3.4, y: 6.9};  
  
  //这行代码不会工作,因为我们的结构体不能存储两种不同的数据类型  
  let point3 = Point{x:3.4, y: 6};  
}
 
为了解决这个问题,我们可以在结构体中添加另一个泛型类型。
struct Point<T, U>{  
  x: T,  
  y: U,  
}  
  
fn main(){  
  let point = Point{x: 3, y: 10};  
  let point2 = Point{x:3.4, y: 6.9};  
  let point3 = Point{x:3.4, y: 6};  
}
 
现在这一切都能工作了,这就是我们如何在结构体中使用泛型类型…
枚举中的泛型类型
我们可以对枚举做同样的事情,我将给你展示两个最流行的带有泛型类型的枚举示例:
enum Option<T>{  
    Some(T),  
    None,  
}
enum Result<T,E>{  
  Ok(T),  
  Err(E),  
}
 
方法中的泛型类型
如果你还记得,我们可以使用 impl 为结构体声明实现块,以声明结构体的方法。
回到我们的 Point 结构体,让我们使用泛型类型为结构体声明一个实现块。
struct Point<T>{  
  x: T,  
  y: T,  
}  
  
impl<U> Point<U>{  
  fn x(&self) -> &U {  
    &self.x  
  }  
}  
  
fn main(){  
  let point = Point{x: 3, y: 10};  
  let point2 = Point{x:3.4, y: 6.9};  
}
 
X 方法返回点的 X 坐标,取自 self 的引用。
注意,声明结构体时使用了 T 作为泛型类型,在 impl 块中我使用了 U,这表明这两个泛型类型并不相连。
如果我声明另一个具体类型的 impl 块会发生什么?
struct Point<T>{  
  x: T,  
  y: T,  
}  
  
impl<U> Point<U>{  
  fn x(&self) -> &U {  
    &self.x  
  }  
}  
  
impl Point<i32>{  
  fn y(&self) -> i32{  
    self.y  
  }  
}  
  
fn main(){  
  let point1 = Point{x: 3, y: 10};  
  let point2 = Point{x:3.4, y: 6.9};  
  
  println!("X: {}, Y: {}",point1.x(),point1.y());  
  println!("X: {}, Y: {}",point2.x(),point2.y);  
}
 
y 方法只对实例可用,这些实例的 x 和 y 值为有符号的 32 位整数,而 x 方法可以用于任何类型的实例。
混合使用泛型
让我们看一个更复杂的例子,我们的 Point 结构体中有两个泛型,并理解混合它们的需要…
struct Point<T, U>{  
  x: T,  
  y: U,  
}  
  
impl<T,U> Point<T,U>{  
  fn mixup<V, W>(self,other: Point<V, W>) -> Point<V, W>{  
    Point{  
      x: self.x,  
      y: other.y,  
    }  
  }  
}  
  
fn main(){  
  let point = Point{x: 3, y: 10};  
  let point2 = Point{x:3.4, y: 6.9};  
  let point3 = Point{x:3.4, y: 6};  
  let point4 = point1.mixup(point2);  
  
  println!("X: {}, Y: {}",point4.x, point4.y);  
}
 
为什么要混合使用泛型?
V 和 W 像是类型的占位符。我们在 mixup 方法中使用它们,因为它允许我们混合匹配两个 Point 实例的 x 和 y 值的不同类型。这种灵活性使我们的代码更加多样化,因为它允许我们处理不同的类型而无需改变结构体本身。所以,使用 V 和 W 而不是 T 和 U,在方法中保持事物清晰且灵活。
结论
总之,泛型类型是减少代码重复的绝佳方式,因此它们对性能有巨大的影响,通过在 Rust 中利用泛型类型,开发者可以编写更多样化和可重用的代码,同时保持类型安全和性能。这种方法不仅减少了冗余,还增强了代码的可维护性和可扩展性,使 Rust 成为广泛应用程序的强大语言选择。此外,拥抱泛型促进了更清晰、更灵活的设计,使开发者能够轻松适应不断变化的需求。










