目录
前言
泛型
单态化
特质
多态
总结
前言
这节课来介绍一下Rust中的泛型和特质,泛型在大部分语言中都有,和Java中的泛型差不多,特质在Java并没有,但是在Scala中有,特质其实类似于Java中的接口Interface。
泛型
(1)容器中的泛型
Rust中我们使用的Vector、HashMap其实都是有泛型的,和其他编程语言中的泛型一样
(2)结构体中的泛型
下面代码展示了结构体中的泛型,包括结构体的方法如何定义泛型,包括方法和关联函数。值的注意的是在调用关联函数的时候,我们指定了具体的类型,可以试一下如果不指定会怎么样?不指定编译会报错,为什么会报错呢?这里涉及Rust中泛型的实现方式。
use std::fmt::Display;fn main() {let r1 = RecTangle {width: 1,height: 2.0,};println!("r1.width={}", r1.width);println!("r1.height={}", r1.height);r1.custom_print1("hello");RecTangle::<i32, i32>::custom_print2("hello");
}struct RecTangle<T, U> {width: T,height: U,
}impl<T, U> RecTangle<T, U> {fn get_width(&self) -> &T {&self.width}fn get_height(&self) -> &U {&self.height}fn custom_print1<E: Display>(&self, other: E) {println!("other={}", other);}fn custom_print2<E: Display>(text: E) {println!("text = {}", text);}
}
单态化
我们来聊一聊Rust中泛型的实现方式:单态化。
有了解过Java中的泛型实现可以知道,Java中的泛型是伪泛型,存在泛型擦除,不管什么泛型最终都是Object。但是Rust中的泛型实现不一样,他是依赖编译期将存在的类型都固定死。什么意思呢?假设我们定义了一个泛型函数,在整个程序中,该泛型可以是i32,f64.那么在编译期间,Rust编译器会根据类型推断生成具体类型的代码,比如这个函数会生成i32和f64对应的函数,在调用函数的地方也会替换为i32和f64的函数,这种行为就叫做单态化,单态化的好处是类型确定且性能优化。我们提供一段代码解释一下,下面的代码中我们定义了泛型函数,在main方法中,调用了&str和i32和f64类型的函数,那么在编译期间,会生成对应类型的my_print函数,并且在main方法中替换函数。
use std::fmt::Display;fn main() {my_print("hello");my_print(12);my_print(3.14);}fn my_print<T: Display>(input: T) {println!("input is {}", input);
}
特质
Rust中的特质trait可以类比于Java中的接口interface理解。
在下面的代码中,我们定义了一个Trait:Say;并且定义了抽象方法say_hi,又定义了三个不同的结构体,并且为这些结构体实现了Say特质。
use std::fmt::Display;fn main() {let p1 = Person {name: String::from("p"),};let d1 = Dog {name: String::from("d1"),};let c1 = Cat {name: String::from("c1"),};p1.say_hi();d1.say_hi();c1.say_hi();}trait Say {fn name_f() {println!("我是接口Say")}fn say_hi(&self);
}struct Person {name: String,
}impl Say for Person {fn say_hi(&self) {println!("{} say hi", self.name);}
}struct Dog {name: String,
}impl Say for Dog {fn say_hi(&self) {println!("{} say wang", self.name);}
}struct Cat {name: String,
}impl Say for Cat {fn say_hi(&self) {println!("{} say miao", self.name);}
}
多态
由于Rust中没有继承的概念,不像Java等语言,有父类和子类的概念,在java代码中经常看到使用父类接收子类对象的代码,在Rust中都是依赖Trait来实现这样的功能的。Trait可以当作参数也可以当作返回值。
use std::fmt::Display;fn main() {let p1 = Person {name: String::from("p"),};let d1 = Dog {name: String::from("d1"),};let c1 = Cat {name: String::from("c1"),};custom_print(&p1);p1.say_hi();}fn custom_print(input: &impl Say) {input.say_hi();
}trait Say {fn name_f() {println!("我是接口Say")}fn say_hi(&self);
}struct Person {name: String,
}impl Say for Person {fn say_hi(&self) {println!("{} say hi", self.name);}
}struct Dog {name: String,
}impl Say for Dog {fn say_hi(&self) {println!("{} say wang", self.name);}
}struct Cat {name: String,
}impl Say for Cat {fn say_hi(&self) {println!("{} say miao", self.name);}
}
上面的代码是用Trait当作参数的例子,定义了custom_print函数,函数的参数使用impl Say表示接收一个实现了Say特质的参数,注意这里我使用了&表示这只是一个引用,我们复习一下所有权转移的知识,如果不加&,所以权会转移给入参,当函数运行结束后,这一块内存会被释放,于是我们还在想函数调用结束后,使用结构体对象的话,就会报错,于是我们使用&借用只读权限,这样不会报错,由此可见,所有权无处不在。
下面的代码展示了如何使用Trait作为函数的返回值
在函数的返回值处使用impl Say表示函数返回的是一个实现了Say特质的对象。
use std::fmt::Display;fn main() {let p1 = Person {name: String::from("p"),};let d1 = Dog {name: String::from("d1"),};let c1 = Cat {name: String::from("c1"),};Say::name_f();Person::name_f();custom_print(&p1);p1.say_hi();let xx = new_say();xx.say_hi();}fn new_say() -> impl Say {Dog {name: String::from("www"),}
}fn custom_print(input: &impl Say) {input.say_hi();
}trait Say {fn name_f() {println!("我是接口Say")}fn say_hi(&self);
}struct Person {name: String,
}impl Say for Person {fn say_hi(&self) {println!("{} say hi", self.name);}
}struct Dog {name: String,
}impl Say for Dog {fn say_hi(&self) {println!("{} say wang", self.name);}
}struct Cat {name: String,
}impl Say for Cat {fn say_hi(&self) {println!("{} say miao", self.name);}
}
总结
这节课讲述了Rust中面向对象的部分,对于泛型,Rust中泛型实现方式和其他语言不用,采用了单态化的方式在编译器生成代码,Rust将很多工作都移动到了编译器执行,这也是编译时间长的原因之一,Rust中的特质和Scala中的特质Java中的接口类似。