欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Rust突破編譯器限制構造可修改的全局變量

 更新時間:2023年10月15日 11:10:00   作者:garfileo  
這篇文章主要為大家介紹了Rust突破編譯器限制構造可修改的全局變量示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

問題

在前面一些章節(jié)里,在使用正則表達式對文本進行分割時,皆使用局部變量存儲正則表達式,且皆為硬代碼——在程序運行時無法修改正則表達式。本章嘗試構造一個可在運行時被修改的全局變量用以表達正則表達式。

失敗的全局原始指針

倘若將原始指針作為全局變量,在程序運行時,可以令其指向與其類型相匹配的任何一個值,這是我想要的全局變量。于是,試著寫出以下代碼:

use regex::Regex;
use std::ptr::null_mut;
let a: *mut Regex = null_mut();

fn main() {
    a = Box::into_raw(Box::new(Regex::new(" *@ *")));
    let v = (*a).unwrap().split("num@ 123@456  @ 789");
    for i in v {
        println!("{}", i);
    }
}

Rust 編譯器編譯上述代碼時會報錯,建議使用 const 或 static 代替全局變量 a 的定義語句中的 let,亦即 Rust 語言不允許使用 let 定義全局變量。const 修飾的全局變量,其值不可修改。static 修飾的全局變量,其值可修改。故而,我將變量 a 的定義修改為

static a: *mut Regex = null_mut();

Rust 編譯器依然報錯,稱 *mut regex::Regex 類型的值不能被不同的線程安全共享,雖不甚知其意,但也應知此路不通了。

也許在素有經(jīng)驗的 Rust 程序員看來,上述代碼會令他一言難盡,但是如果我說通過以上代碼可以看出 Rust 語言并不希望程序員使用全局變量,料想不會引起他的反對。Rust 不希望什么,那是它的事,而我卻需要它?,F(xiàn)在的問題是,無法構造全局原始指針。Rust 編譯器給出的建議是,如果想讓 *mut regex::Regex 類型的指針作為全局變量,前提是需要為該類型實現(xiàn) Sync 特性。這個建議對于目前的我來說是超綱的,所以我完全可以認為,在 Rust 語言中不允許出現(xiàn)全局原始指針。

Option<T> 于事無補

在表示空值方面,Option<T> 類型可以代替原始指針,用該類型封裝原始指針是否能作為全局變量呢?試試看:

static foo: Option<*mut i32> = None;
fn main() {
    let a = 3;
    foo = Some(&a as *mut i32);
    println!("{:?}", foo);
}

答案是否定的。Rust 編譯器依然稱:

`*mut i32` cannot be shared between threads safely

并建議

shared static variables must have a type that implements `Sync`

此路依然不通。

結構體屏障

無論是直接用原始指針,還是用 Option<T> 封裝原始指針,在構造全局變量時,都會導致原始指針直接暴露在 Rust 編譯器面前,而編譯器堅持認為,所有的全局變量類型都應該實現(xiàn) Sync 特性。現(xiàn)在,換一個思路,倘若將原始指針類型封裝在結構體中,是否可以騙過編譯器呢?

以下代碼將 *mut i32 類型的指針封裝在一個結構體類型中,并使用該結構體類型構造全局變量:

#[derive(Debug)]
struct Foo {
    data: *mut i32
}
static mut A: Foo = Foo{data: std::ptr::null_mut()};
fn main() {
    unsafe {
        println!("{:?}", A);
    }
}

上述程序可以通過編譯,其輸出為

Foo { data: 0x0 }

以下代碼嘗試能否修改 A.data 的值:

let mut a = 3;
unsafe {
    A.data = &mut a as *mut i32;
    println!("{:?}", A);
    println!("{}", *A.data);
}

依然能通過編譯,其輸出結果與以下結果類似:

Foo { data: 0x7fff64cdecb4 }
3

這樣騙編譯器,好么?我不知道。Rust 標準庫在 std::marker::Sync 的文檔中提到,所有的基本類型,復合類型(元組、結構體和枚舉),引用,Vec<T>Box<T 以及大多數(shù)集合類型等皆實現(xiàn)了 Sync 特性,所以上述手法并不能稱為「騙」。

回到本章開始的問題,現(xiàn)在可寫出以下代碼:

use regex::Regex;
use std::ptr::null_mut;
#[derive(Debug)]
struct Foo {
    data: *mut Regex
}
static mut A: Foo = Foo{data: null_mut()};
fn main() {
    unsafe {
        A = Foo {data: Box::into_raw(Box::new(Regex::new(" *@ *").unwrap()))};
        let v = (*A.data).split("num@ 123@456  @ 789");
        for i in v {
            println!("{}", i);
        }
        let _ = Box::from_raw(A.data);
    }
}

注意,上述代碼中的 let _ = ... 表示不關心右側函數(shù)調(diào)用的返回值,但是該行代碼可將 A.data 指向的內(nèi)存空間歸還于 Rust 的智能指針管理系統(tǒng),從而實現(xiàn)自動釋放。

制造內(nèi)存泄漏

上述基于原始指針的全局變量構造方法似乎并不為 Rust 開發(fā)者欣賞,因為在他們眼里,任何一個原始指針都像一個不知道什么時候會被一腳踩上去的地雷,他們更喜歡是引用。

下面嘗試使用引用構造全局變量。由于引用不具備空值,所以必須使用 Option<T> 進行封裝,例如

use regex::Regex;
static mut A: Option<&Regex> = None;

fn main() {
    unsafe {
        let re = Regex::new(" *@ *").unwrap();
        A = Some(&re);
        // ... 待補充
    }
}

Rust 編譯器對上述代碼給出的錯誤信息是,re 被一個全局變量借用,但是前者的壽命短于后者,亦即當后者還存在時,前者已經(jīng)死亡,導致后者引用失效。在 C 語言中,這種錯誤就是鼎鼎有名的「懸垂指針」錯誤,Rust 編譯器會盡自己最大能力去阻止此類錯誤。

不過,Rust 標準庫給我們留了一個后門,使用 Box<T> 的 leak 方法可將位于堆空間的值的壽命提升為全局變量級別的壽命:

unsafe {
    let re = Box::new(Regex::new(" *@ *").unwrap());
    A = Some(Box::leak(re));
    let v = A.unwrap().split("num@ 123@456  @ 789");
    for i in v {
        println!("{}", i);
    }
}

需要注意的是,Box::leak 名副其實,會導致內(nèi)存泄漏,因為堆空間的值其壽命經(jīng) Box::leak 提升后,與程序本身相同,無法回收。Rust 官方說,如果你介意這樣的內(nèi)存泄漏,那就需要考慮走原始指針路線。

延遲初始化

對于支持運行時修改的全局變量,還有一類方法是將全局變量的初始化推遲在程序運行時,但該類方法要么依賴第三方庫(crate),例如 lazy_static,要么是標準庫目前尚未穩(wěn)定的功能 OnceCell,此外該類方法只能對全局變量完成一次賦值。這些方法,rzeo 并不打算使用,故而略過。

值的所有權轉移

基于值的所有權轉移也能實現(xiàn)在程序的運行時修改全局變量的值。例如

use regex::Regex;
static mut A: Option<Regex> = None;

fn main() {
    unsafe {
        let re = Regex::new(" *@ *").unwrap();
        A = Some(re);
        let v = A.unwrap().split("num@ 123@456  @ 789");
        for i in v {
            println!("{}", i);
        }
    }
}

不過,上述代碼無法通過編譯,原因是 Option<T> 的實例方法 unwrap 需要轉移實例的所有權——消耗一個臨時變量,但是上述代碼中的 Option<T> 的實例 A 是全局變量,與程序同壽,其所有權無法轉移。有兩種方法可規(guī)避該錯誤,一種是

unsafe {
    let re = Regex::new(" *@ *").unwrap();
    A = Some(re);
    match A {
        Some(ref b) => {
            let v = b.split("num@ 123@456  @ 789");
            for i in v {
                println!("{}", i);
            }
        },
        None => panic!("...")
    }
}

另一種是使用 Option<T> 的 as_ref 方法,將類型 &Option<T> 轉換為類型 Option<&T>,然后使用 Option<&T> 的 unwrap 方法:

unsafe {
    let re = Regex::new(" *@ *").unwrap();
    A = Some(re);
    let v = A.as_ref().unwrap().split("num@ 123@456  @ 789");
    for i in v {
        println!("{}", i);
    }
}

不妨將 as_ref 方法視為上述模式匹配代碼的簡化。

小結

全局變量是構成程序的不安全因素之一,但它并非洪水猛獸,只要保證程序在任一時刻全局變量不會被多個線程同時修改即可。如果全局變量給程序帶來了災難,這往往意味著是程序的設計出現(xiàn)了嚴重問題。我認為 Rust 對全局變量的限制太過于嚴厲,特別是在禁止直接將原始指針作為全局變量這一方面,畢竟即使不使用原始指針,對全局變量的修改在 Rust 語言看來,也是不安全的。既然都不安全,何必五十步笑百步。

以上就是Rust突破編譯器限制構造可修改的全局變量的詳細內(nèi)容,更多關于Rust全局變量的資料請關注腳本之家其它相關文章!

相關文章

  • Rust中的方法與關聯(lián)函數(shù)使用解讀

    Rust中的方法與關聯(lián)函數(shù)使用解讀

    在Rust中,方法是定義在特定類型(如struct)的impl塊中,第一個參數(shù)是self(可變或不可變),方法用于描述該類型實例的行為,而關聯(lián)函數(shù)則不包含self參數(shù),常用于構造新實例或提供一些與實例無關的功能,Rust的自動引用和解引用特性使得方法調(diào)用更加簡潔
    2025-02-02
  • Rust  利用 chrono 庫實現(xiàn)日期和字符串互相轉換的示例

    Rust  利用 chrono 庫實現(xiàn)日期和字符串互相轉換的示例

    在Rust中,chrono庫提供了強大的日期和時間處理功能,使得日期與字符串之間的轉換變得簡單,本文介紹了如何在Rust中使用chrono庫將日期轉換成字符串,以及如何將字符串解析為日期,對于需要進行日期時間格式化、解析或進行時區(qū)處理的開發(fā)者來說,chrono庫是一個不可或缺的工具
    2024-11-11
  • Rust如何使用config配置API

    Rust如何使用config配置API

    這篇文章主要介紹了Rust如何使用config配置API,這里記錄了如何聲明配置類型,讀取配置,通過環(huán)境變量來覆蓋配置值等開發(fā)中常見的動作,需要的朋友可以參考下
    2023-11-11
  • 如何用Rust打印hello world

    如何用Rust打印hello world

    這篇文章主要介紹了如何用Rust打印hello world,本文分步驟通過圖文并茂的形式給大家講解的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-09-09
  • Rust 累計時間長度的操作方法

    Rust 累計時間長度的操作方法

    在Rust中,如果你想要記錄累計時間,通常可以使用標準庫中的std::time::Duration類型,這篇文章主要介紹了Rust如何累計時間長度,需要的朋友可以參考下
    2024-05-05
  • rust中的match表達式使用詳解

    rust中的match表達式使用詳解

    在rust中提供了一個極為強大的控制流運算符match,這篇文章主要介紹了rust中的match表達式,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-08-08
  • Rust標量類型的具體使用

    Rust標量類型的具體使用

    本文主要介紹了Rust標量類型的具體使用,其中包括整數(shù)類型、浮點類型、布爾類型以及字符類型,具有一定的參考價值,感興趣的可以了解一下
    2024-03-03
  • Rust語言之Copy和Clone詳解

    Rust語言之Copy和Clone詳解

    在 Rust 中,Copy 和 Clone trait 用于控制類型的復制行為。它們允許你定義如何復制類型的值,以及在什么情況下可以復制。本文將詳細介紹這兩個 trait 的作用和用法,并通過代碼示例來展示它們的使用,需要的朋友可以參考下
    2023-05-05
  • Rust日期與時間的操作方法

    Rust日期與時間的操作方法

    Rust的時間操作主要用到chrono庫,接下來我將簡單選一些常用的操作進行介紹,感興趣的朋友跟隨小編一起看看吧
    2023-09-09
  • Rust for循環(huán)語法糖背后的API場景分析

    Rust for循環(huán)語法糖背后的API場景分析

    for語句是一種能確定循環(huán)次數(shù)的循環(huán),for 語句用于執(zhí)行代碼塊指定的次數(shù),今天通過本文給大家介紹Rust for循環(huán)語法糖背后的API場景分析,感興趣的朋友跟隨小編一起看看吧
    2022-11-11

最新評論