詳解Rust編程中的生命周期

1.摘要

生命周期在Rust編程中是一個重要概念, 它能確保引用像預期的那樣一直有效。在Rust語言中, 每一個引用都有其生命周期, 通俗講就是每個引用在程序執行的過程中都有其自身的作用域, 一旦離開其作用域, 其生命周期也宣告結束, 值不再有效。幸運的是, 在絕大多數時間里, 生命周期是隱含且可以進行推斷的, 類似于當有多種可能的類型時必須注明類型, 正因為如此, 所以Rust需要使用者使用泛型生命周期參數來注明它們的關系, 從而確保程序運行時實際使用的引用絕對有效。

2.懸垂引用問題

懸垂引用會導致Rust編程中出現一些潛在的安全問題, 例如: 程序在無意之中引用了非預期引用的數據, 而這種現象在沒有任何約束的情況下很容易出現。Rust編程中引入生命周期的主要原因就是避免編程過程中出現的懸垂引用問題。

下面看一個代碼示例:

fn main() {let num;{let count = 5;num = &count;}println!("num: {}", num);
}

首先定義了一個變量num, 下面的花括號表示進入到一個作用域, 在該作用域中, 定義了一個變量count,并賦值為5, 在這個內部作用域中,&count表示一個對變量count的引用, 然后將其賦給變量num, 在作用域的外部, 調用println打印出num的值。

先嘗試編譯一下這段代碼試試:

Rust編譯器報錯的地方指向代碼: num = &count, 并報了一個錯誤:"borrowed value does not live long enough", 意思是&count的值并沒有存在足夠久, 并很貼心的用藍色字告訴我們作用域的范圍界定。那么有一個問題, Rust編譯器是以什么機制來判定作用域使用的合法性呢?

3.Rust檢查機制

在Rust編譯器中, 有一個被稱為借用檢查器的機制, 它的主要工作原理是通過比較作用域來確保代碼中所有的借用都是有效的, 看一下下面的代碼標識:

fn main() {let num; ?  ------------------------- num_s{ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |let count = 5; ------ count_s ?  |num = &count;  --------- ? ? ? ? |} ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |println!("num: {}", num);------------
}

這里將上面代碼中的兩個關鍵變量num和count分別引入一個各自代表其生命周期的標識:num_s和count_s。很明顯可以看到, num變量的起點在作用域上面, 終點在作用域下面,。而count_s的生命周期起點在進入第一個花括號后面, 終點在第二個花括號前面, 也就是說, num變量的生命周期num_s包含了count_s的生命周期, 所以Rust編譯器利用借用檢查器比較兩個變量的生命周期大小, 很容易推斷出num的生命周期明顯要長。

上面的代碼被Rust編譯器拒絕編譯, 正是因為借用檢查器首先發現 num_s的生命周期比count_s要長, 而num = &count這句代碼, 被引用的對象&count比引用者num存在的時間更短, 因此產生了懸垂引用。

那么解決該問題的方式也比較簡單, 只要被引用對象和引用者處于同一作用域即可解決, 如下代碼:

方式一:

fn main() {let count = 5;let num = &count;println!("num: {}", num);
}

方式二:

fn main() {let num;{let count = 5;num = &count;println!("num: {}", num);}
}

4.泛型生命周期

下面有一段代碼, 主要完成了兩個字符串的長度比較功能, 其中compare函數負責完成兩個字符串的長度比較并返回長度最長的字符串的

切片。代碼如下:

fn compare(a: &str, b: &str) -> &str {if a.len() > b.len() {a} else {b}
}
?
fn main() {let sample1 = String::from("sample for suntiger");let sample2 = "suntiger";let c_result = compare(sample1.as_str(), sample2);println!("最長的字符串是 {}", c_result);
}

這段代碼編譯時,Rust編譯器的返回如下:

上面的錯誤提示分為三個部分: compare函數的兩個參數以及返回值存在生命周期問題。首先, Rust編譯器并不清楚將要返回的引用&str到底是指向參數a還是參數b, 其實作為程序員自己也是不知道的, 因為只有在運行時通過比較兩個參數的長度大小后才知道哪個參數切片的字符串內容更長。

因此, 根據Rust編譯器的綠色標記提示, 在編寫compare函數時, 必須增加泛型生命周期參數來定義引用間的關系以便Rust的檢查機制能夠正確分析。

5.生命周期注解

在上面的編譯器返回提示中, 綠色的部分: <'a>、&'a被稱為生命周期注解, 這個也是Rust語言獨特的語法, 看起來比較奇葩和抽象, 那么Rust如何去定義這個注解呢, 以下是簡單的語法:

&str ? ? ?  // 稱為引用
&'a str ? ? // 稱為帶有顯式生命周期的引用
&'a mut str // 稱為帶有顯式生命周期的可變引用

生命周期注解的一個重要作用就是告訴Rust編譯器在多個引用的泛型生命周期參數存在期間它們如何相互聯系。

嘗試將compare函數代碼修改如下:

fn compare<'a>(a: &'a str, b: &'a str) -> &'a str {if a.len() > b.len() {a} else {b}
}

再次嘗試編譯, Rust編譯器返回如下:

這次返回了正確的結果, 當在函數中使用生命周期注解時, 這些注解只存在于函數簽名中, 而不存在于函數體的任何代碼中, 當在實際應用過程中, 參數的引用傳給compare函數時, 被'a取代的具體生命周期是參數a的作用域與參數b的作用域重疊的那一部分, 換句話說就是兩個參數中生命周期較小的那一個。

6.結構體生命周期注解

在定義結構體時, 也要在相應的地方加上生命周期注解, 結構體定義如下:

struct PersonInfo<'a> {name: &'a str,
}

在該結構體中定義了一個name的字段, 其中存放了一個字符串切片, 為了能夠在結構體定義中使用生命周期參數, 必須在結構體名稱后面的括號中聲明泛型生命周期參數。

接下來需要在main函數中創建一個結構體實例, 將一個字符串切片內容傳給結構體參數, 代碼如下:

fn main() {let sayinfo = String::from("今天天氣不錯#挺風和日麗的...");let headerinfo = sayinfo.split('#').next().expect("找不到分隔符'#'");let pi = PersonInfo {name: headerinfo,};println!("分割name內容為: {}", pi.name);
}

在上面的代碼中, 對變量sayinfo中的內容作了字符串分割, 如果找到符號#,則取前面的內容,然后將該部分內容存到結構體字段中。

編譯結果如下:

因為變量sayinfo在結構體PersonInfo之前創建, 且結構體離開作用域之后,變量sayinfo仍然不會離開作用域, 因此PersonInfo實例中的引用一直都是有效的, 并不會出問題。

7.靜態生命周期

靜態生命周期和靜態變量一樣, 都有一個關鍵字: static, 例子代碼如下:

let sample: &'static str = "我是一個靜態周期的例子.";

現在變量sample的生命周期會一直持續, 在整個程序中都是有效的, 盡管靜態生命周期會避免編碼過程中的很多編譯器檢查錯誤, 但是一旦在編碼過程中出現懸垂引用的錯誤編碼時, 更正確的做法應該是想辦法解決懸垂引用的問題,而不是靠靜態生命周期避開錯誤。

8.總結

在本篇文章中我們探索了生命周期在Rust常見場景中的各種應用, 但在復雜的業務場景中, 可能還會遇到其它錯誤, 這時候依靠Rust編譯器強大的提示功能應該能夠準確找到出現問題的地方, 在這個過程中解決問題, 除了加深印象, 還能起到舉一反三的作用。

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

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

相關文章

15 reids哨兵機制

redis主機默認是10s發送一次心跳給從節點。 從節點默認1s去發送心跳給主節點。 1、原理 當主節點出現故障時&#xff0c;由Redis Sentinel自動完成故障發現和轉移&#xff0c;并通知應用方&#xff0c;實現高可用性。 從節點的主要兩個作用&#xff1a; 主節點的數據備份。…

【2023年APMCM亞太杯C題】完整代碼+結果分析+論文框架

2023年APMCM亞太杯C題 完整代碼結果分析論文框架第一問問題分析技術文檔1 基于AHP的新能源汽車發展影響因素分析1.1 AHP模型的構建1.2 AHP模型的求解 2 基于自適應ARIMA-非線性回歸模型的影響因素預測2.1 ARIMA模型的建立2.2 非線性回歸模型的建立2.3 自適應混合ARIMA-非線性回…

【數據結構/C++】線性表_順序表的基本操作

#include <iostream> using namespace std; #define MaxSize 10 // 1. 順序表 // 靜態分配 typedef struct {int data[MaxSize];int length; // 當前長度 } SqList; // 靜態分配初始化順序表 void InitList(SqList &L) {for (int i 0; i < MaxSize; i){L.data[i]…

政務大數據與資源平臺建設解決方案:PPT全文75頁,附下載

關鍵詞&#xff1a;智慧政務解決方案&#xff0c;大數據解決方案&#xff0c;數據中心解決方案&#xff0c;數據治理解決方案 一、政務大數據與資源平臺建設背景 1、政務大數據已成為智慧城市建設的必要基礎 為響應國家不斷加快5G基建、大數據、人工智能等新型基礎設施建設布…

在MySQL中,修改字段A相同的記錄的字段B ,要使得字段C小的記錄的字段B值等于字段C大的記錄的字段B值

例如&#xff1a;更新具有相同電話號碼的用戶記錄&#xff0c;使得updatetime小的記錄的name值等于updatetime大的記錄的name值。 首先&#xff0c;我們需要創建一個用戶表&#xff0c;這個用戶表包含以下字段&#xff1a;phone&#xff0c;updatetime, name。以下是創建這個表…

Redis的性能,哨兵模式,集群,

Redis的性能管理; redis的數據保存在內存中 redis-cli info memory redis內存使用info memory命令參數解析 used_memory:236026888 由 Redis 分配器分配的內存總量&#xff0c;包含了redis進程內部的開銷和數據占用的內存&#xff0c;以字節&#xff08;byte&#xff09…

css里面的@import

import 說明 用于從其他樣式表導入樣式規則。可以是絕對或相對路徑&#xff0c;也可以是一個在線的url地址。import 規則必須在 CSS 文檔的頭部&#xff0c;但可以在 charset 規則后面。import 規則不是一個嵌套語句&#xff0c;import不能在條件組的規則中使用。import 規則…

虛擬化原理

目錄 什么是虛擬化廣義虛擬化狹義虛擬化 虛擬化指令集敏感指令集虛擬化指令集的工作模式監視器對敏感指令的處理過程&#xff1a; 虛擬化類型全虛擬化類虛擬化硬件輔助虛擬化 虛擬化架構裸金屬架構宿主機模式架構 什么是虛擬化 虛擬化就是通過模仿下層原有的功能模塊創造接口來…

【開源】基于JAVA的衣物搭配系統

項目編號&#xff1a; S 016 &#xff0c;文末獲取源碼。 \color{red}{項目編號&#xff1a;S016&#xff0c;文末獲取源碼。} 項目編號&#xff1a;S016&#xff0c;文末獲取源碼。 目錄 一、摘要1.1 項目介紹1.2 項目錄屏 二、研究內容2.1 衣物檔案模塊2.2 衣物搭配模塊2.3 衣…

linux進程調度(二)-進程創建

文章目錄 2.進程創建和終止2.1 進程創建的4種方法2.2 進程創建過程分析2.2.1 copy_process函數分析2.2.1.1 dup_task_struct函數分析2.2.1.2 sched_fork函數分析2.2.1.3 copy_mm函數分析2.2.1.4 copy_thread函數分析 2.2.2 wake_up_new_task函數分析 2.進程創建和終止 在 Linu…

常用數據存儲格式介紹:Excel、CSV、JSON、XML

在現代數字時代&#xff0c;數據經過提煉后可以推動創新、簡化運營并支持決策流程。然而&#xff0c;在提取數據之后&#xff0c;并將其加載到數據庫或數據倉庫之前&#xff0c;需要將數據轉化為可用的數據存儲格式。本文將介紹開發者常用的4種數據存儲格式&#xff0c;包括 Ex…

布局下一個時代,UTONMOS夯實元宇宙發展基礎

從 PC 互聯網到移動互聯網&#xff0c;再到元宇宙&#xff0c;互聯網的發展在一直不斷演變和升級著。元宇宙的時代紅利將帶來從底層基礎設施向外延展到用戶體驗的全面升級。 人們以各自不同視角理解元宇宙。但我們認為&#xff0c;目前學術界和產業界對元宇宙雖然沒有統一規范的…

JavaScript 閉包技巧

什么是閉包&#xff1f; MDN&#xff1a;“閉包是捆綁在一起&#xff08;封閉&#xff09;的函數及其周圍狀態&#xff08;詞法環境&#xff09;的引用的組合。換句話說&#xff0c;閉包使您可以從內部函數訪問外部函數的作用域。在 JavaScript 中&#xff0c;每次創建函數時都…

css引入的三種方式

css引入的三種方式 一、內聯樣式二、外部樣式表三、 內部樣式表總結trouble 一、內聯樣式 內聯樣式也被稱為行內樣式。它是將 CSS 樣式直接應用于 HTML 元素的 style 屬性中的一種方式 <p style"color: blue; font-size: 16px;">這是一個帶有內聯樣式的段落。&…

Modbus RTU轉Profinet網關連接PLC與變頻器通訊在機床上應用案例

背景&#xff1a;以前在機床加工車間里&#xff0c;工人們忙碌地操作著各種機床設備。為了使整個生產過程更加高效、流暢&#xff0c;進行智能化改造。 方案&#xff1a;在機床上&#xff0c;PLC通過Modbus RTU轉Profinet網關連接變頻器進行通訊&#xff1a;PLC作為整個生產線…

實現簡單的操作服務器和客戶端(下)

一、說明 描述:本教程介紹如何使用 simple_action_client 庫創建斐波那契操作客戶端。此示例程序創建一個操作客戶端并將目標發送到操作服務器。 內容 代碼代碼解釋編譯運行操作客戶端連接服務器和客戶端二、代碼 首先,在您喜歡的編輯器中創建actionlib_tutorials/src/fib…

【封裝UI組件庫系列】封裝Icon圖標組件

封裝UI組件庫系列第三篇封裝Icon圖標組件 &#x1f31f;前言 &#x1f31f;封裝Icon 1.創建Icon組件 2.引用svg圖標庫 第一步 第二步 第三步 3.二次封裝 4.封裝自定義屬性 &#x1f31f;總結 &#x1f31f;前言 在前端開發中&#xff0c;大家可能已經用過各種各樣的UI組…

VUE項目部署過程中遇到的錯誤:POST http://124.60.11.183:9090/test/login 405 (Not Allowed)

我當初報了這個405錯誤&#xff0c;再網上查了半天&#xff0c;他們都說什么是nginx部署不支持post訪問靜態資源。 但后面我發現我是因為另一個原因才導致的無法訪問。 我再vue中有使用devServer:{ proxy:{} }進行路由轉發。 但是&#xff01;&#xff01; 在這個配置只…

接口測試學習路線

接口測試分為兩種&#xff1a; 測試外部接口&#xff1a;系統和外部系統之間的接口 如&#xff1a;電商網站&#xff1a;支付寶支付 測試內部接口&#xff1a;系統內部的模塊之間的聯調&#xff0c;或者子系統之間的數據交互 測試重點&#xff1a;測試接口參數傳遞的正確性&…

node與 pnpm、node-sass 等工具的版本兼容關系

1. node & pnpm 2. node & node-sass 3. node-sass & sass-loader sass-loader依賴于node-sass&#xff0c;以下是部分版本號對應