Rust编程语言入门之泛型Trait生命周期
Posted 小乔的博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Rust编程语言入门之泛型Trait生命周期相关的知识,希望对你有一定的参考价值。
泛型、Trait、生命周期
一、提取函数消除重复
fn main()
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = number_list[0];
for number in number_list
if number > largest
largest = number;
println!("The largest number is ", largest);
重复代码
- 重复代码的危害:
- 容易出错
- 需求变更时需要在多处进行修改
- 消除重复:提取函数
fn largest(list: &[i32]) -> i32
let mut largest = list[0];
for &item in list // &item 解构
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 number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let result = largest(&number_list);
println!("The largest number is ", result);
消除重复的步骤
- 识别重复代码
- 提取重复代码到函数体中,并在函数签名中指定函数的输入和返回值
- 将重复的代码使用函数调用进行替代
二、泛型
泛型
- 泛型:提高代码复用能力
- 处理重复代码的问题
- 泛型是具体类型或其它属性的抽象代替:
- 你编写的代码不是最终的代码,而是一种模版,里面有一些“占位符”
- 编译器在编译时将“占位符”替换为具体的类型
- 例如:
fn largest<T>(list: &[T]) -> T ...
- 类型参数:
- 很短,通常一个字母
- CamelCase
- T:type 的缩写
函数定义中的泛型
- 泛型函数:
- 参数类型
- 返回类型
fn largest<T>(list: &[T]) -> T
let mut largest = list[0];
for &item in list
if item > largest // 比较 报错 ToDo
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 number is ", result);
Struct 定义中的泛型
struct Point<T>
x: T,
y: T,
struct Point1<T, U>
x: T,
y: U,
fn main()
let integer = Point x: 5, y: 10;
let float = Point(x: 1.0, y: 4.0);
let integer1 = Point1 x: 5, y: 10.0;
- 可以使用多个泛型的类型参数
- 太多类型参数:你的代码需要重组为多个更小的单元
Enum 定义中的泛型
- 可以让枚举的变体持有泛型数据类型
- 例如
Option<T>
,Result<T, E>
- 例如
enum Option<T>
Some(T),
None,
enum Result<T, E>
Ok(T),
Err(E),
fn main()
方法定义中的泛型
- 为 struct 或 enum 实现方法的时候,可在定义中使用泛型
struct Point<T>
x: T,
y: T,
impl<T> Point<T>
fn x(&self) -> &T
&self.x
impl Point<i32>
fn x1(&self) -> &i32
&self.x
fn main()
let p = Point x: 5, y: 10;
println!("p.x = ", p.x());
- 注意:
- 把 T 放在 impl 关键字后,表示在类型 T 上实现方法
- 例如:
impl<T> Point<T>
- 例如:
- 只针对具体类型实现方法(其余类型没实现方法):
- 例如:
impl Point<f32>
- 例如:
- 把 T 放在 impl 关键字后,表示在类型 T 上实现方法
- struct 里的泛型类型参数可以和方法的泛型类型参数不同
struct Point<T, U>
x: T,
y: U,
impl<T, U> Point<T, U>
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W>
Point
x: self.x,
y: other.y,
fn main()
let p1 = Point x: 5, y: 4;
let p2 = Point x: "Hello", y: \'c\';
let p3 = p1.mixup(p2);
println!("p3.x = , p3.y = ", p3.x, p3.y);
泛型代码的性能
- 使用泛型的代码和使用具体类型的代码运行速度是一样的。
- 单态化(monomorphization):
- 在编译时将泛型替换为具体类型的过程
fn main()
let integer = Some(5);
let float = Some(5.0);
enum Option_i32
Some(i32),
None,
enum Option_f64
Some(f64),
None,
fn main()
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);
三、Trait(上)
Trait
- Trait 告诉 Rust 编译器:
- 某种类型具有哪些并且可以与其它类型共享的功能
- Trait:抽象的定义共享行为
- Trait bounds(约束):泛型类型参数指定为实现了特定行为的类型
- Trait 与其它语言的接口(Interface)类似,但有些区别
定义一个 Trait
- Trait 的定义:把方法签名放在一起,来定义实现某种目的所必需的一组行为。
- 关键字:trait
- 只有方法签名,没有具体实现
- trait 可以有多个方法:每个方法签名占一行,以 ; 结尾
- 实现该 trait 的类型必须提供具体的方法实现
pub trait Summary
fn summarize(&self) -> String;
// NewsArticle
// Tweet
fn main()
在类型上实现 trait
- 与为类型实现方法类似
- 不同之处:
impl xxxx for Tweet ...
- 在 impl 的块里,需要对 Trait 里的方法签名进行具体的实现
lib.rs 文件
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)
main.rs 文件
use demo::Summary;
use demo::Tweet;
fn main()
let tweet = Tweet
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
;
println!("1 new tweet: ", tweet.summarize())
实现 trait 的约束
- 可以在某个类型上实现某个 trait 的前提条件是:
- 这个类型或这个 trait 是在本地 crate 里定义的
- 无法为外部类型来实现外部的 trait:
- 这个限制是程序属性的一部分(也就是一致性)
- 更具体地说是孤儿规则:之所以这样命名是因为父类型不存在
- 此规则确保其他人的代码不能破坏您的代码,反之亦然
- 如果没有这个规则,两个crate 可以为同一类型实现同一个 trait,Rust就不知道应该使用哪个实现了
默认实现
lib.rs 文件
pub trait Summary
// fn summarize(&self) -> String;
fn summarize(&self) -> String
String::from("(Read more...)")
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)
main.rs 文件
use demo::NewsArticle;
use demo::Summary;
fn main()
let article = NewsArticle
headline: String::from("Penguins win the Stanley Cup Championship!"),
content: String::from("The pittsburgh penguins once again are the best hockey team in the NHL."),
author: String::from("Iceburgh"),
location: String::from("Pittsburgh, PA, USA"),
;
println!("1 new tweet: ", article .summarize())
- 默认实现的方法可以调用 trait 中其它的方法,即使这些方法没有默认实现。
pub trait Summary
fn summarize_author(&self) -> String;
fn summarize(&self) -> String
format!("Read more from ...", self.summarize_author())
pub struct NewsArticle
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
impl Summary for NewsArticle
fn summarize_author(&self) -> String
format!("@", self.author)
- 无法从方法的重写实现里面调用默认的实现
四、Trait(下)
Trait 作为参数
pub fn notify(item: impl Summary)
println!("Breaking news! ", item.summarize());
- impl Trait 语法:适用于简单情况
- Trait bound 语法:可用于复杂情况
- impl Trait 语法是 Trait bound 的语法糖
pub fn notify<T: Summary>(item: T)
println!("Breaking news! ", item.summarize());
- 使用 + 指定多个 Trait bound
pub fn notify(item: impl Summary + Display)
println!("Breaking news! ", item.summarize());
pub fn notify<T: Summary + Display>(item: T)
println!("Breaking news! ", item.summarize());
- Trait bound 使用where 子句
- 在方法签名后指定 where 子句
pub fn notify<T: Summary + Display, U: Clone + Debug>(a: T, b: U) -> String
format!("Breaking news! ", a.summarize())
pub fn notify<T, U>(a: T, b: U) -> String
where
T: Summary + Display,
U: Clone + Debug,
format!("Breaking news! ", a.summarize())
实现 Trait 作为返回类型
- impl Trait 语法
pub fn notify1(s: &str) -> impl Summary
NewsArticle
headline: String::from("Penguins win the Stanley Cup Championship!"),
content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
author: String::from("Iceburgh"),
location: String::from("Pittsburgh, PA, USA"),
- 注意: impl Trait 只能返回确定的同一种类型,返回可能不同类型的代码会报错
使用 Trait Bound 的例子
- 例子:使用 Trait Bound 修复 largest 函数
fn largest<T: PartialOrd + Clone>(list: &[T]) -> T
let mut largest = list[0].clone();
for item in list.iter()
if item > &largest // std::cmp::ParticalOrd
largest = item.clone();
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)
fn largest<T: PartialOrd + Clone>(list: &[T]) -> &T
let mut largest = &list[0];
for item in list.iter()
if item > &largest // std::cmp::ParticalOrd
largest = item;
largest
fn main()
let str_list = vec![String::from("hello"), String::from("world")];
let result = largest(&str_list);
println!("The largest word is ", result);
使用 Trait Bound 有条件的实现方法
- 在使用泛型类型参数的 impl 块上使用 Trait Bound,我们可以有条件的为实现了特定 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);
- 也可以为实现了其它Trait的任意类型有条件的实现某个Trait
- 为满足Trait Bound 的所有类型上实现 Trait 叫做覆盖实现(blanket implementations)
fn main()
let s = 3.to_string();
五、生命周期(1/4)
生命周期
- Rust的每个引用都有自己的生命周期
- 生命周期:引用保持有效的作用域
- 大多数情况:生命周期是隐式的、可被推断的
- 当引用的生命周期可能以不同的方式互相关联时:手动标注生命周期。
生命周期 - 避免悬垂引用(dangling regerence)
- 生命周期的主要目标:避免悬垂引用(dangling regerence)
fn main()
let r;
let x = 5;
r = &x; // 报错
println!("r: ", r);
借用检查器
- Rust编译器的借用检查器:比较作用域来判断所有的借用是否合法。
fn main()
let x = 5;
let r = &x;
println!("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<\'a>(x: &\'a str, y: &\'a str) -> &\'a str
if x.len() > y.len()
x
else
y
六、生命周期(2/4)
生命周期标注语法
- 生命周期的标注不会改变引用的生命周期长度
- 当指定了泛型生命周期参数,函数可以接收带有任何生命周期的引用
- 生命周期的标注:描述了多个引用的生命周期间的关系,但不影响生命周期
生命周期标注 - 语法
- 生命周期参数名:
- 以 \' 开头
- 通常全小写且非常短
- 很多人使用 \'a
- 生命周期标注的位置:
- 在引用的 & 符号后
- 使用空格将标注和引用类型分开
生命周期标注 - 例子
- &i32 // 一个引用
- &\'a i32 // 带有显示生命周期的引用
- &\'a mut i32 // 带有显示生命周期的可变引用
- 单个生命周期标注本身没有意义
函数签名中的生命周期标注
- 泛型生命周期参数声明在:函数名和参数列表之间的 <>里
- 生命周期 \'a 的实际生命周期是:x 和 y 两个生命周期中较小的那个
fn main()
let string1 = String::from("abcd");
let result;
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.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
七、生命周期(3/4)
深入理解生命周期
- 指定生命周期参数的方式依赖于函数所做的事情
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: &str) -> &\'a str
x
- 从函数返回引用时,返回类型的生命周期参数需要与其中一个参数的生命周期匹配
- 如果返回的引用没有指向任何参数,那么它只能引用函数内创建的值
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: &str) -> &\'a str
let result = String::from("abc");
result.as_str() // 报错
fn longest<\'a>(x: &\'a str, y: &str) -> String
let result = String::from("abc");
result
Struct 定义中的生命周期标注
- Struct 里可包括:
- 自持有的类型
- 引用:需要在每个引用上添加生命周期标注
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 found a \'.\'");
let i = ImportantExcerpt
part: first_sentence
;
生命周期的省略
- 我们知道:
- 每个引用都有生命周期
- 需要为使用生命周期的函数或Struct指定生命周期参数
生命周期省略规则
- 在Rust引用分析中所编入的模式称为生命周期省略规则。
- 这些规则无需开发者来遵守
- 它们是一些特殊情况,由编译器来考虑
- 如果你的代码符合这些情况,那么就无需显式标注生命周期
- 生命周期省略规则不会提供完整的推断:
- 如果应用规则后,引用的生命周期仍然模糊不清-> 编译错误
- 解决办法:添加生命周期标注,表明引用间的相互关系
输入、输出生命周期
- 生命周期在:
- 函数/方法的参数:输入生命周期
- 函数/方法的返回值:输出生命周期
生命周期省略的三个规则
- 编译器使用3个规则在没有显示标注生命周期的情况下,来确定引用的生命周期
- 规则 1 应用于输入生命周期
- 规则 2、3 应用于输出生命周期
- 如果编译器应用完 3 个规则之后,仍然有无法确定生命周期的引用 -> 报错
- 这些规则适用于 fn 定义和 impl 块
- 规则 1:每个引用类型的参数都有自己的生命周期
- 规则 2:如果只有 1 个输入生命周期参数,那么该生命周期被赋给所有的输出生命周期参数
- 规则 3:如果有多个输入生命周期参数,但其中一个是 &self 或 &mut self (是方法),那么 self 的生命周期会被赋给所有的输出生命周期参数
生命周期省略的三个规则 - 例子
- 假设我们是编译器:
fn first_word(s: &str) -> &str
fn first_word<\'a>(s: &\'a 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
// 报错
八、生命周期(4/4)
方法定义中的生命周期标注
- 在 Struct 上使用生命周期实现方法,语法和泛型参数的语法一样
- 在哪声明和使用生命周期参数,依赖于:
- 生命周期参数是否和字段、方法的参数或返回值有关
- Struct 字段的生命周期名:
- 在 impl 后声明
- 在 struct 名后声明
- 这些声明周期是 Struct 类型的一部分
- impl 块内的方法签名中:
- 引用必须绑定于 Struct 字段引用的生命周期,或者引用是独立的也可以
- 生命周期省略规则经常使得方法中的生命周期标注不是必须的
struct ImportantExcerpt<\'a>
part: &\'a str,
impl<\'a> ImportantExcerpt<\'a>
fn level(&self) -> i32
3
fn snnounce_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 found a \'.\'");
let i = ImportantExcerpt
part: first_sentence,
;
静态生命周期
- \'static 是一个特殊的生命周期:整个程序的持续时间。
- 例如:所有的字符串字面值都拥有 ‘static 生命周期
let s: &\'static str = "I have a static lifetime.";
- 为引用指定 ’static 生命周期前要三思:
- 是否需要引用在程序整个生命周期内都存活。
泛型参数类型、Trait Bound、生命周期
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
fn main()
本文来自博客园,作者:QIAOPENGJUN,转载请注明原文链接:https://www.cnblogs.com/QiaoPengjun/p/17279765.html
为啥 Rust 编译器要求我限制泛型类型参数的生命周期(错误 E0309)?
【中文标题】为啥 Rust 编译器要求我限制泛型类型参数的生命周期(错误 E0309)?【英文标题】:Why does the Rust compiler request I constrain a generic type parameter's lifetime (error E0309)?为什么 Rust 编译器要求我限制泛型类型参数的生命周期(错误 E0309)? 【发布时间】:2016-12-02 06:31:32 【问题描述】:为什么 Rust 编译器会发出一个错误,要求我在以下结构中限制泛型参数的生命周期?
pub struct NewType<'a, T>
x: &'a T,
error[E0309]: the parameter type `T` may not live long enough
--> src/main.rs:2:5
|
2 | x: &'a T,
| ^^^^^^^^
|
= help: consider adding an explicit lifetime bound `T: 'a`...
note: ...so that the reference type `&'a T` does not outlive the data it points at
--> src/main.rs:2:5
|
2 | x: &'a T,
| ^^^^^^^^
我可以通过更改来修复它
pub struct NewType<'a, T>
where
T: 'a,
x: &'a T,
我不明白为什么必须将T: 'a
部分添加到结构定义中。我想不出T
中包含的数据比对T
的引用更有效的方法。 x
的引用对象需要比 NewType
结构更长寿,如果 T
是另一个结构,那么它包含的任何引用也需要满足相同的标准。
是否存在需要这种类型的注释的具体示例,或者 Rust 编译器只是迂腐?
【问题讨论】:
这会与关联类型混淆。你必须绑定T: 'a
的意思是T
中的任何引用都必须比'a
寿命长。
这意味着你不能这样做:
let mut o: Option<&str> = Some("foo");
let mut nt = NewType x: &o ; // o has a reference to &'static str, ok.
let s = "bar".to_string();
let o2: Option<&str> = Some(&s);
nt.x = &o2;
这会很危险,因为nt
在块之后会有一个对s
的悬空引用。在这种情况下,它也会抱怨o2
的寿命也不够长。
我想不出一种方法可以让 &'a
引用包含较短生命周期的引用,并且显然编译器知道以某种方式(因为它告诉你添加约束)。但是我认为在某些方面说明限制是有帮助的,因为它使借用检查器不那么神奇:您可以仅从类型声明和函数签名来推断它,而不必查看字段是如何定义的(通常是实现文档中没有的详细信息)或函数的实现方式。
【讨论】:
我同意直言不讳从来都不是一件坏事。我总是问“源是否比目标更长寿”的问题,并且以这种心态很难写出你的例子,因为我隐含地知道编译器会拒绝它。感谢您提供简单而清晰的示例。以上是关于Rust编程语言入门之泛型Trait生命周期的主要内容,如果未能解决你的问题,请参考以下文章