Rust 泛型
泛型程序设计(generic programming)是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。在Rust中编译时编译器会将泛型代码具化为不同具体类型的多份代码,所以泛型代码只是提供了一种通用代码的抽象,并不能减少编译后真正生成的代码量。
泛型创建
泛型函数
fn give_me<T>(value:T){
let _=value;
}
泛型函数的创建需要在方法名和圆括号间放置泛型参数,之后就可以将该泛型参数视为类型使用。
使用
fn main() {
let a="generics";
let b=1024;
give_me(a);
give_me(b);
}
泛型类型
泛型类型的创建需要在类型名和{}间放置泛型参数,之后就可以将该泛型参数视为类型使用。
结构体:
struct GenericStruct<T>(T);
struct Container<T>{
item:T,
}
使用:
let i=GenericStruct(1);
let s=GenericStruct(String::from("1111"));
let ic=Container{item:1};
let is=Container{item:String::from("is")};
枚举:
enum Transmission <T>{
Singal(T),
NoSingal,
}
使用:
let ei=Transmission::Singal(1);
let es=Transmission::Singal(String::from("222"));
let eni=Transmission::<i32>::NoSingal;
let ens:Transmission<String>=Transmission::NoSingal;
注意:使用泛型ZST枚举实例要使用::<>(turbofish)来指明泛型参数是哪种类型。
泛型实现:
泛型实现要在impl后声明泛型参数,然后就可以把泛型参数当作具体类型使用。
拿上一部分的Container为例,为其实现方法:
impl<T> Container<T> {
fn new(item:T)->Self{
Container{item}
}
}
使用:
let container_i32=Container::new(1);
let container_str=Container::new(String::from("111"));
还可以为具体类型参数提供实现:
impl Container<i32>{
// fn new(item:i32)->Self{
// Container{item}
// }
fn toString(&self)->String{
self.item.to_string()
}
}
这种具体类型参数的实现只对此类型可用,并且不能和泛型实现函数名冲突。
特征区间(trait bounds)
上述所有示例程序,我们都只是简单的提供了一个占位符来表示类型,编译器并不知道具体的类型信息,所以我们除了用它们来表示类型外,并不能对这些占位符或者它们表示的变量调用方法,好在Rust提供了trait bounds来告知编译器相关的类型信息来完成这项工作。
我们可以在泛型声明处使用“:”来对类型占位符进行一些约束来告知编译器相关的类型信息。一些例子如下:
在方法上使用trait bounds:
trait Person{
fn sleep(){
println!("person need sleep");
}
fn get_name(&self)->&str;
}
struct Student{name:String}
impl Person for Student {
fn get_name(&self)->&str{
&self.name
}
}
fn who<T:Person>(anyone:&T){
println!("I'm {}",anyone.get_name());
}
fn doing_at_night<T:Person>(anyone:&T){
T::sleep();
}
使用:
let stu=Student{name:"YSS".to_string()};
Student::who(&stu);
Student::doing_at_night(&stu);
需要注意的是,trait bounds提供了对泛型参数的约束导致对传入的泛型类型有了一定的要求,只有符合泛型约束的类型才能正确使用泛型代码。
trait bounds除了可以像上述那样指定以外,还可以把泛型约束单独提出来放置到返回值之后{}之前的where语句中(多个泛型参数","分割),这在约束较长或者泛型参数较多的情况下能够提高代码可读性。
上述例子可改写如下:
fn who<T>(anyone: &T)
where
T: Person,
{
println!("I'm {}", anyone.get_name());
}
fn doing_at_night<T>(anyone: &T)
where
T: Person,
{
T::sleep();
}
类型上的trait bounds:
struct Foo<T:Display>{
bar:T
}
或
struct Foo<T> where T:Display{
bar:T
}
这样约束后,实例化结构体Foo时,bar只能是实现了Display trait的实例。
泛型实现上的trait bounds:
trait Eatable{
fn eat(&self);
}
#[derive(Debug)]
struct Food<T>(T);
impl<T:Debug> Eatable for Food<T>{
fn eat(&self) {
println!("Eating{:?}",self);
}
}
或
impl<T> Eatable for Food<T>where T:Debug{
fn eat(&self) {
println!("Eating{:?}",self);
}
}
特征区间组合
我们还可以使用“+”为泛型指定多个特征区间,例如:
trait Eat{
fn eat(&self){
println!("eat");
}
}
trait Code{
fn code(&self){
println!("code");
}
}
trait Sleep{
fn sleep(&self){
println!("sleep");
}
}
fn coder_life <T:Eat+Code+Sleep> (coder:T){
coder.eat();
coder.code();
coder.sleep();
}
impl特征区间
对于涉及到堆分配或无法用具体类型表示约束的类型(例如闭包),我们可以使用impl来指定特征区间:
fn lazy_adder(a:u32,b:u32)->impl Fn()->u32{
move||a+b
}
如果用普通trait bounds的话,编译器会报如下错误:
fn lazy_adder<T:Fn()->u32>(a:u32,b:u32)->T{
move||a+b
}
error[E0308]: mismatched types
--> src/main.rs:106:5
|
105 | fn lazy_adder<T:Fn()->u32>(a:u32,b:u32)->T{
| - this type parameter - expected `T` because of return type
106 | move||a+b
| ^^^^^^^^^ expected type parameter `T`, found closure
|
= note: expected type parameter `T`
found closure `[closure@src/main.rs:106:5: 106:14]`
= help: every closure has a distinct type and so could not always match the caller-chosen type of parameter `T`
因为闭包是堆分配的,如果返回类型不做特殊处理的话,是在栈上的变量,不会像智能指针那样指向堆分配内容,所以我们只能用impl或者用智能指针包裹,像下面这样:
fn lazy_adder(a:u32,b:u32)->Box<Fn()->u32>{
Box::from(move||a+b)
}
对返回闭包感兴趣的老哥可以看看这个帖子:
https://medium.com/journey-to-rust/closures-part-2-29536393dd92
|
请发表评论