關(guān)于rust中內(nèi)存模塊-代碼分析(一)

對(duì)比現(xiàn)代語法的高級(jí)語言如Java/Go/Python等冬三,Rust需要對(duì)內(nèi)存進(jìn)行控制尤勋,即程序可在代碼中編寫專屬內(nèi)存管理系統(tǒng)佣谐,并將內(nèi)存管理系統(tǒng)與語言類型相關(guān)聯(lián),在內(nèi)存塊與語言類型間能夠自如的進(jìn)行轉(zhuǎn)換涎劈。但相對(duì)于C來說,rust的現(xiàn)代語法特性及內(nèi)存安全會(huì)導(dǎo)致rust的內(nèi)存塊與類型系統(tǒng)的轉(zhuǎn)換細(xì)節(jié)相對(duì)非常復(fù)雜阅茶,不易被透徹理解蛛枚。接下來從代碼層面來深入理解rust內(nèi)存及內(nèi)存安全:

Rust標(biāo)準(zhǔn)庫內(nèi)存模塊代碼

%本地代碼路徑%/src\rust\library\core\src\alloc*.*
%本地代碼路徑%\src\rust\library\core\src\ptr*.*
%本地代碼路徑%\src\rust\library\core\src\mem*.*
%本地代碼路徑%\src\rust\library\core\src\intrinsic.rs
%本地代碼路徑%\src\rust\library\alloc\src\alloc.rs

從內(nèi)存角度來考察一個(gè)變量,則每個(gè)變量具備統(tǒng)一的內(nèi)存參數(shù):

  1. 變量的首地址脸哀,是一個(gè)usize的數(shù)值
  2. 變量類型占用的內(nèi)存塊大小
  3. 變量類型內(nèi)存字節(jié)對(duì)齊的基數(shù)
  4. 變量類型中成員內(nèi)存順序

若是變量成員是復(fù)合類型蹦浦,可遞歸上面的四個(gè)參數(shù).

rust與c的“異同”

不同于C,rust則認(rèn)為變量類型、成員順序與編譯優(yōu)化不可分割送漠,因此,變量成員內(nèi)存順序完全由編譯器控制,C中變量類型成員的順序是不能被編譯器改動(dòng)的博投。這使得C變量的內(nèi)存布局對(duì)程序員是透明的尿瞭。這種透明性導(dǎo)致了C語言在設(shè)計(jì)類型內(nèi)存布局的操作中會(huì)出現(xiàn)一些"壞代碼"充石。如,直接用頭指針+偏移數(shù)值來獲得類型內(nèi)部變量的指針丛版,直接導(dǎo)致變量類型可修改性極差好芭。

但與C相同之處: rust能夠?qū)⒁粔K內(nèi)存塊直接轉(zhuǎn)換成某一類型變量恩尾。這也是rust能夠操作系統(tǒng)內(nèi)核編程及其高效的“核心”弛说。不過也因這個(gè)轉(zhuǎn)換使得代碼可以繞過編譯器的類型系統(tǒng)檢查,造成了bug也繞過了編譯器的某些錯(cuò)誤檢查翰意,而這些錯(cuò)誤很可能在系統(tǒng)運(yùn)行很久之后才真正的出錯(cuò)木人,造成排錯(cuò)的極高成本。

而rust在確保這些的同時(shí)冀偶,并通過明確標(biāo)識(shí)unsafe, 再加上整體的內(nèi)存安全框架設(shè)計(jì)醒第,使得此類錯(cuò)誤更易被發(fā)現(xiàn),更易被定位进鸠,極大的降低了錯(cuò)誤的數(shù)目及排錯(cuò)的成本稠曼。
不過unsafe讓初學(xué)rust語言的程序員產(chǎn)生“排斥”,但unsafe實(shí)際上是rust不可分割的部分客年,一個(gè)好的rust程序員絕不是不使用unsafe霞幅,而是能夠準(zhǔn)確的把握好unsafe使用的合適場(chǎng)合及合適范圍,必要的時(shí)候必須使用量瓜,但不濫用司恳。
關(guān)于rust中的“安全”與“不安全”

接下來為了掌握rust的內(nèi)存我們會(huì)從如下幾個(gè)部分入手:

  1. 編譯器提供的固有內(nèi)存操作函數(shù)
  2. 內(nèi)存塊與類型系統(tǒng)的結(jié)合點(diǎn):裸指針 *const T/*mut T
  3. 裸指針的包裝結(jié)構(gòu): NonNull<T>/Unique<T>
  4. 未初始化內(nèi)存塊的處理:MaybeUninit<T>/ManuallyDrop<T>
  5. 堆內(nèi)存申請(qǐng)及釋放

針對(duì)初始化變量相關(guān)的指針操作

關(guān)于裸指針

裸指針*const T/* mut T將內(nèi)存和類型系統(tǒng)相關(guān)聯(lián):
1、 *const T代表了一個(gè)內(nèi)存塊绍傲,指示了內(nèi)存塊首地址扔傅,大小,對(duì)齊等屬性烫饼,以及元數(shù)據(jù)猎塞,但不保證這個(gè)內(nèi)存塊的有效性和安全性

2杠纵、與*const T/* mu T不同: &T/&mut T則保證內(nèi)存塊是安全和有效的邢享,這表示&T/&mut T滿足內(nèi)存塊首地址對(duì)齊,內(nèi)存塊已經(jīng)完成了初始化淡诗。

在rust中骇塘,&T/&mut T是被綁定在某一內(nèi)存塊上伊履,只能對(duì)這一內(nèi)存塊讀寫。

對(duì)于內(nèi)存塊更復(fù)雜的操作款违,由*const T/*mut T 負(fù)責(zé)

主要有:

  1. 將usize類型數(shù)值強(qiáng)制轉(zhuǎn)換成裸指針類型唐瀑,以此數(shù)值為首地址的內(nèi)存塊被轉(zhuǎn)換為相應(yīng)的類型; 不過若是對(duì)這一轉(zhuǎn)換后的內(nèi)存塊進(jìn)行讀寫插爹,可能造成內(nèi)存安全問題哄辣。

  2. 在不同的裸指針類型之間進(jìn)行強(qiáng)制轉(zhuǎn)換,實(shí)質(zhì)上完成了裸指針指向的內(nèi)存塊的類型強(qiáng)轉(zhuǎn)赠尾,若是對(duì)這一轉(zhuǎn)換后的內(nèi)存塊進(jìn)行讀寫力穗,可能造成內(nèi)存安全問題。

  3. *const u8作為堆內(nèi)存申請(qǐng)的內(nèi)存塊綁定變量 气嫁。

  4. 內(nèi)存塊置值操作当窗,如清零或置一個(gè)魔術(shù)值 。

  5. 顯式的內(nèi)存塊拷貝操作寸宵,某些情況下崖面,內(nèi)存塊拷貝是必須的高性能方式。

  6. 利用指針偏移計(jì)算獲取新的內(nèi)存塊梯影, 比如在數(shù)組及切片訪問巫员,字符串,協(xié)議字節(jié)填寫甲棍,文件緩存等都需要指針偏移計(jì)算简识。

  7. 從外部的C函數(shù)接口對(duì)接的指針參數(shù)。

等等

rust的裸指針類型不像C語言的指針類型那樣僅僅是一個(gè)地址值感猛,為滿足實(shí)現(xiàn)內(nèi)存安全的類型系統(tǒng)需求财异,并兼顧內(nèi)存使用效率和方便性,rust的裸指針實(shí)質(zhì)是一個(gè)較復(fù)雜的類型結(jié)構(gòu)體唱遭。

裸指針具體實(shí)現(xiàn)

*const T/*mut T實(shí)質(zhì)是個(gè)結(jié)構(gòu)體戳寸,由兩個(gè)部分組成:第一個(gè)部分是一個(gè)內(nèi)存地址;第二個(gè)部分對(duì)這個(gè)內(nèi)存地址的約束性描述-元數(shù)據(jù)拷泽。

偽碼如下(并非真實(shí)的代碼定義)

struct Pointer  {
   address: usize,  // 當(dāng)前裸指針的地址
   metadata: T,      //  針對(duì)當(dāng)前指針地址的描述
}

接下來看看rust關(guān)于這塊的定義疫鹊,從下面結(jié)構(gòu)定義可以看到,裸指針本質(zhì)就是PtrComponents<T>

pub(crate) union PtrRepr<T: ?Sized> {
    pub(crate) const_ptr: *const T,     // 只讀指針
    pub(crate) mut_ptr: *mut T,          // 可變指針
    pub(crate) components: PtrComponents<T>,  /
}

pub(crate) struct PtrComponents<T: ?Sized> {
    //*const ()保證元數(shù)據(jù)部分是空 
    pub(crate) data_address: *const (),
    //不同類型指針的元數(shù)據(jù)
    pub(crate) metadata: <T as Pointee>::Metadata,
}

// Pointee只用來指定Metadata的類型司致。
pub trait Pointee {
    /// The type for metadata in pointers and references to `Self`.
    type Metadata: Copy + Send + Sync + Ord + Hash + Unpin;
}

// thin廋指針元數(shù)據(jù)是單元類型拆吆,即是空
pub trait Thin = Pointee<Metadata = ()>;

元數(shù)據(jù)的規(guī)則:

  • 對(duì)于固定大小類型的指針(實(shí)現(xiàn)了 Sized Trait), 在rust被定義為廋指針(thin pointer),元數(shù)據(jù)大小為0脂矫,類型為(),

需要注意的:rust中數(shù)組也是固定大小的類型枣耀,運(yùn)行中對(duì)數(shù)組下標(biāo)合法性的檢測(cè)比較是否已經(jīng)越過了數(shù)組的內(nèi)存大小。

  • 對(duì)于動(dòng)態(tài)大小類型的指針(DST 類型)庭再,被定義為胖指針(fat pointer 或 wide pointer), 元數(shù)據(jù)為:
    • 對(duì)于結(jié)構(gòu)類型捞奕,如果最后一個(gè)成員是動(dòng)態(tài)類型(struct中的其他成員不允許為動(dòng)態(tài)類型)牺堰,則元數(shù)據(jù)為此動(dòng)態(tài)類型的元數(shù)據(jù);
    • 對(duì)于str類型, 元數(shù)據(jù)是按字節(jié)計(jì)算的長(zhǎng)度值颅围,元數(shù)據(jù)類型是usize伟葫;
    • 對(duì)于切片類型,例如[T]類型院促,元數(shù)據(jù)是數(shù)組元素的數(shù)目值筏养,元數(shù)據(jù)類型是usize;
    • 對(duì)于trait對(duì)象常拓,例如 dyn XXXTrait渐溶, 元數(shù)據(jù)則是DynMetadata<Self>
      (例如:DynMetadata<dyn XXXTrait>);

伴隨著rust的發(fā)展弄抬,后期有可能會(huì)根據(jù)需要引入新的元數(shù)據(jù)種類茎辐。

在標(biāo)準(zhǔn)庫代碼當(dāng)中沒有指針類型如何實(shí)現(xiàn)Pointee Trait的代碼,編譯器針對(duì)每個(gè)類型自動(dòng)的實(shí)現(xiàn)了Pointee眉睹。
看看如下rust編譯器實(shí)現(xiàn)的代碼

    pub fn ptr_metadata_ty(&'tcx self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
        // FIXME: should this normalize?
        let tail = tcx.struct_tail_without_normalization(self);
        match tail.kind() {
            // Sized types
            ty::Infer(ty::IntVar(_) | ty::FloatVar(_))
            | ty::Uint(_)
            | ty::Int(_)
            | ty::Bool
            | ty::Float(_)
            | ty::FnDef(..)
            | ty::FnPtr(_)
            | ty::RawPtr(..)
            | ty::Char
            | ty::Ref(..)
            | ty::Generator(..)
            | ty::GeneratorWitness(..)
            | ty::Array(..)
            | ty::Closure(..)
            | ty::Never
            | ty::Error(_)
            | ty::Foreign(..)
            | ty::Adt(..)
            // 當(dāng)是固定類型,元數(shù)據(jù)是單元類型 tcx.types.unit废膘,即為空
            | ty::Tuple(..) => tcx.types.unit,

            //  當(dāng)為字符串和切片類型竹海,元數(shù)據(jù)為長(zhǎng)度tcx.types.usize,是元素長(zhǎng)度
            ty::Str | ty::Slice(_) => tcx.types.usize,

            // 對(duì)于dyn Trait類型丐黄, 元數(shù)據(jù)從具體的DynMetadata獲取*
            ty::Dynamic(..) => {
                let dyn_metadata = tcx.lang_items().dyn_metadata().unwrap();
                tcx.type_of(dyn_metadata).subst(tcx, &[tail.into()])
            },
            
            // 并不是所有的類型 都需要具有元數(shù)據(jù)的
            // 以下類型不應(yīng)有元數(shù)據(jù)
            ty::Projection(_)
            | ty::Param(_)
            | ty::Opaque(..)
            | ty::Infer(ty::TyVar(_))
            | ty::Bound(..)
            | ty::Placeholder(..)
            | ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
                bug!("`ptr_metadata_ty` applied to unexpected type: {:?}", tail)
            }
        }
    }

以上代碼說明了編譯器對(duì)每一個(gè)類型(或類型指針)都實(shí)現(xiàn)了Pointee中元數(shù)據(jù)類型的獲取斋配。

對(duì)于Trait對(duì)象的元數(shù)據(jù)的具體結(jié)構(gòu)定義見如下代碼:

//dyn Trait裸指針的元數(shù)據(jù)結(jié)構(gòu)
pub struct DynMetadata<Dyn: ?Sized> {
    //堆中的VTable變量的引用
    vtable_ptr: &'static VTable,

    // 標(biāo)識(shí)結(jié)構(gòu)對(duì)Dyn的所有權(quán)關(guān)系,
    //其中PhantomData與具體變量的聯(lián)系在初始化時(shí)由編譯器自行推斷完成, 
    // 這里PhantomData主要對(duì)編譯器做出提示:在做Drop check時(shí)注意本結(jié)構(gòu)體會(huì)負(fù)責(zé)對(duì)Dyn類型變量做drop灌闺。
    phantom: crate::marker::PhantomData<Dyn>,
}

struct VTable {
    //trait對(duì)象的drop方法的指針艰争,這里trait對(duì)象是一個(gè)具體的結(jié)構(gòu)體,它實(shí)現(xiàn)了trait
    drop_in_place: fn(*mut ()),
    //trait對(duì)象類型的內(nèi)存大小
    size_of: usize,
    //trait對(duì)象類型的字節(jié)對(duì)齊大小
    align_of: usize,
    //后續(xù)是trait對(duì)象的所有方法實(shí)現(xiàn)的指針數(shù)組
}

元數(shù)據(jù)類型相同的裸指針可以任意的轉(zhuǎn)換桂对,例如:可以有 * const [usize; 3] as * const[usize; 5] ;
元數(shù)據(jù)類型不同的裸指針之間不能轉(zhuǎn)換甩卓,例如;* const [usize;3] as *const[usize] 而這種語句無法通過編譯器

裸指針的操作函數(shù)——intrinsic模塊內(nèi)存相關(guān)固有函數(shù)

intrinsics模塊中的函數(shù)由編譯器內(nèi)置實(shí)現(xiàn)蕉斜,并提供給其他模塊使用逾柿。intrinsics模塊的內(nèi)存函數(shù)一般不被庫以外的代碼直接調(diào)用,而是由mem模塊和ptr模塊封裝后再提供給其他模塊宅此。

相關(guān)內(nèi)存申請(qǐng)及釋放函數(shù):

  • intrinsics::drop_in_place<T: ?Sized>(to_drop: * mut T)
    在某些情況下机错,可能會(huì)將變量設(shè)置成不允許編譯器自動(dòng)調(diào)用變量的drop函數(shù)父腕, 此時(shí)若是仍需要對(duì)變量調(diào)用drop弱匪,則在代碼中顯式調(diào)用此函數(shù)以出發(fā)對(duì)T類型的drop調(diào)用。
  • intrinsics::forget<T: ?Sized> (_:T)
    代碼中調(diào)用這個(gè)函數(shù)后璧亮,編譯器不對(duì)forget的變量自動(dòng)調(diào)用變量的drop函數(shù)萧诫。
  • intrinsics::needs_drop<T>()->bool
    判斷T類型是否需要做drop操作斥难,如若實(shí)現(xiàn)了Copy trait的類型會(huì)返回false

類型轉(zhuǎn)換:

  • intrinsics::transmute<T,U>(e:T)->U
    對(duì)于內(nèi)存布局相同的類型 T和U, 完成將類型T變量轉(zhuǎn)換為類型U變量,此時(shí)T的所有權(quán)將轉(zhuǎn)換為U的所有權(quán)

指針偏移函數(shù):

  • intrinsics::offset<T>(dst: *const T, offset: usize)->* const T
    類似C的類型指針加計(jì)算
  • intrinsics::ptr_offset_from<T>(ptr: *const T, base: *const T) -> isize
    基于類型T內(nèi)存布局的兩個(gè)裸指針之間的偏移量

內(nèi)存塊內(nèi)容修改函數(shù):

  • intrinsics::copy<T>(src:*const T, dst: *mut T, count:usize)
    內(nèi)存拷貝财搁, src和dst內(nèi)存可重疊蘸炸, 類似c語言中的memmove, 此時(shí)dst原有內(nèi)存如果已經(jīng)初始化,則會(huì)出現(xiàn)內(nèi)存泄漏尖奔。src的所有權(quán)實(shí)際會(huì)被復(fù)制搭儒,從而也造成重復(fù)drop問題。
  • intrinsics::copy_no_overlapping<T>(src:*const T, dst: * mut T, count:usize)
    內(nèi)存拷貝提茁, src和dst內(nèi)存不重疊
  • intrinsics::write_bytes(dst: *mut T, val:u8, count:usize)
    C語言的memset的rust實(shí)現(xiàn), 此時(shí)淹禾,原內(nèi)存如果已經(jīng)初始化,則原內(nèi)存的變量可能造成內(nèi)存泄漏茴扁,且因?yàn)榫幾g器會(huì)繼續(xù)對(duì)dst的內(nèi)存塊做drop調(diào)用铃岔,有可能會(huì)UB。

類型內(nèi)存參數(shù)函數(shù):

  • intrinsics::size_of<T>()->usize
    類型內(nèi)存空間字節(jié)大小
  • intrinsics::min_align_of<T>()->usize
    返回類型對(duì)齊字節(jié)大小
  • intrinsics::size_of_val<T>(_:*const T)->usize
    返回指針指向的變量?jī)?nèi)存空間字節(jié)大小
  • intrinsics::min_align_of_val<T>(_: * const T)->usize
    返回指針指向的變量對(duì)齊字節(jié)大小

禁止優(yōu)化的內(nèi)存函數(shù):

類似volatile_xxxx 的函數(shù)是通知編譯器不做內(nèi)存優(yōu)化的操作函數(shù),一般硬件相關(guān)操作需要禁止優(yōu)化峭火。

  • intrinsics::volatile_copy_nonoverlapping_memory<T>(dst: *mut T, src: *const T, count: usize)
    內(nèi)存拷貝
  • intrinsics::volatile_copy_memory<T>(dst: *mut T, src: *const T, count: usize)
    功能類似C語言memmove
  • intrinsics::volatile_set_memory<T>(dst: *mut T, val: u8, count: usize)
    功能類似C語言memset
  • intrinsics::volatile_load<T>(src: *const T) -> T
    讀取內(nèi)存或寄存器毁习,T類型字節(jié)對(duì)齊到2的冪次
  • intrinsics::volatile_store<T>(dst: *mut T, val: T)
    內(nèi)存或寄存器寫入,字節(jié)對(duì)齊
  • intrinsics::unaligned_volatile_load<T>(src: *const T) -> T
    字節(jié)非對(duì)齊
  • intrinsics::unaligned_volatile_store<T>(dst: *mut T, val: T)
    字節(jié)非對(duì)齊

內(nèi)存比較函數(shù):

  • intrinsics::raw_eq<T>(a: &T, b: &T) -> bool
    內(nèi)存比較卖丸,類似C語言memcmp
  • pub fn ptr_guaranteed_eq<T>(ptr: *const T, other: *const T) -> bool
    判斷兩個(gè)指針是否判斷, 相等返回ture, 不等返回false
  • pub fn ptr_guaranteed_ne<T>(ptr: *const T, other: *const T) -> bool
    判斷兩個(gè)指針是否不等纺且,不等返回true
裸指針方法

在rust中針對(duì)*const T/*mut T的類型實(shí)現(xiàn)了若干方法,是對(duì)語言的原生類型實(shí)現(xiàn)方法稍浆,并擴(kuò)展的實(shí)例:

impl <T:?Sized> * const T { // 只讀指針
    //省略部分代碼
}
impl <T:?Sized> *mut T{ // 可變指針
    // 省略部分代碼
}
impl <T> *const [T] { // 只讀[T]指針
    // 省略部分代碼
}
impl <T> *mut [T] { // 可變[T]指針
    // 省略部分代碼
}

對(duì)于裸指針载碌,rust標(biāo)準(zhǔn)庫包含了最基礎(chǔ)的 * const T/* mut T, 以及在* const T/*mut T 基礎(chǔ)上特化的切片類型[T]的裸指針* const [T]/*mut [T]衅枫。
在標(biāo)準(zhǔn)庫針對(duì)兩種基礎(chǔ)類型指針實(shí)現(xiàn)了一些關(guān)聯(lián)函數(shù)及方法嫁艇。這里一定注意,所有針對(duì) * const T的方法在* const [T]上都是適用的弦撩。

以上有幾點(diǎn)值得注意:

  1. 可以針對(duì)原生類型實(shí)現(xiàn)方法(實(shí)現(xiàn)trait)步咪,這也是rust類型系統(tǒng)的強(qiáng)大擴(kuò)展性,也是對(duì)函數(shù)式編程的強(qiáng)大支持益楼;
  2. 針對(duì)泛型約束實(shí)現(xiàn)方法歧斟,我們可以大致認(rèn)為*const T/* mut T實(shí)質(zhì)是一種泛型約束,*const [T]/*mut [T]是更進(jìn)一步的約束偏形,這使得rust可以具備更好的數(shù)據(jù)抽象能力静袖,簡(jiǎn)化代碼,復(fù)用模塊俊扭。
裸指針的創(chuàng)建

1队橙、從已經(jīng)初始化的變量創(chuàng)建裸指針:

    &T as *const T;
    &mut T as * mut T;

2、用usize的數(shù)值創(chuàng)建裸指針:并使用了unsafe

    {
        let  a: usize = 0xf000000000000000;
        unsafe {a as * const i32};
    }

在操作系統(tǒng)內(nèi)核時(shí)需要直接將一個(gè)地址數(shù)值轉(zhuǎn)換為某一類型的裸指針, 故而rust也提供了一些其他的裸指針創(chuàng)建關(guān)聯(lián)函數(shù):

  • ptr::null<T>() -> *const T
    創(chuàng)建一個(gè)0值的*const T捐康,等同于是 0 as *const T仇矾,用null()函數(shù)明顯更符合程序員的習(xí)慣 ;
  • ptr::null_mut<T>()->*mut T
    功能同上解总,創(chuàng)建可變裸指針贮匕;
  • ptr::from_raw_parts<T: ?Sized>(data_address: *const (), metadata: <T as Pointee>::Metadata) -> *const T
    從內(nèi)存地址和元數(shù)據(jù)創(chuàng)建裸指針;
  • ptr::from_raw_parts_mut<T: ?Sized>(data_address: *mut (), metadata: <T as Pointee>::Metadata) -> *mut T
    功能同上花枫,創(chuàng)建可變裸指針刻盐;

在進(jìn)行rust裸指針類型轉(zhuǎn)換時(shí),經(jīng)常使用以上兩個(gè)函數(shù)獲得需要的指針類型劳翰。

切片類型的裸指針創(chuàng)建函數(shù)如下:

  • ptr::slice_from_raw_parts<T>(data: *const T, len: usize) -> *const [T]
    ptr::slice_from_raw_parts_mut<T>(data: *mut T, len: usize) -> *mut [T]
    由裸指針類型及切片長(zhǎng)度獲得切片類型裸指針敦锌,調(diào)用代碼應(yīng)保證data是切片的裸指針地址

由類型裸指針轉(zhuǎn)換為切片類型裸指針最突出的應(yīng)用之一是內(nèi)存申請(qǐng)佳簸,申請(qǐng)的內(nèi)存返回 * const u8的指針乙墙,這個(gè)裸指針是沒有包含內(nèi)存大小的,只有頭地址生均,因此需要將這個(gè)指針轉(zhuǎn)換為 * const [u8]听想,將申請(qǐng)的內(nèi)存大小包含入裸指針結(jié)構(gòu)體中。

slice_from_raw_parts代碼如下:

pub const fn slice_from_raw_parts<T>(data: *const T, len: usize) -> *const [T] {
    // data.cast()將*const T轉(zhuǎn)換為 *const()
    from_raw_parts(data.cast(), len)
}

pub const fn from_raw_parts<T: ?Sized>(
    data_address: *const (),
    metadata: <T as Pointee>::Metadata,
) -> *const T {
    //由以下代碼可以確認(rèn) * const T實(shí)質(zhì)就是PtrRepr類型結(jié)構(gòu)體马胧。
    unsafe { 
        PtrRepr { 
            components: PtrComponents { data_address, metadata } 
        }.const_ptr 
    }
}

裸指針函數(shù)(不屬于方法)

  • ptr::drop_in_place<T: ?Sized>(to_drop: *mut T)
    此函數(shù)是編譯器實(shí)現(xiàn)的汉买,用于由程序代碼人工釋放所有權(quán),而不是交由rust編譯器處理漓雅。此函數(shù)會(huì)引發(fā)T內(nèi)部成員的系列drop調(diào)用录别。
  • ptr::metadata<T: ?Sized>(ptr: *const T) -> <T as Pointee>::Metadata
    用來返回裸指針的元數(shù)據(jù)
  • ptr::eq<T>(a: *const T, b: *const T)->bool
    比較指針朽色,此處需要注意邻吞,地址比較不但是地址,也比較元數(shù)據(jù)

ptr模塊的函數(shù)大部分邏輯都比較簡(jiǎn)單葫男。很多就是對(duì)intrinsic 函數(shù)做調(diào)用抱冷。

裸指針類型轉(zhuǎn)換方法

裸指針類型之間的轉(zhuǎn)換:

  • *const T::cast<U>(self) -> *const U
    本質(zhì)上就是一個(gè)*const T as *const U。利用rust的類型推斷梢褐,此函數(shù)可以簡(jiǎn)化代碼并支持鏈?zhǔn)秸{(diào)用旺遮。
  • *mut T::cast<U>(self)->*mut U
    功能同上

調(diào)用以上的函數(shù)要注意,若是后續(xù)要把返回的指針轉(zhuǎn)換成引用盈咳,必須保證T類型與U類型內(nèi)存布局完全一致耿眉。
如果僅僅是將返回值做數(shù)值應(yīng)用,則此約束可以不遵守鱼响,cast函數(shù)轉(zhuǎn)換后的類型通常由編譯器自行推斷鸣剪,有時(shí)需要仔細(xì)分析。

裸指針與引用之間的類型轉(zhuǎn)換:

  • *const T::as_ref<`a>(self) -> Option<&`a T>
    將裸指針轉(zhuǎn)換為引用,由于*const T可能為零筐骇,所有需要轉(zhuǎn)換為Option<& `a T>類型债鸡,轉(zhuǎn)換的安全性由程序員保證,尤其注意滿足rust對(duì)引用的安全要求铛纬。這里要注意厌均,轉(zhuǎn)換后的生命周期實(shí)際上與原變量的生命周期相獨(dú)立。因此告唆,生命周期的正確性將由調(diào)用代碼保證棺弊。
  • *mut T::as_ref<`a>(self)->Option<&`a T>
    同上
  • *mut T::as_mut<`a>(self)->Option<&`a mut T>
    同上,但轉(zhuǎn)化類型為 &mut T悔详。

切片類型裸指針類型轉(zhuǎn)換:

  • ptr::*const [T]::as_ptr(self) -> *const T
    將切片類型的裸指針轉(zhuǎn)換為切片成員類型的裸指針镊屎, 這個(gè)轉(zhuǎn)換會(huì)導(dǎo)致指針的元數(shù)據(jù)丟失
  • ptr::*mut [T]::as_mut_ptr(self) -> *mut T
    同上
裸指針結(jié)構(gòu)體屬性相關(guān)方法:
  • ptr::*const T::to_raw_parts(self) -> (*const (), <T as super::Pointee>::Metadata)
    ptr::*mut T::to_raw_parts(self)->(* const (), <T as super::Pointee>::Metadata)
    由裸指針獲得地址及元數(shù)據(jù)
  • ptr::*const T::is_null(self)->bool
    ptr::*mut T::is_null(self)->bool此
    函數(shù)判斷裸指針的地址值是否為0

切片類型裸指針:

  • ptr::*const [T]:: len(self) -> usize
    獲取切片長(zhǎng)度,直接從裸指針的元數(shù)據(jù)獲取長(zhǎng)度
  • ptr:: *mut [T]:: len(self) -> usize
    同上

裸指針偏移計(jì)算相關(guān)方法

  • ptr::*const T::offset(self, count:isize)->* const T
    得到偏移后的裸指針
  • ptr::*const T::wrapping_offset(self, count: isize) -> *const T
    考慮溢出繞回的offset
  • ptr::*const T::offset_from(self, origin: *const T) -> isize
    計(jì)算兩個(gè)裸指針的offset值
  • ptr::*mut T::offset(self, count:isize)->* mut T
    偏移后的裸指針
  • ptr::*const T::wrapping_offset(self, count: isize) -> *const T
    考慮溢出繞回的offset
  • ptr::*const T::offset_from(self, origin: *const T) -> isize
    計(jì)算兩個(gè)裸指針的offset值
    以上兩個(gè)方法基本上通過intrinsic的函數(shù)實(shí)現(xiàn)

ptr::*const T::add(self, count: usize) -> Self
ptr::*const T::wraping_add(self, count: usize)->Self
ptr::*const T::sub(self, count:usize) -> Self
ptr::*const T::wrapping_sub(self, count:usize) -> Self
ptr::*mut T::add(self, count: usize) -> Self
ptr::*mut T::wraping_add(self, count: usize)->Self
ptr::*mut T::sub(self, count:usize) -> Self
ptr::*mut T::wrapping_sub(self, count:usize) -> Self
以上是對(duì)offset函數(shù)的包裝茄螃,使之更符合語義習(xí)慣缝驳,并便于理解

裸指針直接賦值方法
    //該方法用于僅給指針結(jié)構(gòu)體的 address部分賦值  
    pub fn set_ptr_value(mut self, val: *const u8) -> Self {
        // 以下代碼因?yàn)橹恍薷腜trComponent.address,所以不能直接用相等
        // 代碼采取的方案是取self的可變引用归苍,將此引用轉(zhuǎn)換為裸指針的裸指針用狱,
        let thin = &mut self as *mut *const T as *mut *const u8;

        // 這個(gè)賦值僅僅做了address的賦值,對(duì)于瘦指針拼弃,這個(gè)相當(dāng)于賦值操作夏伊,
        // 對(duì)于胖指針,則沒有改變胖指針的元數(shù)據(jù)吻氧。這種操作方式僅僅在極少數(shù)的情況下
        // 可以使用溺忧,極度危險(xiǎn)。
        unsafe { *thin = val };
        self
    }
rust引用&T的安全要求
  1. 引用的內(nèi)存地址必須滿足類型T的內(nèi)存對(duì)齊要求
  2. 引用的內(nèi)存內(nèi)容必須是初始化過的
    舉例:
   #[repr(packed)]
   struct RefTest {a:u8, b:u16, c:u32}
   fn main() {
       let test = RefTest{a:1, b:2, c:3};
       //下面代碼編譯會(huì)有告警盯孙,因?yàn)閠est.b 內(nèi)存字節(jié)位于奇數(shù)鲁森,無法用于借用
       let ref1 = &test.b
   }

編譯器出現(xiàn)如下警告

|
9 | let ref1 = &test.b;
 |            ^^^^^^^
 |
 = note: `#[warn(unaligned_references)]` on by default
 = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
 = note: for more information, see issue #82523 <https://github.com/rust-lang/rust/issues/82523>
 = note: fields of packed structs are not properly aligned, and creating a misaligned reference is undefined behavior (even if that reference is never dereferenced)
 = help: copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers)

針對(duì)未初始化變量相關(guān)的指針操作

MaybeUninit<T>標(biāo)準(zhǔn)庫代碼分析

通常rust中對(duì)于變量的要求是必須初始化后才能使用,否則就會(huì)編譯告警振惰。但在程序中歌溉,總有內(nèi)存還未初始化,卻需要使用的情況:

  1. 從堆申請(qǐng)的內(nèi)存塊骑晶,這些內(nèi)存塊都是沒有初始化的
  2. 需要定義一個(gè)新的泛型變量時(shí)痛垛,并且不合適用轉(zhuǎn)移所有權(quán)進(jìn)行賦值時(shí)
  3. 需要定義一個(gè)新的變量铸抑,但希望不初始化便能使用其引用時(shí)
  4. 定義一個(gè)數(shù)組蹬碧,但必須在后繼代碼對(duì)數(shù)組成員初始化時(shí)
    ......

為了處理這種需要在代碼中使用未初始化內(nèi)存的情況,rust標(biāo)準(zhǔn)庫定義了MaybeUninit<T>

MaybeUninit<T>結(jié)構(gòu)定義
    #[repr(transparent)] 
    pub union MaybeUninit<T> {
        uninit: (),
        value: ManuallyDrop<T>,
    }

說明:
屬性repr(transparent)實(shí)際上表示外部的封裝結(jié)構(gòu)在內(nèi)存中等價(jià)于內(nèi)部的變量,
MaybeUninit<T>的內(nèi)存布局就是ManuallyDrop<T>的內(nèi)存布局.

在后面的內(nèi)容中可以看到ManuallyDrop<T>實(shí)際就是T的內(nèi)存布局桃移。故而MaybeUninit<T>在內(nèi)存中實(shí)質(zhì)也就是T類型仔雷。

MaybeUninit<T>容器來實(shí)現(xiàn)對(duì)未初始化變量的封裝蹂析,以便在不引發(fā)編譯錯(cuò)誤完成對(duì)T類型未初始化變量的相關(guān)操作.

如果T類型的變量未初始化抖剿,那需要顯式的提醒編譯器不做T類型的drop操作,因?yàn)閐rop操作可能會(huì)對(duì)T類型內(nèi)部的變量做連鎖drop處理识窿,從而引用未初始化的內(nèi)容斩郎,造成未定義行為(undefined behavior)

而rust是用ManuallyDrop<T>封裝結(jié)構(gòu)完成了對(duì)編譯器的顯式提示:對(duì)于用ManuallyDrop<T>封裝的變量喻频,生命周期終止的時(shí)候編譯器不會(huì)調(diào)用drop操作缩宜。

ManuallyDrop<T> 結(jié)構(gòu)及方法

源代碼如下:

#[repr(transparent)]
pub struct ManuallyDrop<T: ?Sized> {
    value: T,
}

重點(diǎn)關(guān)注的一些方法:

  • ManuallyDrop<T>::new(val:T) -> ManuallyDrop<T>
    此函數(shù)返回ManuallyDrop變量擁有傳入的T類型變量所有權(quán),并將此塊內(nèi)存直接用ManuallyDrop封裝, 對(duì)于val甥温,編譯器不再主動(dòng)做drop操作锻煌。
    pub const fn new(value: T) -> ManuallyDrop<T> {
        //所有權(quán)轉(zhuǎn)移到結(jié)構(gòu)體內(nèi)部,value生命周期結(jié)束時(shí)不會(huì)引發(fā)drop
        ManuallyDrop { value }
    }
  • ManuallyDrop<T>::into_inner(slot: ManuallyDrop<T>)->T
    將封裝的T類型變量所有權(quán)轉(zhuǎn)移出來姻蚓,轉(zhuǎn)移出來的變量生命周期終止時(shí)宋梧,編譯器會(huì)自動(dòng)調(diào)用類型的drop。
    pub const fn into_inner(slot: ManuallyDrop<T>) -> T {
        //將value解封裝狰挡,所有權(quán)轉(zhuǎn)移到返回值中捂龄,編譯器重新對(duì)所有權(quán)做處理
        slot.value
    }
  • ManuallyDrop<T>::drop(slot: &mut ManuallyDrop<T>)
    drop掉內(nèi)部變量,封裝入ManuallyDrop<T>的變量一定是在程序運(yùn)行的某一時(shí)期不需要編譯器drop加叁,所以調(diào)用這個(gè)函數(shù)的時(shí)候一定要注意正確性倦沧。
  • ManuallyDrop<T>::deref(&self)-> & T
    返回內(nèi)部包裝的變量的引用
    fn deref(&self) -> &T {
        //返回后,代碼可以用&T對(duì)self.value做讀操作,但不改變drop的規(guī)則
        &self.value
    }
  • ManuallyDrop<T>::deref_mut(&mut self)-> & mut T
    返回內(nèi)部包裝的變量的可變引用它匕,調(diào)用代碼可以利用可變引用對(duì)內(nèi)部變量賦值展融,但不改變drop機(jī)制
    ManuallyDrop樣例:
    use std::mem::ManuallyDrop;
    let mut x = ManuallyDrop::new(String::from("Hello World!"));
    x.truncate(5); // 此時(shí)會(huì)調(diào)用deref
    assert_eq!(*x, "Hello");
    // 但對(duì)x的drop不會(huì)再發(fā)生
MaybeUninit<T> 創(chuàng)建方法
  • MaybeUninit<T>::uninit()->MaybeUninit<T>
    可視為在棧空間上申請(qǐng)內(nèi)存的方法豫柬,申請(qǐng)的內(nèi)存大小是T類型的內(nèi)存大小告希,該內(nèi)存沒有初始化。利用泛型和Union內(nèi)存布局烧给,rust巧妙的利用此函數(shù)在棧上申請(qǐng)一塊未初始化內(nèi)存燕偶。此函數(shù)非常非常非常值得關(guān)注,在需要在棿匆梗空間定義一個(gè)未初始化泛型時(shí)杭跪,應(yīng)第一時(shí)間想到MaybeUninit::<T>::uninit()仙逻。
    pub const fn uninit() -> MaybeUninit<T> {
        //變量?jī)?nèi)存布局與T類型完全一致
        MaybeUninit { uninit: () }
    }
  • MaybeUninit<T>::new(val:T)->MaybeUninit<T>
    內(nèi)部用ManuallyDrop封裝了val, 然后用MaybeUninit封裝ManuallyDrop驰吓。如果T沒有初始化過,調(diào)用這個(gè)函數(shù)會(huì)編譯失敗系奉,此時(shí)內(nèi)存實(shí)際上已經(jīng)初始化過了檬贰。調(diào)用此函數(shù)要額外注意val的drop必須在后續(xù)有交代。
    pub const fn new(val: T) -> MaybeUninit<T> {
        //val這個(gè)時(shí)候是初始化過的缺亮。
        MaybeUninit { value: ManuallyDrop::new(val) }
    }
  • MaybeUninit<T>::zeroed()->MaybeUninit<T>
    申請(qǐng)了T類型內(nèi)存并清零翁涤。
    pub fn zeroed() -> MaybeUninit<T> {
        let mut u = MaybeUninit::<T>::uninit();
        unsafe {
            //因?yàn)闆]有初始化,所以不存在所有權(quán)問題,
            //必須使用ptr::write_bytes葵礼,否則無法給內(nèi)存清0
            //ptr::write_bytes直接調(diào)用了intrinsics::write_bytes
            u.as_mut_ptr().write_bytes(0u8, 1);
        }
        u
    }
對(duì)未初始化的變量賦值的方法
  • 將值寫入MaybeUninit<T>: MaybeUninit<T>::write(val)->&mut T
    這個(gè)函數(shù)是在未初始化時(shí)使用号阿,如果已經(jīng)調(diào)用過write,且不希望解封裝鸳粉,那后續(xù)的賦值使用返回的&mut T扔涧。代碼如下:
    pub const fn write(&mut self, val: T) -> &mut T {
        //下面這個(gè)賦值,會(huì)導(dǎo)致原*self的MaybeUninit<T>的變量生命周期截止届谈,會(huì)調(diào)用drop枯夜。但不會(huì)對(duì)內(nèi)部的T類型變量做drop調(diào)用。所以如果*self內(nèi)部的T類型變量已經(jīng)被初始化且需要做drop艰山,那會(huì)造成內(nèi)存泄漏湖雹。所以下面這個(gè)等式實(shí)際上隱含了self內(nèi)部的T類型變量必須是未初始化的或者T類型變量不需要drop。
        *self = MaybeUninit::new(val);
        // 函數(shù)調(diào)用后的賦值用返回的&mut T來做曙搬。
        unsafe { self.assume_init_mut() }
    }
初始化后解封裝的方法

用assume_init返回初始化后的變量并消費(fèi)掉MaybeUninit<T>變量摔吏,這是最標(biāo)準(zhǔn)的做法:
MaybeUninit<T>::assume_init()->T,代碼如下:

    pub const unsafe fn assume_init(self) -> T {
        // 調(diào)用者必須保證self已經(jīng)初始化了
        unsafe {
            intrinsics::assert_inhabited::<T>();
            //把T的所有權(quán)返回,編譯器會(huì)主動(dòng)對(duì)T調(diào)用drop
            ManuallyDrop::into_inner(self.value)
        }
    }

assume_init_read是不消費(fèi)self的情況下獲得內(nèi)部T變量纵装,內(nèi)部T變量的所有權(quán)已經(jīng)轉(zhuǎn)移到返回變量舔腾,后繼要注意不能再次調(diào)用其他解封裝函數(shù)。否則解封裝后搂擦,會(huì)出現(xiàn)雙份所有權(quán)稳诚,引發(fā)兩次對(duì)同一變量的drop,導(dǎo)致UB瀑踢。

    pub const unsafe fn assume_init_read(&self) -> T {
        
        unsafe {
            intrinsics::assert_inhabited::<T>();
            //會(huì)調(diào)用ptr::read
            self.as_ptr().read()
        }
    }
    //此函即ptr::read, 會(huì)復(fù)制一個(gè)變量扳还,此時(shí)注意,實(shí)際上src指向的變量的所有權(quán)已經(jīng)轉(zhuǎn)移給了返回變量橱夭,
    //所以調(diào)用此函數(shù)的前提是src后繼一定不能調(diào)用T類型的drop函數(shù)氨距,例如src本身處于ManallyDrop,或后繼對(duì)src調(diào)用forget棘劣,或給src綁定新變量俏让。
    //在rust中,不支持 let xxx = *(&T) 這種轉(zhuǎn)移所有權(quán)的方式茬暇,因此對(duì)于只有指針輸入首昔,又要轉(zhuǎn)移所有權(quán)的,智能利用淺拷貝進(jìn)行粗暴轉(zhuǎn)移糙俗。
    pub const unsafe fn read<T>(src: *const T) -> T {` 
        //利用MaybeUninit::uninit申請(qǐng)未初始化的T類型內(nèi)存
        let mut tmp = MaybeUninit::<T>::uninit();
        unsafe {
            //完成內(nèi)存拷貝
            copy_nonoverlapping(src, tmp.as_mut_ptr(), 1);
            //初始化后的內(nèi)存解封裝并返回
            tmp.assume_init()
        }
    }

與上個(gè)函數(shù)比較類似的ManuallyDrop<T>::take方法勒奇,用take函數(shù)將變量復(fù)制并獲得變量的所有權(quán)。此時(shí)原變量仍然保留在ManuallyDrop中巧骚,后繼不能再調(diào)用其他解封裝函數(shù)赊颠,否則可能會(huì)出現(xiàn)UB格二。這里要特別注意理解take已經(jīng)把變量的所有權(quán)轉(zhuǎn)移到返回變量中。

    pub unsafe fn take(slot: &mut ManuallyDrop<T>) -> T {
        // 拷貝內(nèi)部變量竣蹦,并返回內(nèi)部變量的所有權(quán)
        // 返回后顶猜,原有的變量所有權(quán)已經(jīng)消失,不能再用into_inner來返回
        // 否則會(huì)UB
        unsafe { ptr::read(&slot.value) }
    }

  • MaybeUninit<T>::assume_init_drop(&self)
    對(duì)于已經(jīng)初始化過的MaybeUninit<T>痘括, 如果所有權(quán)一直沒有轉(zhuǎn)移驶兜,則必須調(diào)用此函數(shù)以觸發(fā)T類型的drop函數(shù)完成所有權(quán)的釋放。
  • MaybeUninit<T>::assume_init_ref(&self)->&T
    返回內(nèi)部T類型變量的借用远寸,調(diào)用者應(yīng)保證內(nèi)部T類型變量已經(jīng)初始化抄淑,返回值按照一個(gè)普通的引用使用。應(yīng)注意返回值的生命周期應(yīng)該小于self的生命周期
  • MaybeUninit<T>::assume_init_mut(&mut self)->&mut T
    返回內(nèi)部T類型變量的可變借用驰后,調(diào)用者應(yīng)保證內(nèi)部T類型變量已經(jīng)初始化肆资,返回值按照一個(gè)普通的可變引用使用。應(yīng)注意返回值的生命周期應(yīng)該小于self的生命周期
MaybeUninit<[T]>的方法

創(chuàng)建一個(gè)MaybeUninit的未初始化數(shù)組:

  • MaybeUninit<T>::uninit_array<const LEN:usize>()->[Self; LEN]
    此處對(duì)LEN的使用方式需要注意灶芝,這是不常見的一個(gè)泛型寫法,這個(gè)函數(shù)同樣的申請(qǐng)了一塊內(nèi)存郑原。代碼:
    pub const fn uninit_array<const LEN: usize>() -> [Self; LEN] {
        unsafe { MaybeUninit::<[MaybeUninit<T>; LEN]>::uninit().assume_init() }
    }

這里要注意區(qū)別數(shù)組類型和數(shù)組元素的初始化。對(duì)于數(shù)組[MaybeUninit<T>;LEN]這一類型本身來說夜涕,初始化就是確定整體的內(nèi)存大小犯犁,所以數(shù)組類型的初始化在聲明后就已經(jīng)完成了。這時(shí)assume_init()是正確的女器。這是一個(gè)理解上的盲點(diǎn)酸役。

  • MaybeUninit<T>::array_assume_init<const N:usize>(array: [Self; N]) -> [T; N]
    這個(gè)函數(shù)沒有把所有權(quán)轉(zhuǎn)移出來,代碼分析如下:
    pub unsafe fn array_assume_init<const N: usize>(array: [Self; N]) -> [T; N] {
        unsafe {
            //最后調(diào)用是*const T::read()驾胆,此處 as *const _的寫法可以簡(jiǎn)化代碼,read后涣澡,所有權(quán)已經(jīng)轉(zhuǎn)移到返回值
            //返回后,此數(shù)組內(nèi)所有的MaybeUninit變量成員不能再解封裝
            (&array as *const _ as *const [T; N]).read()
        }
    }

MaybeUnint<T>典型案列

對(duì)T類型變量申請(qǐng)內(nèi)存及賦值:

    use std::mem::MaybeUninit;

    // 獲得一個(gè)未初始化的i32引用類型內(nèi)存
    let mut x = MaybeUninit::<&i32>::uninit();
    // 將&0寫入變量丧诺,完成初始化
    x.write(&0);
    // 將初始化后的變量解封裝供后繼的代碼使用入桂。
    let x = unsafe { x.assume_init() };

以上代碼,編譯器不會(huì)對(duì)x.write進(jìn)行報(bào)警驳阎,這是MaybeUninit<T>的最重要的應(yīng)用抗愁,這個(gè)例子展示了rust如何給未初始化內(nèi)存賦值的處理方式。調(diào)用assume_init前呵晚,必須保證變量已經(jīng)被正確初始化蜘腌。

更復(fù)雜的初始化例子:

    use std::mem::{self, MaybeUninit};
    
    let data = {
      // data在聲明后實(shí)際上就已經(jīng)初始化完畢。
      let mut data: [MaybeUninit<Vec<u32>>; 1000] = unsafe {
        //這里注意實(shí)際調(diào)用是MaybeUninit::<[MaybeUninit<Vec<u32>>;1000]>::uninit(), rust的類型推斷機(jī)制完成了泛型實(shí)例化
          MaybeUninit::uninit().assume_init()
      };
    
      for elem in &mut data[..] {
        elem.write(vec![42]);
      }
    
      // 直接用transmute完成整個(gè)數(shù)組類型的轉(zhuǎn)換
      // 仔細(xì)思考一下劣纲,這里除了用transmute逢捺,似乎沒有其他辦法了谁鳍,
      unsafe { mem::transmute::<_, [Vec<u32>; 1000]>(data) }
    };
    
    assert_eq!(&data[0], &[42]);

下面例子說明一塊內(nèi)存被 MaybeUnint<T>封裝后癞季,編譯器將不再對(duì)其做釋放劫瞳,必須在代碼中顯式釋放:

    use std::mem::MaybeUninit;
    use std::ptr;
   
    let mut data: [MaybeUninit<String>; 1000] = unsafe { MaybeUninit::uninit().assume_init() };
    // 初始化了500個(gè)String變量
    let mut data_len: usize = 0;
    for elem in &mut data[0..500] {
        //write沒有將所有權(quán)轉(zhuǎn)移出ManuallyDrop
        elem.write(String::from("hello"));
        data_len += 1;
    }
    //編譯器無法自動(dòng)調(diào)用drop釋放String變量, 必須顯式用drop_in_place釋放
    for elem in &mut data[0..data_len] {
        //實(shí)際上也可以調(diào)用assume_init_drop來完成此工作
        unsafe { ptr::drop_in_place(elem.as_mut_ptr()); }
    }

上例中,在沒有assume_init()調(diào)用的情況下绷柒,必須手工調(diào)用drop_in_place釋放內(nèi)存志于。
MaybeUninit<T>是一個(gè)非常重要的類型結(jié)構(gòu),未初始化內(nèi)存是編程中不可避免要遇到的情況废睦,MaybeUninit<T>也就是rust編程中必須熟練使用的一個(gè)類型伺绽。

(待續(xù))

引用

rust標(biāo)準(zhǔn)庫關(guān)于內(nèi)存模塊
rust內(nèi)存布局

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嗜湃,隨后出現(xiàn)的幾起案子奈应,更是在濱河造成了極大的恐慌,老刑警劉巖购披,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杖挣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡刚陡,警方通過查閱死者的電腦和手機(jī)惩妇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筐乳,“玉大人氓皱,你說我怎么就攤上這事朵你〖缮担” “怎么了镰矿?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)茫船,這世上最難降的妖魔是什么然眼? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任鲸匿,我火速辦了婚禮,結(jié)果婚禮上渡贾,老公的妹妹穿的比我還像新娘刻诊。我一直安慰自己则涯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布冲簿。 她就那樣靜靜地躺著粟判,像睡著了一般。 火紅的嫁衣襯著肌膚如雪峦剔。 梳的紋絲不亂的頭發(fā)上档礁,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音吝沫,去河邊找鬼呻澜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛惨险,可吹牛的內(nèi)容都是我干的羹幸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼辫愉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼栅受!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起恭朗,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤屏镊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后痰腮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體而芥,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年膀值,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蔚出。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡虫腋,死狀恐怖骄酗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悦冀,我是刑警寧澤趋翻,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站盒蟆,受9級(jí)特大地震影響踏烙,放射性物質(zhì)發(fā)生泄漏师骗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一讨惩、第九天 我趴在偏房一處隱蔽的房頂上張望辟癌。 院中可真熱鬧,春花似錦荐捻、人聲如沸黍少。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厂置。三九已至,卻和暖如春魂角,著一層夾襖步出監(jiān)牢的瞬間昵济,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工野揪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留访忿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓斯稳,卻偏偏與公主長(zhǎng)得像海铆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子平挑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容