Rust內(nèi)部可變性之RefCell

背景

在Rust中,每個(gè)對(duì)象(變量)的可見性與可變性均受到所有權(quán)的限制确垫,一個(gè)對(duì)象只能有一個(gè)所有者冗茸。這個(gè)限制對(duì)于內(nèi)存管理來說贞盯,無疑是一個(gè)非常友善的設(shè)計(jì),因?yàn)橹恍枰S護(hù)好所有者的生命周期草戈,就可以對(duì)使用該對(duì)象的安全性進(jìn)行清晰的管理。比如如果對(duì)一個(gè)對(duì)象進(jìn)行賦值,則伴隨者所有權(quán)的轉(zhuǎn)讓枯怖,原有對(duì)象失去控制權(quán),可以放心的進(jìn)行清理能曾。

可見性與可變性主要體現(xiàn)在對(duì)引用的處理上增加了理解與使用的負(fù)擔(dān)度硝,在C語(yǔ)言中,指針的靈活性賦予了開發(fā)者施展奇技淫巧的空間寿冕,但同時(shí)把麻煩推給了開發(fā)者蕊程。Rust用所有權(quán),生命周期等概念限制了對(duì)指針的過度使用驼唱,但對(duì)于初學(xué)者而言藻茂,難免感覺到這些限制矯枉過正,嚴(yán)重制約了想象力的施展。

針對(duì)引用辨赐,Rust提供了兩種類型:

  • &:共享引用
  • &mut:可變引用

在編譯階段优俘,Rust的行為是,同一作用域內(nèi)掀序,對(duì)于某一個(gè)對(duì)象的引用帆焕,只允許存在兩種情況:要么只有一個(gè)可變引用,要么同時(shí)存在多個(gè)共享引用不恭,共享引用不允許修改內(nèi)容叶雹,可變引用才有修改權(quán)限。

比如:

struct Person {
  name: String,
  age: usize,
}

fn main() {
  let person = Person { name: "Joe Biden".to_string(), age: 79 };  
  let person_ref: &Person = &person;  
  person_ref.age = 83;
}

其中换吧,

person_ref.age = 34;

編譯失敗浑娜,因?yàn)?code>person_ref屬于共享引用,并沒有修改權(quán)限式散。

再看一個(gè)簡(jiǎn)單的例子:

fn main() {
    let x = 1;
    let y = &x;
    y = 2;
}

其中筋遭,

y=2;

報(bào)錯(cuò),因?yàn)?code>y屬于共享引用暴拄。

編譯階段漓滔,Rust borrow checker會(huì)對(duì)對(duì)象修改進(jìn)行檢查,一旦監(jiān)測(cè)到共享引用修改引用指向的內(nèi)容就會(huì)報(bào)錯(cuò)乖篷,這個(gè)檢查符合rust的設(shè)計(jì)原則响驴,但是對(duì)于開發(fā)者的要求卻顯得過于苛刻。

作為面向系統(tǒng)開發(fā)的語(yǔ)言撕蔼,大多數(shù)rust開發(fā)者很有可能是從C/C++系列遷移而來豁鲤,在此類語(yǔ)言中,獲得一個(gè)變量的引用鲸沮,然后對(duì)其進(jìn)行修改是一件非常正常的事情琳骡。

另一方面,由于 Rust 的 mutable特性讼溺, 一個(gè)結(jié)構(gòu)體中的字段楣号,要么全都是 immutable,要么全部是mutable怒坯,不支持針對(duì)部分字段進(jìn)行設(shè)置。比如剔猿,在一個(gè)struct中视译,可能只有個(gè)別的引用需要修改,而其他變量并不需要修改归敬,為了一個(gè)變量而將整個(gè)struct變?yōu)?code>&mut也是不合理的酷含。

作為語(yǔ)言規(guī)范鄙早,盡管Rust在設(shè)計(jì)范式上開誠(chéng)布公,也給了開發(fā)者明確的預(yù)期第美,但是與大多數(shù)人長(zhǎng)期形成的習(xí)慣進(jìn)行對(duì)抗并非什么明智之舉蝶锋。畢竟陆爽,弱小和無知不是生存的障礙什往,傲慢才是。

還好Rust的founder們并不是傲慢的人慌闭,為了解決這個(gè)現(xiàn)實(shí)問題别威,專門引入了內(nèi)部可變性。

所謂內(nèi)部可變性驴剔,簡(jiǎn)單理解省古,就是賦予共享引用修改的權(quán)限,由于這個(gè)“賦予”行為是明確指定的丧失,并未違反rust的設(shè)計(jì)原則豺妓。

內(nèi)部可變性引入了Cell與RefCell兩個(gè)wrapper。

示例

樣例1:

use std::cell::Cell;

#[derive(Debug)]
struct Person {
 name: String,
 age: Cell<usize>,
}

fn main() {
 let person = Person { name: "Joe Biden".to_string(), age: Cell::new(79) };  
 let person_ref: &Person = &person;  

 println!("Age is : {:?}", person_ref);
 person_ref.age.set(83);
 println!("Age is : {:?}", person_ref);
}

樣例2:

use std::cell::Cell;
fn main() {
    let x = Cell::new(1);
    let y = &x;
    y.set(2);
    println!("{}", x);
}

也可以使用RefCell來實(shí)現(xiàn):
樣例1:

use std::cell::RefCell;

#[derive(Debug)]
struct Person {
  name: String,
  age: RefCell<usize>,
}
fn main() {
  let person = Person { name: "Joe Biden".to_string(), age: RefCell::new(79) };  
  let person_ref: &Person = &person;  
  println!("Age is : {:?}", person_ref);
  *person_ref.age.borrow_mut() = 83;
  println!("Age is : {:?}", person_ref);
}

樣例2:

use std::cell::RefCell;
fn main() {
    let x = RefCell::new(1);
    let y = &x;
    *y.borrow_mut() =2;
    println!("{}", x);
}

內(nèi)部可變性違背了rust關(guān)于共享引用的約定布讹,但是通過引入Cell與RefCell琳拭,這種走后門的行為是嚴(yán)格備案的,并沒有違背原則描验。

既然Cell與RefCell都能夠?qū)崿F(xiàn)內(nèi)部可變性白嘁,那這兩者之間有什么差異呢?

Cell與RefCell差異

首先來看定義:

struct Cell<T> {
    value: UnsafeCell<T>, 
}

struct RefCell<T: ?Sized> {
    borrow: Cell<usize>,
    value: UnsafeCell<T>,
}

RefCell相比Cell膘流,內(nèi)部維護(hù)了一個(gè)包裝對(duì)象的引用計(jì)數(shù)絮缅,當(dāng)通過RefCell.borrow獲取一個(gè)共享引用時(shí),內(nèi)部引用計(jì)數(shù)加一呼股,當(dāng)獲取的引用離開作用域時(shí)耕魄,內(nèi)部引用計(jì)數(shù)減一,當(dāng)RefCell.borrow_mut獲取一個(gè)可變引用時(shí)彭谁,首先檢測(cè)引用技數(shù)是否為 0屎开,如果為 0,正常返回马靠,如果不為 0奄抽,直接 panic,其實(shí)RefCell.borrow時(shí)也會(huì)做類似的檢測(cè)甩鳄,當(dāng)已經(jīng)獲取了可變引用也是直接 panic逞度, 當(dāng)然為了避免 panic,我們可以用 RefCell.try_borrowRefCell.try_borrow_mut 來獲取一個(gè) Result 類型妙啃。

由于Cell并未引入引用計(jì)數(shù)档泽,所以Cell<T>需要滿足T:Copy

impl<T> Cell<T> where T: Copy {

  const fn new(value: T) -> Cell<T>;
  
  // Returns a copy of the contained value.
  fn get(&self) -> T;
  
  // Sets the contained value.
  fn set(&self, val: T);
}

對(duì)于Cell而言俊戳,通過get獲取到的是原有對(duì)象的拷貝,set則使用新的對(duì)象替換原有老對(duì)象馆匿。RefCell<T>沒有這個(gè)約束抑胎,它的操作都是通過返回可變指針完成。

由于實(shí)現(xiàn)機(jī)制上的差別渐北,Cell只能包裝Copy類型阿逃,而RefCell能夠包裝任意類型,所以在不確定一個(gè)對(duì)象是否實(shí)現(xiàn)了Copy時(shí)赃蛛,應(yīng)該選擇RefCell恃锉。

由于上述差異,RefCell更加常用呕臂,通常的做法是配合Rc破托,組成Rc<RefCell<T>>

限制

由于Cell與RefCell均未實(shí)現(xiàn)Sync歧蒋,所以這兩種類型均只能用于單線程土砂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者谜洽。
  • 序言:七十年代末萝映,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子褥琐,更是在濱河造成了極大的恐慌锌俱,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敌呈,死亡現(xiàn)場(chǎng)離奇詭異贸宏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)磕洪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門吭练,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人析显,你說我怎么就攤上這事鲫咽。” “怎么了谷异?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵分尸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我歹嘹,道長(zhǎng)箩绍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任尺上,我火速辦了婚禮材蛛,結(jié)果婚禮上圆到,老公的妹妹穿的比我還像新娘。我一直安慰自己卑吭,他們只是感情好芽淡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著豆赏,像睡著了一般挣菲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上河绽,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天己单,我揣著相機(jī)與錄音唉窃,去河邊找鬼耙饰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛纹份,可吹牛的內(nèi)容都是我干的苟跪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼蔓涧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼件已!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起元暴,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤篷扩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后茉盏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鉴未,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年鸠姨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铜秆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡讶迁,死狀恐怖连茧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巍糯,我是刑警寧澤啸驯,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站祟峦,受9級(jí)特大地震影響罚斗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜搀愧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一惰聂、第九天 我趴在偏房一處隱蔽的房頂上張望疆偿。 院中可真熱鬧,春花似錦搓幌、人聲如沸杆故。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)处铛。三九已至,卻和暖如春拐揭,著一層夾襖步出監(jiān)牢的瞬間撤蟆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工堂污, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留家肯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓盟猖,卻偏偏與公主長(zhǎng)得像讨衣,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子式镐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

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