協變

在Rust中是一個今人迷惑的話題。對於新手可能感知不到這些概念。本篇內容將給出一個更通俗的理解。

首先, 有兩個重要的點我們要關注一下。

變性是一個與通用引數

T

有關的概念

Rust的生命週期引數(‘a)具有子型別化的設計

?關於子型別是什麼,如果你學習過其它高階語言,就很容易理解。

?關於生命週期,你可以理解為一個變數在棧上什麼時候有效,沒被釋放。更通俗的來說。 就是

一個作用域

程式碼塊

fn

main

()

{

// 這{}括號包起來的,就是一個作用域,叫行生命週期範圍也行。

// 在括號內的區域性棧變數只在這個括號內有效,

}

// ———————— 結束

Rust中的型變就是允許對一個生命週期引數進行如何的變化:

壽命變短:協變 (子型別向基類進化。縮減作用域。)

壽命變長:逆變 (基類向子型別進化,聽進來很荒謬,擴大生命週期,這時危險的)

沒有關係:不變 (不允許協變與逆變,只允許替換成為相同的型別)

注意:變性這些概念實際上是我們需要做的理論假設, 這樣我們才能確保我們的實現是正確的。不然, 程式會炸,出現各種無效指標。

在Rust中無論你在哪裡看到變性,預設情況下,它表示協變。

現在這裡有一個經典的例子。

先試著猜測輸出結果:

struct

MyCell

<

T

>

{

value

T

}

impl

<

T

>

MyCell

<

T

>

{

fn

new

value

T

->

Self

{

MyCell

{

value

}

}

fn

get

&

self

->

&

T

{

&

self

value

}

fn

set

&

self

new_value

T

{

// 注意這裡,方法獲取的是一個不可變的例項(&self)不是&mut self。如果你改成了。那就不會

// 有問題了。 編譯時會提示new_value生命週期不足

// 我們需要使用不安全的記憶體寫入方法,製造這個’BUG‘

unsafe

{

std

::

ptr

::

write

&

self

value

as

*

const

_

as

*

mut

T

new_value

);

}

}

}

fn

set_value

source

&

MyCell

<&

i32

>

{

println

“舊的值是:{}”

source

get

());

let

bugvar

=

100

source

set

&

bugvar

);

}

fn

main

()

{

let

a

=

MyCell

::

new

&

10

);

set_value

&

a

);

println

“現在的值是:{}”

a

get

());

}

// 最終輸出:

// 舊的值是:10

// 現在的值是:-65374792

Rust忽然變的記憶體不安全了。 造成這種原因的。就是今天所說的

變性。

我們展開一下類似編譯的MIR風格程式碼解析一下問題所在。

’a

{

let

a

=

MyCell

::

new

&

‘a

10

);

’b

{

let

bugvar

i32

=

100

// <—— 這個BUG變數建立在這裡

‘c

{

’d

{

source

set

&

‘d

bugvar

);

}

}

// <—— bufvar 釋放在這裡

}

println

“end value: {}”

cell

value

);

}

問題的根源在於,我們允許

改變

了MyCell裡包裹的val。

(重點:*這種關係,MyCell對於val來說。是

協變

我們把生命週期只有

'b

範圍的變數bugvar,強行塞進了生命週期比它更長的

'a

範圍裡。那麼一但較短的那個離開了作用域。那麼更長的變數卻儲存著他。 結果就是:

指向了一個無效的地址。所以,這就相當於, 我借了100萬給你, 1個月後。 你竟然說是我欠了你100萬。你說會不會炸? (你不經我同意單方面修改了內部的值)

這段程式碼的生命週期關係:a > b > c > d

劃重點,如果要讓MyCell包裹的值有效,那麼必須裹入一個至少同樣>=a的作用域

那如何告訴編譯器,對MyCell做一些限制呢。

先參考Cell原始碼,核心在於多了一條

#[lang = “unsafe_cell”]

編譯器會認為這是一個不安全的塊,限制你傳入的引數生命週期必須和Cell一樣或更長,否則 編譯報錯。不過

#[lang = “unsafe_cell”]

這條屬性並不對外開放,語言內部使用, 你只有透過其它方式標記我們的MyCell了

pub

struct

Cell

<

T

Sized

>

{

value

UnsafeCell

<

T

>

}

#[lang =

“unsafe_cell”

// ——> 只是比我們寫的MyCell多了一條這個屬性

pub

struct

UnsafeCell

<

T

Sized

>

{

value

T

}

在標準庫中

,有一個叫幽靈資料

PhantomData

,它用於模擬另一個型別,Rust在編譯時會檢查這個標誌,讓MyCell模擬一個特性

struct

MyCell

<

T

>

{

value

T

_pd

std

::

marker

::

PhantomData

<

std

::

cell

::

UnsafeCell

<

T

>>

}

這裡MyCell模擬了UnsafeCell。變相加上了

#[lang = “unsafe_cell”]

這條屬性讓MyCell的變性保持

不變

。到此。你編譯時就得到一個生命週期不足的錯誤,自己動手試試吧。

變性 整體上來說。 就是一個容器與被包裹值的一種關係

變性只是一個概念,不要過度理解造成負擔,你可以想成其實啥也不是,就是一種我們假設的理論性規範, 遵守它, 程式才能正確的執行。不遵守。程式就是一個字: 炸

變性還有

逆變

不變

,之後有機會將繼續