一、泛型
泛型用于简化、方便代码复用。
与C++的模板函数,模板类相似。除了语法上有些不同,没什么特别的。
泛型结构
struct Point<T> {
x: T,
y: T,
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
}
二、特征(trait)
trait类似其它语言中的interface(比如go中的interface,也类似于C++中的抽象类)。
用法
pub trait Summary {
fn summarize(&self) -> String;
}
go的示例
type Reader interface {
Read(p []byte) (n int, err error)
}
Rust示例
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
与go相比,需要显式指明实现哪,go是隐式的,只要某个类实现了某些方法,而这些方法符合某些接口,那就可以无缝使用。
限制:只有当trait或者type中至少一个是本crate中本地的,才可以为该type实现该trait。避免多个crate实现为同一个type实现同一个trait,但具体实现不同。
coherence, 连贯性,一致性
孤儿规则
默认实现
trait也可以有默认实现,等同于C++的虚函数(而非纯虚函数)。
go的interface是没有默认实现的吗?
不同点
主要是语法和具体使用规则上的,其实差不多的。
作为参数
pub trait Summary {
fn summarize(&self) -> String;
}
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
Summary
作为参数后,notify就可以支持所有实现了该trait的类型。语法上,需要明确加上关键字impl
。
这样其实就实现了多态,而且是运行期的。某种意义上的继承?
trait限制语法
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
pub fn notify<T: Summary>(item1: &T, item2: &T) {
两种方式各有优劣。
第一种方式可能更简洁
第二种方式可以限定两个参数必须是同一种类型,而不仅仅是实现了Summary
接口就行。
指定多个trait限制
pub fn notify(item: &(impl Summary + Display)) {
pub fn notify<T: Summary + Display>(item: &T) {
多个trait用+
连接即可。
where
从句
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug
{
语法糖,让阅读更清晰。
trait作为返回值
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
这个也是顺其自然的规则,就像C++返回抽象基类。
例子
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
用trait解决函数模板,解决模板参数T类型需要实现>
的功能。
根据trait限制为泛型实现不同的方法
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
cmp_display只有当类型T实现了Display + PartialOrd trait,才会实现。
impl<T: Display> ToString for T {
// --snip--
}
只有当类型T实现了Display trait,才会实现ToString trait(blanket implementation覆盖实现)。
trait和trait 限制降低重复代码,增加了灵活性。trait bound帮助编译器检查,而无需在运行期检查某个类型是否实现了某个方法,提高了运行效率。而像C++的虚函数,则是运行期检查,不过也不会出现调用不存在方法的现象,哪类编程语言会出现呢(python这些?)?
生命期验证
生命期(寿命)防止悬空(dangling)引用
fn main() {
{
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}
}
r引用了x,r却在x死亡后使用x,显然不行。
借用检查器
检查所有的借用是否有效。
前提是必须在x的寿命之后再用了r,如果没用r,是没有关系的。
函数中的泛型生命期
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
问题
解决
生命期注解
&i32 // a reference
&'a i32 // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime
吊诡的语法
generic lifetime parameter??泛型生命期参数?
生命期相同,表面参数的生命期相同。
函数签名中的生命期注解
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
生命期注解是'a
,泛型指的是<>
,所以<'a>
就是泛型生命期参数。这表明返回值的生命期和入参相同。
但这里只是注解,并不改变变量的实际生命期。如果注解与实际不符合,还是报错的。
比如
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
}
println!("The longest string is {}", result);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
string2的生命期短于result,报错
思考生命期
fn main() {
let string1 = String::from("abcd");
let string2 = "efghijklmnopqrstuvwxyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
}
返回值一定 为x,生命期与y完全无关,因此y不需要注解。编译器要做的事情还是相当多的!!!
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}
fn longest<'a>(x: &str, y: &str) -> &'a str {
let result = String::from("really long string");
result.as_str()
}
返回值虽然有生命期注解,但是没有和入参关联,一样导致编译错误。
生命期语法用于关联入参和返回值的生命期,关联后,rust就可以保证内存安全,并阻止非法的虚悬指针、非法内存使用。
结构体定义中注解
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
let i = ImportantExcerpt {
part: first_sentence,
};
}
结构体可能有引用,那么就需要生命期注解了。上面指,结构体对象的生命期不能长于它的引用成员变量。
生命期偷懒
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let my_string = String::from("hello world");
// first_word works on slices of `String`s
let word = first_word(&my_string[..]);
let my_string_literal = "hello world";
// first_word works on slices of string literals
let word = first_word(&my_string_literal[..]);
// Because string literals *are* string slices already,
// this works too, without the slice syntax!
let word = first_word(my_string_literal);
}
不需要生命期注解,在rust1.0之前,需要这样
但后来发现特定条件下借用检查器可以发现某些问题,不需要显式的注解,称作生命期偷懒(省略)。
编译器自动推导引用生命期的规则。
- 每个参数单独一个生命期
- 如果只有一个入参,那入参的生命期就是所有出参的生命期
- 如果有多个入参生命期,但是有一个入参是&self或者&mut self,那么self的生命期就所有出参的。这让成员方法更简洁。
成功推导的例子
fn first_word(s: &str) -> &str {
等价于
fn first_word<'a>(s: &'a str) -> &'a str {
未能成功的例子
fn longest(x: &str, y: &str) -> &str {
等价于
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
因为有两个入参,而且不少成员方法,而是函数,因此无法推导出参生命期。rust管成员函数叫方法,管普通函数叫函数。
方法中的生命期注解
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
let i = ImportantExcerpt {
part: first_sentence,
};
}
返回值的生命期和self相同。
静态生命期
#![allow(unused)]
fn main() {
let s: &'static str = "I have a static lifetime.";
}
静态生命期,与程序生命期相同,存储于程序的二进制文件中。与C++中的static变量类似。字符串常量(享元模式?)。单例中的懒汉模式(早早的构建好实例)?
综合运用泛化类型参数、特征限定、生命期
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest_with_an_announcement(
string1.as_str(),
string2,
"Today is someone's birthday!",
);
println!("The longest string is {}", result);
}
use std::fmt::Display;
fn longest_with_an_announcement<'a, T>(
x: &'a str,
y: &'a str,
ann: T,
) -> &'a str
where
T: Display,
{
println!("Announcement! {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}
区区一个比较字符串的函数,都这么复杂,值得吗?
总结
泛化类型参数消除了重复,特征和特征限定保证在类型泛化的情况下,类型的行为仍然满足代码正确运行的需求。
另外,检查在编译期,对运行性能无影响。