泛型、Trait、生命周期 一、提取函數消除重覆 fn main() { let number_list = vec![34, 50, 25, 100, 65]; let mut largest = number_list[0]; for number in number_list { if num ...
泛型、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