Rust 入門 生命周期-next2 (十九)

生命周期消除

實際上,對于編譯器來說,每一個引用類型都有一個生命周期,那么為什么我們在使用過程中,很多時候無需標注生命周期?例如:

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[..]
}

該函數的參數和返回值都是引用類型,盡管我們沒有顯式的為其標柱生命周期, 編譯依然可以通過。 其實原因不復雜,編譯器為了簡化用戶的使用,運用來生命周期消除大法。

對于first_word 函數, 它的返回值是一個引用類型,那么該引用只有兩種情況:

? ? ? ?1.從參數獲取

? ? ? ? 2.從函數體內新創建的變量獲取

如果是后者,就會出現懸垂引用,最終被編譯器拒絕,因此只剩一種情況:返回值的引用是獲取自參數。 這就意味著參數和返回值的生命周期是一樣的。 道理很簡單,我們能看出來,編譯器自然也能看出來,因此。就算我們不標注生命周期,也不會產生其一。

實際上,在Rust 1.0 版本之前。這種代碼果斷不給通過,因為Rust 要求必須顯式的為所有引用標注生命周期

fn first_word<'a>(s: &'a str) -> & 'a str {

在些來大量的類似代碼后,Rust社區抱怨聲四起,包括開發者自己都忍不了了,最終揭鍋而起,這才有來我們今日的幸福。

生命周期消除的規則不是一蹴而就,而是伴隨著 總結-改善 流程的周而復始,一步一步走到今天,這也意味著,該規則以后可能也會進一步增加,我們需要手動標注生命周期的時候也會越來越少。?

在開始之前有幾點需要注意

? ? ? ? 1.消除規則不是萬能的, 若編譯器不能確定某件事是正確時, 會直接判為不正確。那么你還是需要手動標注生命感周期

? ? ? ? 2.函數或者方法中,參數的生命周期被成為? 輸入生命周期,返回值的生命周期 被成為 輸出生命周期

三條消除規則

編譯器使用三條消除規則來確定哪些場景不需要顯式地去標注生命周期,其中第一條規則應用在輸入生命周期上,第二,三條應用在輸出生命周期上,若編譯器發現三條規則都不適用時,就會報錯, 提示你需要手動標注生命周期。

? ? ? ? 1.每一個引用參數都會獲得獨自的生命周期

? ? ? ? 例如一個引用參數的函數就有一個生命周期標注: fn foo<'a>(x: &'a i32),? 兩個引用參數的有兩個生命周期標注 fn foo<'a,'b>(x: &'a i32, y: &'b i32) 依此類推

? ? ? ? 2.若只有一個輸入生命周期(函數參數中只有一個引用類型) ,那么該生命周期會被賦給所有的輸出生命周期, 也就是所有返回值的生命周期都等于該輸入生命周期。

? ? ? ? 例如函數 fn foo(x: &i32) -> &i32, x 參數的生命周期會被自動賦給返回值,&i32,? 因此該函數等同于 fn foo<'a>(x: &'a i32) -> &'a i32

? ? ? ? 3. 若存在多個輸入生命周期,且其中一個是&self 或 &mut self ,則 &self 的生命周期被賦給所有的輸出生命周期

? ? ? ??擁有 &self? 形式的參數,說明該函數是一個 方法, 該規則讓方法的使用便利度大幅提升。?

規則其實很好理解,但是,愛是靠的讀者肯定要發問來, 例如第三條規則,若一個方法,它的返回值的生命周期就是跟參數 &self 的不一樣怎么辦? 總不能強迫我返回的值總是和 &self 活得一樣久吧? 答案很記得那年: 手動標注生命周期,因為這些規則知識編譯器發現你沒有標注生命周期時默認去使用的, 當你標注生命周期后, 編譯器自然會乖乖聽你的話。

讓我們假裝自己是編譯器,然后看下以下的函數該如何引用這些規則:

例子1?

fn first_word(s: &str) -> &str { // 實際項目中的手寫代碼

首先,我們手寫的代碼如上所示時,編譯器會想引用第一條規則, 為每個參數標注一個生命周期:?

fn first_word<'a>(s: &'a str) -> &str{ // 編譯器自動為參數添加生命周期

此時,第二條規則就可以進行應用,因為函數只有一個輸入生命周期,因此該生命周期會被賦予所有的輸出生命周期:?

fn first_word<'a>(s: &'a str) -> &'a str{ // 編譯器自動為返回值添加生命周期

此時,編譯器為函數簽名中的所有引用都自動添加來具體的生命周期,因此編譯通過,且用戶無需手動去標注生命周期,只要按照 fn? first_word(s: &str) -> &str { 的形式寫代碼即可。

例子2

fn longest(x: &str,y: &str) -> &str { // 實際項目中的手寫代碼

首先,編譯器會引用第一條規則,為每個參數都標注生命周期:?

fn longest<'a,'b>(x:&'a str ,y: &'b str) -> &str {

但是此時,第二條規則卻無法被使用,因為輸入生命周期有兩個,第三條規則也不符合,因為它是函數,不是方法,因此沒有 &self 參數。 在挑用所有規則后,編譯器依然無法為返回值標注合適的生命周期。因此,編譯器就會報錯,提示我們需要手動標注生命周期。

error[E0106]: missing lifetime specifier
--> src/main.rs:1:47
|
1 | fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
| ? ? ? ? ? ? ? ? ? ? ? ------- ? ? ------- ? ? ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
note: these named lifetimes are available to use
--> src/main.rs:1:12
|
1 | fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
| ? ? ? ? ? ?^^ ?^^
help: consider using one of the available lifetimes here
|
1 | fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'lifetime str {
| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?+++++++++

? 不得不說,Rust 編譯器真的很強大,還貼心的給我們提示來該如何修改,雖然。。。好像。。。 。它的提示貌似不太準確,這里我們希望參數和返回值都是 'a 生命周期。

方法中的生命周期

先來回憶下泛型的語法:?

struct Point<T> {x: T,y: T,
}impl<T> Point<T> {fn x(&self) -> &T {&self.x}}

實際上,為具有生命周期的結構體實現方法時,我們使用的語法跟泛型參數語法很相似:?

struct ImportantExcerpt<'a> {part: &'a str,
}impl<'a> ImportantExcerpt<'a> {fn level(&self) -> i32 {3}
}

其中有幾點需要注意的:

? ? ? ?  1.impl 中必須使用結構體的完整名稱, 包括 <'a> , 因為生命周期標注也是結構體類型的一部分!

? ? ? ? 2.方法簽名中,往往不需要標注生命周期,得益于生命周期消除的第一和第三規則?

下面的例子展示來第三規則引用的場景:

impl<'a> ImportantExcerpt<'a> {fn announce_and_return_part(&self , announcement: &str) -> &str{println!("Attention please: {}",announcement);self.part}    
}

首先,編譯器引用第一規則,給予每個輸入參數一個生命周期:?

impl<'a> ImportantExcerpt<'a> {fn announce_and_return_part<'b>(&'a self,announcement: &'b str) -> &str {println!("Attention please:{} ",announcement);self.part}
}

需要注意的是, 編譯器不知道 announcement 的生命周期帶地多長,因此它無法簡單的給予它生命周期 'a , 而是重新聲明了一個全新的生命周期 'b .?

接著,編譯器應用第三規則額,將 &self 的生命周期賦給返回值 &str :

impl<'a> ImportantExcerpt<'a> {fn announce_and_return_aprt<'b> (&'a self, announcement: &'b str) -> 'a str{println!("Attention please: {}",announcement);self.part}    
}    

最開始的代碼,盡管我們沒有給方法標注生命周期,但是在第一和第三規則的配合下,編譯器依然完美的為我們亮起來綠燈。

在結束這塊內容之前,再來做一個有趣的修改, 將方法返回的生命周期改為'b :?

impl<'a> ImportantExcerpt<'a> {fn announte_and_return_part<'b>(&'a self,announcement: & 'b str) -> &'b str {println!("Attention please: {}",announcement);self.part}
}

此時編譯器會報錯,因為編譯器無法知道 'a 和 'b 的關系,&self? 生命周期是'a? ,那么 self.part 的生命周期也是? 'a , 但是好巧不巧的是,我們手動為返回值 self.part 標注來生命周期 'b, 因此編譯器知道 'a? 和 'b 的關系。?

有一點很容易推理出來: 由于 &'a self 是被引用的一方,因此引用它的&'b str 必須要活得比它端,否則會出現懸垂引用。 因此說明生命周期 'b 必須要比 'a 小,只要滿足來這一點,編譯器就不會在報錯

impl<'a: 'b,'b> ImportantExcerpt<'a> {fn announce_and_return_part(&'a self,announcement: &'b str) -> &'b str{println!("attention please: {}",announcement);self.part}
}

一個復雜的玩意被甩到來你面前,就問怕不怕?

就關鍵點稍微解釋下: 

? ? ? ? 1.‘a: 'b, 是生命周期約束語法, 跟泛型越說非常顯式,用于說明'a 必須比'b 活得九

? ? ? ? 2.可以把 'a 和 'b 都在同一個地方聲明:(如上),或者分開聲明 但通過where 'a:'b 約束生命周期關系

impl<'a> ImportantExcerpt<'a> {fn announce_add_return_part<'b>(&'a self,announcement: &'b str) -> &'b str where 'a: 'b,{println!("Attention please: {}",announcement);self.part}
}

總之,實現方法比想象中簡單,: 加一個約束,就能暗示編譯器,盡管引用吧, 反正我先引用的內容比我活得久

靜態生命周期

在Rust中有一個非常特殊的生命周期,那就是 'static ,擁有該生命周期的引用可以和整個程序活得一樣久。

在之前我們學過字符串字面量, 提到過它是被硬編碼進Rust的二進制文件中,因此這些字符串變量全部具有 'static 的生命周期

let s: &'static str = "我沒啥優點,就是活得久,";

這時候,有些聰明的小腦瓜就開始動來, 當生命周期不知道怎么標時,對類型施加一個靜態生命周期的約束 T : 'static 是不是很爽? 這樣我和編譯器再也不用操心它到底活多久來,

嗯,只能說,這個想法是對的,在不少情況下, 'static 約束哦確實可以解決生命周期編譯不通過的問題, 但是問題來了,: 本來該引用沒有活那么久,但是你非要說它活那么久,玩意引入了潛在的Bug 怎么辦?

因此遇到因為生命周期導致的編譯不通過問題, 首先想的應該是,: 是否是我們試圖創建一個懸垂引用,或者是試圖匹配不一致的生命周期,而不是簡單粗暴的用 'static 來解決問題。

但是話說回來, 存在即合理,有時候, 'static 確實可以幫助我們解決非常復雜的生命周期問題, 甚至是無法被手動解決的生命周期問題, 那么此時就應該放心大膽的用, 只要你確定:? 你的所有引用的生命周期都是正確的。知識編譯器太笨不懂罷來。

總結下:

? ? ? ? 1.生命周期' static 意味著能和程序活得一樣久,例如字符串字面量和特征對象

? ? ? ? 2.是在遇到解決不了的生命周期標注問題, 可以嘗試 T: 'static ,有時候它會給你奇跡

一個復雜例子: 泛型 ,特征約束

手指已經疲軟物理,我好想停止,但是華麗的開場都要有與之匹配的謝幕,那我們就用一個稍微復雜點的例子來結束:?

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}
}

依然是熟悉的配方longest? , 但是多來一段廢話:ann ,因為要用格式化{}? 來輸出 ann , 因此需要實現 Display 特征。

總結

我不知道支撐我一口氣寫完的勇氣是什么, 也許是不做完不算夫斯基,也許是一些讀者對本書的期待,不管如何, 這章足足寫了17000字,可惜不是寫小說, 不然肯定可以獲取很多月票;

但是還沒完,是的,就算是將近兩萬字,生命周期的旅程依然沒有完結,在本書的進階部分,我們將介紹一些關于生命周期的高級特性, 這些特性你在其它中文書中目前還看不到的。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/93755.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/93755.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/93755.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Three.js 動畫循環學習記錄

在上一篇文章中&#xff0c;我們學習了Three.js 坐標系系統與單位理解教程&#xff1a; Three.js 坐標系系統與單位理解教程 接下來我們要學習的是Three.js 的動畫循環 一、動畫循環基礎原理 1. 什么是動畫循環&#xff1f; 動畫循環是連續更新場景狀態并重新渲染的過程&am…

ktg-mes 改造成 Saas 系統

ktg-mes 改造成 Saas 系統 快速檢驗市場&#xff0c;采用最簡單的方案&#xff0c;即添加表字段 截止2025年8月16日上傳的ktg-mes搭建存在一些問題&#xff0c;搭建可看文章&#xff1a; 搭建ktg-mes 改造 1. 添加租戶表 create table sys_tenant (tenant_id bigint au…

【新手易混】find 命令中 -perm 選項的知識點

find 命令是 Linux/Unix 系統中強大的文件查找工具&#xff0c;廣泛用于根據文件名、類型、時間、權限等條件搜索文件。其中&#xff0c;-perm 選項用于按文件權限查找文件&#xff0c;而在 -perm /mode 中出現的斜杠 / 是一種特殊的語法&#xff0c;表示“按位或&#xff08;O…

gdb的load命令和傳給opeocd的monitor flash write_image erase命令的區別

問&#xff1a; "monitor flash write_image erase ${workspaceFolder}/obj/ylad_led_blink.elf", 和 "load", "executable" : "${workspaceFolder}/obj/ylad_led_blink.elf", 的區別&#xff1f;答&#xff1a; 你提到的 "monit…

1. Docker的介紹和安裝

文章目錄1. Docker介紹核心概念核心優勢與虛擬機的區別一句話總結2. Docker的安裝Windows 10/11 安裝 Docker Desktop&#xff08;推薦 WSL2 方式&#xff09;Linux&#xff08;以 Ubuntu / Debian 系為例&#xff09;Docker 是一個開源的容器化平臺&#xff0c;它允許開發者將…

fastdds.ignore_local_endpoints 屬性

Fast DDS 的 fastdds.ignore_local_endpoints 屬性用于控制同一 DomainParticipant 下的本地端點&#xff08;即 DataWriter 和 DataReader&#xff09;是否自動匹配。以下是對該功能的詳細解釋&#xff0c;并翻譯為中文&#xff0c;結合其上下文、實現原理和使用場景&#xff…

華清遠見25072班C語言學習day11

重點內容:函數&#xff1a;定義&#xff1a;返回值類型 函數名(參數列表) { //函數體 }函數的參數列表中可以有多個數據返回值&#xff1a;如果函數沒有返回值可以寫成void 返回值的作用&#xff0c;函數的結果用來返回給主調函數的&#xff0c;如果主調函數處不需要函數的結果…

視覺語言導航(7)——VLN的數據集和評估方法 3.2

這是課上做的筆記&#xff0c;因此很多記得比較急&#xff0c;之后會逐步完善&#xff0c;每節課的邏輯流程寫在大綱部分。成功率(SR)導航誤差(NE)成功加權路徑長度&#xff08;SucceedPLength&#xff09;軌跡長度&#xff08;TL&#xff09;先知成功率&#xff08;OS&#xf…

ElasticSearch不同環境同步索引數據

目的&#xff1a;在生產環境把一個索引的數據同步到測試環境中1、在生產環境導出json數據curl -u "adims_user:xkR%cHwR5I9g" -X GET "http://172.18.251.132:9200/unify_info_mb_sp_aggregatetb_0004/_search?scroll1m" -H Content-Type: applicatio…

咨詢進階——解讀咨詢顧問技能模型

適應人群為咨詢行業從業者、咨詢團隊管理者、想提升咨詢技能的職場人士及咨詢公司培訓人員。主要內容圍繞咨詢顧問技能模型展開,核心包括五大核心能力(解決問題能力,涵蓋洞察力、分析技巧、問題構建等,從識別問題實質到構建新分析方法分層次闡述;管理能力,涉及管理他人與…

2025年- H98-Lc206--51.N皇后(回溯)--Java版

1.題目描述2.思路 二維數組集合 (1&#xff09;N皇后規則 1&#xff09;不能同行&#xff08;同一行不能出現2個皇后&#xff09; 2&#xff09;不能同列&#xff08;同一列不能出現2個皇后&#xff09; 3&#xff09;不能說45度或135度&#xff08;斜對角線不能出現2個皇后&am…

5G + AI + 云:電信技術重塑游戲生態與未來體驗

在數字娛樂蓬勃發展的今天&#xff0c;游戲產業已然成為科技創新的前沿陣地。電信網絡也經歷了一場深刻的蛻變&#xff0c;從最初僅僅是 “內容傳輸管道”&#xff0c;搖身一變成為與游戲深度綁定的技術共生體。5G 不斷刷新著體驗的邊界&#xff0c;AI 徹底顛覆傳統的創作模式&…

【React Hooks】封裝的藝術:如何編寫高質量的 React 自-定義 Hooks

【React Hooks】封裝的藝術&#xff1a;如何編寫高質量的 React 自-定義 Hooks 所屬專欄&#xff1a; 《前端小技巧集合&#xff1a;讓你的代碼更優雅高效》 上一篇&#xff1a; 【React State】告別 useState 濫用&#xff1a;何時應該選擇 useReducer 作者&#xff1a; 碼力…

華為GaussDB的前世今生:國產數據庫崛起之路

在數據庫領域&#xff0c;華為GaussDB已成為一顆耀眼的明星&#xff0c;為企業核心業務數字化轉型提供堅實的數據底座。但這并非一蹴而就&#xff0c;其背后是長達二十余年的技術沉淀、戰略投入與持續創新。本文將深入探尋華為GaussDB的歷史沿革與核心技術細節&#xff0c;展現…

數據結構初階(16)排序算法——歸并排序

2.4 歸并排序 歸并排序&#xff08;Merge Sort&#xff09;是基于分治思想的經典排序算法。核心邏輯&#xff1a; 分而治之——把復雜排序問題拆分成簡單子問題解決&#xff0c;再合并子問題的結果。聯系鏈表的合并&#xff1a;兩個有序鏈表l1、l2創建新鏈表l3&#xff08;帶頭…

MATLAB實現匈牙利算法求解二分圖最大匹配

MATLAB實現匈牙利算法求解二分圖最大匹配 匈牙利算法&#xff08;也稱為Kuhn-Munkres算法&#xff09;是解決二分圖最大匹配問題的經典算法。 代碼 function [matching, max_match] hungarian_algorithm(adjMatrix)% HUNGARIAN_ALGORITHM 實現匈牙利算法求解二分圖最大匹配% 輸…

自定義table

更好<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"utf-8"><title>數據表格</title><style>* {margin: 0;padding: 0;box-sizing: border-box;font-size: 14px;}html,body {width: 100%;height: 100%…

面向R語言用戶的Highcharts

如果您喜歡使用 R 進行數據科學創建交互式數據可視化&#xff0c;那么請你收藏。今天&#xff0c;我們將使用折線圖、柱狀圖和散點圖來可視化資產回報。對于我們的數據&#xff0c;我們將使用以下 5 只 ETF 的 5 年月回報率。 SPY (S&P500 fund)EFA (a non-US equities fun…

【測試工具】OnDo SIP Server--輕松搭建一個語音通話服務器

前言 Ondo SIP Server 是一款基于 SIP(Session Initiation Protocol)協議的服務器軟件&#xff0c;主要用于實現 VoIP(Voice over IP)通信&#xff0c;支持語音通話、視頻會議等多媒體會話管理&#xff0c;非常適合學習和測試VoIP的基本功能。本文介紹Ondo SIP Server的安裝、…

瘋狂星期四文案網第42天運營日記

網站運營第42天&#xff0c;點擊觀站&#xff1a; 瘋狂星期四 crazy-thursday.com 全網最全的瘋狂星期四文案網站 運營報告 今日訪問量 今日搜索引擎收錄情況 網站優化點 優化一些發現的seo錯誤 增加顏文字欄目 增加了一些tag