最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺談Rust?+=?運(yùn)算符與?MIR?應(yīng)用

 更新時(shí)間:2023年01月31日 10:26:54   作者:Mr_Vague  
這篇文章主要介紹了Rust?+=?運(yùn)算符與?MIR?應(yīng)用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

+= 運(yùn)算符與 MIR 應(yīng)用

本文 += 運(yùn)算符部分整理自 Why does += require manual dereference when AddAssign() does not? 后半部分,
MIR 部分是我自己補(bǔ)充的。

只在 https://zjp-cn.github.io/rust-note/ 上更新,其他地方懶得同步更新。

+= 解語(yǔ)法糖

一個(gè)基礎(chǔ),但很少會(huì)思考的問(wèn)題,Rust 的 += 運(yùn)算符是什么代碼的語(yǔ)法糖?

a = a + b 不等價(jià)于 a += b

a = a + ba += b 的語(yǔ)法糖嗎?這意味著任何 a += b 與任何 a = a + b 代碼等價(jià)。

如果以標(biāo)準(zhǔn)庫(kù)定義的 impls 為例子,你可能覺(jué)得兩種寫(xiě)法都能編譯,而且結(jié)果一致。

但考慮以下自定義類(lèi)型的實(shí)現(xiàn):

use std::ops::{Add, AddAssign};

fn main() {
    let mut s = S;
    s += (); // ok
    s = s + (); // error: expected struct `S`, found `()`
}

struct S;

impl Add<()> for S {
    type Output = ();
    fn add(self, _: ()) { }
}

impl AddAssign<()> for S {
    fn add_assign(&mut self, _: ()) { }
}

代碼不通過(guò),原因是顯然的,s + () 的類(lèi)型是 (),無(wú)法賦值給 s —— a = a + b 不是 a += b 的語(yǔ)法糖。

從運(yùn)算符的 trait 定義來(lái)看(以 + vs += 為例),它們沒(méi)有任何關(guān)系:

pub trait Add<Rhs = Self> {
    type Output;

    fn add(self, rhs: Rhs) -> Self::Output;
}

pub trait AddAssign<Rhs = Self> {
    fn add_assign(&mut self, rhs: Rhs);
}

AddAssign::add_assign(&mut a, b)a += b

++= 是典型的二元運(yùn)算符和復(fù)合賦值運(yùn)算符。根據(jù)各自的運(yùn)算符 trait 定義,可以得到以下解語(yǔ)法糖:

  • a + b 實(shí)際調(diào)用 Add::add(a, b)
  • a += b 實(shí)際調(diào)用 AddAssign::add_assign(&mut a, b)

注意以下幾點(diǎn):

  • 若 a 和 b 擁有所有權(quán)時(shí),其右側(cè)運(yùn)算數(shù) b 的所有權(quán)被獲取1,而對(duì)待左側(cè)運(yùn)算數(shù)所有權(quán)的方式并不相同:
  • a + b 獲取了 a 的所有權(quán)(無(wú)法再使用 a)
  • a += b 獲取了 a 的獨(dú)占引用,而非所有權(quán)(a 必須是 mut 的,而且此后仍可以使用 a)
  • 若 a 或 b 不擁有所有權(quán)時(shí),則不存在對(duì) a 或 b 所有權(quán)的轉(zhuǎn)移2:
  • 當(dāng) implementor 為引用時(shí),參數(shù)一并沒(méi)有發(fā)生所有權(quán)的移動(dòng)
  • 當(dāng)泛型類(lèi)型參數(shù)為引用時(shí),參數(shù)二并沒(méi)有發(fā)生所有權(quán)的移動(dòng)
  • 調(diào)用的形式最好使用完全限定語(yǔ)法,而不是方法調(diào)用語(yǔ)法。這是因?yàn)榉椒ㄕ{(diào)用表達(dá)式存在隱式的 自動(dòng)引用/解引用,而基于類(lèi)型的分析才更可靠。
  • a + b 實(shí)際調(diào)用 <TypeOfA as Add<TypeOfB>>::add(a, b),優(yōu)于 a.add(b)
  • a += b 實(shí)際調(diào)用 <TypeOfA as AddAssign<TypeOfB>>::add_assign(&mut a, b),優(yōu)于 (&mut a).add_assign(b)

本文的重點(diǎn)在于 a += b,而不是 a + b,所以對(duì) a + b 的內(nèi)容就此結(jié)束。

對(duì)于分析 a += b,我遵循以下思考流程:

  • 寫(xiě)下兩側(cè)的類(lèi)型,
  • Self += Rhs實(shí)際調(diào)用的形式,如
  • <Self as AddAssign<Rhs>>::add_assign(&mut Self, Rhs)
  • <Self as AddAssign<Rhs>>::add_assign(&mut a, b)
  • 完全限定語(yǔ)法的幾種等價(jià)形式:
    • Self: AddAssign<Rhs>:這在分析 trait bounds 時(shí)常用
    • impl AddAssign<Rhs> for Self:這在搜索具體實(shí)現(xiàn)時(shí)有用3

但像 += 這樣的“復(fù)合賦值運(yùn)算符”,一個(gè)鮮為人知的規(guī)則是關(guān)于兩側(cè)運(yùn)算數(shù)的求值順序。

賦值表達(dá)式的求值順序

通過(guò)一個(gè)示例來(lái)感受求值順序?yàn)槭裁粗匾?/p>

*{
    print!("lhs ");
    &mut 0
} += {
    print!("rhs ");
    0
};

*{
    print!("lhs ");
    &mut String::from("a")
} += {
    print!("rhs ");
    "b"
};

這段代碼打印什么?

如果你能準(zhǔn)確說(shuō)出和解釋打印的內(nèi)容,那么這小節(jié)內(nèi)容可以跳過(guò)了。

如果你不知道答案,請(qǐng)往下看。

規(guī)則

Rust 中,大部分表達(dá)式是從左往右求值的,比如對(duì)于方法調(diào)用表達(dá)式 (method call expression) a.add(b),脫糖為
Add::add(a, b),然后先計(jì)算左邊的 a,再計(jì)算右邊的 b。但賦值表達(dá)式不一定是從左到右求值。

Rust 具有兩種“賦值表達(dá)式”:

  • 賦值表達(dá)式 (assignment expressions):將一個(gè)值移動(dòng)進(jìn)指定的地方,語(yǔ)法為 assignee operand = assigned value operand
  • 復(fù)合賦值表達(dá)式 (compound assignment expressions):將運(yùn)算/邏輯二元運(yùn)算符與賦值表達(dá)式結(jié)合起來(lái),語(yǔ)法為
  • assigned operand 操作符 modifying operand,其中“操作符”為一個(gè)標(biāo)記后跟一個(gè) =(中間不含空格),比如 +=、|=、<<=

兩側(cè)運(yùn)算數(shù)的名稱(chēng)非常不直觀,所以我使用左右兩側(cè)的表達(dá)方式來(lái)稱(chēng)呼它們。實(shí)際上,它們以前被稱(chēng)作“左值” (lvalue) 和“右值” (rvalue)。

對(duì)賦值表達(dá)式來(lái)說(shuō),先計(jì)算等號(hào)右側(cè)的值,再計(jì)算等號(hào)左側(cè)的值,即從右到左;對(duì)于解構(gòu)賦值,其內(nèi)部求值順序?yàn)閺淖蟮接摇?/p>

# let (mut a, mut b);

(a, b) = (3, 4); // 從右到左:先計(jì)算等號(hào)右側(cè)的 (3, 4),再賦值給等號(hào)左側(cè)的 (a, b)

// 脫糖為

{
    let (_a, _b) = (3, 4); // 解構(gòu)賦值過(guò)程中,從左到右
    a = _a; // 先賦值給解構(gòu)模式左邊的 a
    b = _b; // 再賦值給解構(gòu)模式右邊的 b
}

對(duì)于復(fù)合賦值表達(dá)式,若兩側(cè)的類(lèi)型同時(shí)為 primitives,從右到左計(jì)算;否則從左到右計(jì)算。

回到本小節(jié)開(kāi)頭的示例,現(xiàn)在可以仔細(xì)分析代碼了:

// 等號(hào)兩側(cè)的類(lèi)型都為 `i32`,它是 primitive type,所以從右到左計(jì)算,打印 `rhs lhs `
*{
    print!("lhs ");
    &mut 0
} += {
    print!("rhs ");
    0
};

// 等號(hào)左右的類(lèi)型為 `String` 和 `&str`,都不是 primitive type,所以從左到右計(jì)算,打印 `lhs rhs `
*{
    print!("lhs ");
    &mut String::from("a")
} += {
    print!("rhs ");
    "b"
};

或許這些細(xì)節(jié)你會(huì)感到困惑:

問(wèn)
等號(hào)左側(cè)為什么要那樣寫(xiě)?因?yàn)椴辉试S直接寫(xiě) 0 += ...
為什么左側(cè)可以維持臨時(shí)的引用 &mut見(jiàn) temporary-lifetime-extension
為什么左側(cè)類(lèi)型是 i320 的類(lèi)型為 i32,這是 Rust 默認(rèn)推斷的;&mut 0 類(lèi)型為 &mut i32;*&mut 0 類(lèi)型為 i32
為什么 i32 是 primitive type?見(jiàn)標(biāo)準(zhǔn)庫(kù) primitive types
什么是 primitive type?見(jiàn)標(biāo)準(zhǔn)庫(kù) primitive types
為什么 &str 不是 primitive type?見(jiàn)標(biāo)準(zhǔn)庫(kù) primitive types,且見(jiàn)下面的例子:i32 是,&i32 不是,所以 str 是,&str 不是

總而言之,在 Rust 中,大部分表達(dá)式的求值順序是從左往右的,僅有少數(shù)地方是從右往左的,比如:

賦值表達(dá)式:先計(jì)算等號(hào)右側(cè)復(fù)合賦值表達(dá)式:僅在兩側(cè)運(yùn)算數(shù)都為 primitive types 時(shí)才先計(jì)算右側(cè)運(yùn)算數(shù)。為了鞏固這一條,請(qǐng)確保你完全理解下面的
代碼 和注釋。此外,你還可以看懂 rustc 的這個(gè) 測(cè)試代碼。

use std::num::Wrapping;

macro_rules! add_assign {
    ($e1:expr, $e2:expr) => {
        *({print!("lhs "); &mut $e1}) += {print!("rhs "); $e2};
        println!("");
    }
}

fn main() {
    add_assign!(1, 2); // rhs lhs: both operands are primitives
    add_assign!(1, &2); // lhs rhs: Rhs &i32 is not a direct primitive
    add_assign!(String::new(), ""); // lhs rhs: neither operands are primitives
    add_assign!(Wrapping(1), Wrapping(2)); // lhs rhs: neither operands are primitives
    // So usually the execution order of `+=` is LTR (left-to-right)
}

MIR

Rust 的 MIR 是 HIR 到 LLVM IR 的中間產(chǎn)物,對(duì) Rust 眾多語(yǔ)法糖進(jìn)行了脫糖,并且極大地精簡(jiǎn)了 Rust
語(yǔ)法(但并非其語(yǔ)法子集),是觀察和分析 Rust 代碼的常用手段,尤其是在控制流圖和借用檢查方面。

獲取 MIR 的最簡(jiǎn)便的方式是通過(guò) playground 左上角下拉框,點(diǎn)擊 MIR 按鈕。

此外,你還可以使用 rustc src/main.rs -Z dump-mir=maincargo rustc -- -Z dump-mir=main 獲得有關(guān) main 函數(shù)完整的 MIR

  • 查看 mir_dump/main.main.-------.renumber.0.mir 等文件
  • 使用 cargo rustc -- -Z help 查看更多 mir 相關(guān)命令
  • 相關(guān) MIR 資料

Rust Blog: Introducing MIR 友好的官方入門(mén)解釋

rustc-dev-guide: MIR Debugging

rustc-dev-guide: The MIR (Mid-level IR)

對(duì)于上一節(jié)開(kāi)頭的示例:

// 去除了無(wú)關(guān)和冗雜的 print!,將這段代碼復(fù)制到 play.rust-lang.org 查看 MIR
*{ &mut 0 } += 0;
*{ &mut String::from("a") } += "b";

關(guān)鍵的 MIR 輸出:

bb0: {
    _1 = const 0_i32;
    _3 = const 0_i32;
    _2 = &mut _3;
    _4 = CheckedAdd((*_2), _1);
    assert(!move (_4.1: bool), "attempt to compute `{} + {}`, which would overflow", (*_2), move _1) -> bb1;
}

bb2: {
    _7 = &mut _8;
    _6 = &mut (*_7);
    _10 = const "b";
    _9 = _10;
    _5 = <String as AddAssign<&str>>::add_assign(move _6, move _9) -> [return: bb3, unwind: bb5];
}

這很容易解釋 += 的語(yǔ)法脫糖和真正的執(zhí)行順序:

  • _4 = CheckedAdd((*_2), _1) 這里的執(zhí)行順序是從右到左(注意觀察編號(hào)),并且不是調(diào)用 <i32 as AddAssign<i32>>::add_assign

而是直接調(diào)用 CheckedAdd 函數(shù)。

  • add_assign!(1, &2) 則對(duì)應(yīng) _1 = <i32 as AddAssign<&i32>>::add_assign(move _2, move _13) -> bb5,順序從左到右,調(diào)用了重載的

+= trait 方法。

  • _5 = <String as AddAssign<&str>>::add_assign(move _6, move _9) 這里的順序是從左到右,調(diào)用的是重載的 += trait 方法。

單一實(shí)現(xiàn)下的強(qiáng)轉(zhuǎn)

遵循前面我提到的流程,對(duì)于以下正常工作代碼,第一步,寫(xiě)下左右兩側(cè)的類(lèi)型,你會(huì)得到 S += &&&&&&(),實(shí)際不存在這個(gè)實(shí)現(xiàn),因?yàn)?br />S 僅有 S: AddAssign<&()>。這發(fā)生了什么?

struct S;
impl std::ops::AddAssign<&()> for S {
    fn add_assign(&mut self, _: &()) {}
}

fn main() {
    let mut s = S;
    let rrrrrr = &&&&&&();
    s += rrrrrr;
}

通過(guò) MIR,你會(huì)發(fā)現(xiàn)

  • <S as AddAssign<&()>>::add_assign(move _4, move _5) 表明從左到右執(zhí)行,因?yàn)閮蓚?cè)運(yùn)算數(shù)不是 primitive type
  • 傳給 add_assign 的第二個(gè)參數(shù),其類(lèi)型并不是變量 rrrrrr 的類(lèi)型 &&&&&&(),而是經(jīng)過(guò) 5 次解引用之后的 &() 類(lèi)型
bb0: {
    _6 = const _;
    _4 = &mut _1;
    _7 = deref_copy (*_2);
    _8 = deref_copy (*_7);
    _9 = deref_copy (*_8);
    _10 = deref_copy (*_9);
    _11 = deref_copy (*_10);
    _5 = _11;
    _3 = <S as AddAssign<&()>>::add_assign(move _4, move _5) -> bb1;
}

這里隱式的解引用是因?yàn)閺?qiáng)轉(zhuǎn),而函數(shù)參數(shù)是能夠發(fā)生 強(qiáng)轉(zhuǎn)的地方 之一。

并且,依據(jù)這段 MIR(注意看從上到下的執(zhí)行過(guò)程),我們知道,對(duì)于已知的 add_assign 實(shí)現(xiàn),執(zhí)行順序先于強(qiáng)轉(zhuǎn)發(fā)生。

而當(dāng) SAddAssign 實(shí)現(xiàn)是多個(gè),強(qiáng)轉(zhuǎn)被阻止,你需要傳入準(zhǔn)確的類(lèi)型的值:

struct S;
impl std::ops::AddAssign<()> for S {
    fn add_assign(&mut self, _: ()) {}
}
impl std::ops::AddAssign<&()> for S {
    fn add_assign(&mut self, _: &()) {}
}

fn main() {
    let mut s = S;
    let rrrrrr = &&&&&&();
    s += rrrrrr;
}

// error[E0277]: cannot add-assign `&&&&&&()` to `S`
//   --> src/main.rs:12:7
//    |
// 12 |     s += rrrrrr;
//    |       ^^ no implementation for `S += &&&&&&()`
//    |
//    = help: the trait `AddAssign<&&&&&&()>` is not implemented for `S`
//    = help: the following other types implement trait `AddAssign<Rhs>`:
//              <S as AddAssign<&()>>
//              <S as AddAssign<()>>

兩階段借用的參與

以下代碼能夠運(yùn)行:

  • 由于兩側(cè)類(lèi)型不是 primitive type, add_assign 從左到右執(zhí)行
  • 但已經(jīng)使用 &mut self 的情況下,為什么能夠同時(shí)執(zhí)行帶 &self 的方法?
struct S;
impl std::ops::AddAssign<()> for S {
    fn add_assign(&mut self, _: ()) {}
}
impl S {
    fn no_op(&self) {}
}

fn main() {
    let mut s = S;
    s += s.no_op();
}

通常對(duì)于初學(xué)者, &mut 會(huì)有兩個(gè)更高級(jí)的主題:

  • 重新借用 (reborrow)
  • open 狀態(tài)的 Reference issue、RFC issue,在遷移到 Chalk 之前,不會(huì)正式描述 reborrow
  • 它大概是說(shuō):我們看見(jiàn)的 &'a mut T,實(shí)際被自動(dòng)轉(zhuǎn)化成更短的 &'b mut T,從而看起來(lái) &mut T 一直可用。這也發(fā)生在
  • &T 上面,但通常我們對(duì) &mut T 的 reborrow 更敏感。
  • 這一是個(gè)在 1.0 之前就有的概念
  • UCG 可能會(huì)對(duì) reborrow 做出說(shuō)明
  • 一個(gè)直覺(jué)上的理解
  • 兩階段借用 (two-phase borrows)
  • 它在 rustc dev guide 上的 正式介紹
  • 它大概是說(shuō),某些情況下 &mut T 會(huì)劃分成兩個(gè)階段進(jìn)行使用:
  • 在 reservation 階段:&mut T 像是 &T 那樣,以允許多個(gè) &T 同時(shí)存在
  • 在 activated 階段:&mut T 以完全獨(dú)占的方式使用
  • 某些情況指以下三種情況之一(上述鏈接對(duì)具體例子都有分析):
  • 調(diào)用 receiver 為 &mut self 的方法(包括方法調(diào)用時(shí)的自動(dòng)引用):如 vec.push(vec.len())
  • 函數(shù)參數(shù)中的 &mut T reborrow:如 std::mem::replace(r, vec![r.len()])
  • 重載的復(fù)合賦值運(yùn)算符中隱式的 &mut T:如本小節(jié)示例
  • 代碼中,任何顯式的 &mutref mut 都不是兩階段借用

MIR 可以幫助你看到兩階段借用。

bb0: { // reservation 階段
    _3 = &mut _1; // 兩階段借用的第三種前提:重載的復(fù)合賦值運(yùn)算符中隱式的 `&mut T`
    _5 = &_1;     // `&mut T` 暫時(shí)被視為 `&T`,從而允許在此處使用 `&T`
    _4 = S::no_op(move _5) -> bb1;
}
bb1: { // activated 階段
    _2 = <S as AddAssign<()>>::add_assign(move _3, move _4) -> bb2;
}

實(shí)戰(zhàn)

例子源自 #72199 issue,@steffahn 做了很好的 解釋?zhuān)@里從 MIR 角度進(jìn)行補(bǔ)充。

Vec<i32>v[i] += v[j]

fn main() {
    let mut v = Vec::from([0, 1]); // 為了讓 MIR 精簡(jiǎn),故意不使用 vec![0, 1]
    v[0] += v[1]; // 第一步:i32 += i32
}

// 兩側(cè)為 primitive types, RTL: <i32 as Add<i32>>::add_assign(&mut v[0], v[1])
// 1. 計(jì)算 v[1]:對(duì)它脫糖 `<Vec<i32> as Index<usize>>::index(&v, 1)` 得到 `&i32`,然后解引用得到 `i32` 
// 2. 計(jì)算 &mut v[0]:對(duì)它脫糖 `<Vec<i32> as IndexMut<usize>>::index_mut(&mut v, 0)` 得到 `&mut i32`
// 可以看到先使用了 `&v`,再使用了 `&mut v`,通過(guò)借用檢查

// 僅列出 MIR 中的重點(diǎn)
// let mut _1: std::vec::Vec<i32>;
// bb1: {
//     _5 = &_1;
//     _4 = <Vec<i32> as Index<usize>>::index(move _5, const 1_usize) -> [return: bb2, unwind: bb6];
// }
// bb2: {
//     _3 = (*_4);
//     _7 = &mut _1;
//     _6 = <Vec<i32> as IndexMut<usize>>::index_mut(move _7, const 0_usize) -> [return: bb3, unwind: bb6];
// }
// bb3: {
//     _8 = CheckedAdd((*_6), _3);
//     assert(!move (_8.1: bool), "attempt to compute `{} + {}`, which would overflow", (*_6), move _3) -> [success: bb4, unwind: bb6];
// }
// bb4: {
//     (*_6) = move (_8.0: i32);
//     drop(_1) -> bb5;
// }

&mut [Custom]v[i] += v[j]

#[derive(Clone, Copy)]
struct MyNum(i32);

impl std::ops::AddAssign for MyNum {
    fn add_assign(&mut self, rhs: MyNum) {
        *self = MyNum(self.0 + rhs.0)
    }
}

fn main() {
    let mut b = vec![MyNum(0), MyNum(1)];
    let v = b.as_mut_slice();
    v[0] += v[1]; // MyNum += MyNum
}

// LTR: <MyNum as Add<MyNum>>::add_assign(&mut v[0], v[1])
// 1. 計(jì)算 &mut v[0]:獲取和維持對(duì)第 0 元素的獨(dú)占引用,但只進(jìn)入 reservation 階段,將 &mut 視為 &,從而繼續(xù)使用切片
// 2. 計(jì)算 v[1]:在 `&mut v[0]` 的第一階段,通過(guò) `*_10` 和索引拷貝 MyNum
// 3. 調(diào)用方法,`&mut v[0]` 進(jìn)入 activated 階段

// 僅列出 MIR 中的重點(diǎn)
// let mut _1: std::vec::Vec<MyNum>;
// bb2: {
//     _11 = &mut _1;
//     _10 = Vec::<MyNum>::as_mut_slice(move _11) -> [return: bb3, unwind: bb8];
// }
// bb3: { // 索引前進(jìn)行了邊界檢查
//     _14 = const 0_usize;
//     _15 = Len((*_10));
//     _16 = Lt(_14, _15);
//     assert(move _16, "index out of bounds: the length is {} but the index is {}", move _15, _14) -> [success: bb4, unwind: bb8];
// }
// bb4: {
//     _13 = &mut (*_10)[_14]; // 獲取 &mut v[0],進(jìn)入 reservation 階段
//     _18 = const 1_usize; // 索引前進(jìn)行了邊界檢查
//     _19 = Len((*_10));
//     _20 = Lt(_18, _19);
//     assert(move _20, "index out of bounds: the length is {} but the index is {}", move _19, _18) -> [success: bb5, unwind: bb8];
// }
// bb5: {
//     _17 = (*_10)[_18]; // 計(jì)算 v[1]
//     _12 = <MyNum as AddAssign>::add_assign(move _13, move _17) -> [return: bb6, unwind: bb8]; // activated 階段
// }

Vec<Custom>v[i] += v[j]

#[derive(Clone, Copy)]
struct MyNum(i32);

impl std::ops::AddAssign for MyNum {
    fn add_assign(&mut self, rhs: MyNum) {
        *self = MyNum(self.0 + rhs.0)
    }
}

fn main() {
    let mut b = vec![MyNum(0), MyNum(1)];
    b[0] += b[1];
}

它無(wú)法編譯成功,但編譯器提示你怎么 解決(把右側(cè)的值賦給局部變量,然后使用該變量):

error[E0502]: cannot borrow `b` as immutable because it is also borrowed as mutable
  --> src/main.rs:12:13
   |
12 |     b[0] += b[1];
   |     --------^---
   |     |       |
   |     |       immutable borrow occurs here
   |     mutable borrow occurs here
   |     mutable borrow later used here
   |
help: try adding a local storing this...
  --> src/main.rs:12:13
   |
12 |     b[0] += b[1];
   |             ^^^^
help: ...and then using that local here
  --> src/main.rs:12:5
   |
12 |     b[0] += b[1];
   |     ^^^^^^^^^^^^

當(dāng)你試著從 MIR 分析為什么這樣,你會(huì)發(fā)現(xiàn) playground 因?yàn)榫幾g失敗而沒(méi)有 MIR 的結(jié)果,提示為
Unable to locate file for Rust MIR output。

此時(shí),你仍可以在本地獲取一部分 MIR 結(jié)果,因?yàn)?MIR 其實(shí)經(jīng)過(guò)許多次迭代,mir_dump 文件夾下保留了半成品:運(yùn)行
cargo rustc -- -Z dump-mir=main,查看 mir_dump/simd.main.-------.renumber.0.mir 文件。

// 僅列出關(guān)鍵部分
bb4: {
    _13 = &mut _1;
    _12 = <Vec<MyNum> as IndexMut<usize>>::index_mut(move _13, const 0_usize) -> [return: bb5, unwind: bb9];
}
bb5: {
    _11 = &mut (*_12);
    StorageDead(_13);
    StorageLive(_14);
    StorageLive(_15);
    StorageLive(_16);
    _16 = &_1;
    _15 = <Vec<MyNum> as Index<usize>>::index(move _16, const 1_usize) -> [return: bb6, unwind: bb9];
}
bb6: {
    _14 = (*_15);
    StorageDead(_16);
    _10 = <MyNum as AddAssign>::add_assign(move _11, move _14) -> [return: bb7, unwind: bb9];
}

把它與上一小節(jié)在 &mut [MyNum] 的 MIR 進(jìn)行對(duì)比,你會(huì)發(fā)現(xiàn)在 &mut Vec<MyNum> 上沒(méi)有發(fā)生兩階段借用:

  • 觀察兩個(gè) MIR 片段的 move _13,第二個(gè)片段的 &mut _1 借用已經(jīng)在獲取索引時(shí)結(jié)束(未能到達(dá) add_assign),而第一個(gè)在調(diào)用 add_assign 時(shí)結(jié)束
  • 所以 Vec<MyNum> 上的 b[0] += b[1] 是通過(guò)兩個(gè)不同的 &mut Vec<MyNum>&Vec<MyNum>,分別得到 &mut MyNumMyNum 兩個(gè)操作數(shù)

而 Rust 的借用檢查不允許在一個(gè)函數(shù)調(diào)用中對(duì)同一個(gè)值同時(shí)使用 &mut&,從而編譯報(bào)錯(cuò)。

// b[0] += b[1] on &mut [MyNum]

_10 = Vec::<MyNum>::as_mut_slice(move _11) // _10: &mut [MyNum]

_13 = &mut (*_10)[_14]; // two-phase
_17 = (*_10)[_18];      // reservation 階段

_12 = <MyNum as AddAssign>::add_assign(move _13, move _17) // activated 階段

// b[0] += b[1] on Vec<MyNum>

_13 = &mut _1; // _1: Vec<MyNum>
_12 = <Vec<MyNum> as IndexMut<usize>>::index_mut(move _13, const 0_usize) // _13: &mut Vec<MyNum>, _12: &mut MyNum
_11 = &mut (*_12); // reborrow

_16 = &_1;
_15 = <Vec<MyNum> as Index<usize>>::index(move _16, const 1_usize) // _16: &Vec<MyNum>, _15: &mut MyNum
_14 = (*_15);

_10 = <MyNum as AddAssign>::add_assign(move _11, move _14)
  • 總結(jié) += 是可重載的復(fù)合賦值運(yùn)算符,Self += Rhs 脫糖為 <Self as AddAssign<Rhs>>::add_assign(&mut Self, Rhs),但
  • 對(duì)兩側(cè)為 primitive types 的運(yùn)算數(shù),先計(jì)算 Rhs,再計(jì)算 Self,然后調(diào)用編譯器實(shí)現(xiàn)的相加函數(shù)
  • 若至少有一側(cè)運(yùn)算數(shù)不為 primitive types,則先計(jì)算 Self,再計(jì)算 Rhs,然后調(diào)用重載后的實(shí)現(xiàn)(即 <Self as AddAssign<Rhs>>::add_assign
  • 大多數(shù)表達(dá)式是從左到右執(zhí)行的。從右到左是特殊情況,比如
  • 賦值表達(dá)式中,先計(jì)算 = 右側(cè)的值,再計(jì)算左側(cè)
  • 復(fù)合賦值表達(dá)式中,兩側(cè)為 primitive types 的運(yùn)算數(shù)時(shí),先計(jì)算復(fù)合賦值運(yùn)算符右側(cè),再計(jì)算左側(cè)
  • MIR 是 Rust 編譯過(guò)程的重要一環(huán),(無(wú)論在代碼編譯成功還是失敗的情況下)也可以成為輔助你分析的 Rust 代碼的工具
  1. 一個(gè)例子

  2. 另一個(gè)例子 

  3. 拓展閱讀:運(yùn)用這套流程分析 == 操作符的具體的例子 

到此這篇關(guān)于Rust += 運(yùn)算符與 MIR 應(yīng)用的文章就介紹到這了,更多相關(guān)Rust += 運(yùn)算符內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關(guān)文章

  • 淺析Rust多線程中如何安全的使用變量

    淺析Rust多線程中如何安全的使用變量

    這篇文章主要為大家詳細(xì)介紹了Rust如何在線程的閉包中安全的使用變量,包括共享變量和修改變量,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考下
    2025-01-01
  • 深入了解Rust的生命周期

    深入了解Rust的生命周期

    生命周期指的是引用保持有效的作用域,Rust的每個(gè)引用都有自己的生命周期。本文將通過(guò)示例和大家詳細(xì)說(shuō)說(shuō)Rust的生命周期,需要的可以參考一下
    2022-12-12
  • Rust指南之泛型與特性詳解

    Rust指南之泛型與特性詳解

    泛型機(jī)制是編程語(yǔ)言用于表達(dá)類(lèi)型抽象的機(jī)制,一般用于功能確定、數(shù)據(jù)類(lèi)型待定的類(lèi),如鏈表、映射表等,這篇文章主要介紹了Rust指南泛型與特性,需要的朋友可以參考下
    2022-10-10
  • Rust for循環(huán)語(yǔ)法糖背后的API場(chǎng)景分析

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

    for語(yǔ)句是一種能確定循環(huán)次數(shù)的循環(huán),for 語(yǔ)句用于執(zhí)行代碼塊指定的次數(shù),今天通過(guò)本文給大家介紹Rust for循環(huán)語(yǔ)法糖背后的API場(chǎng)景分析,感興趣的朋友跟隨小編一起看看吧
    2022-11-11
  • Rust如何使用Sauron實(shí)現(xiàn)Web界面交互

    Rust如何使用Sauron實(shí)現(xiàn)Web界面交互

    Sauron?是一個(gè)多功能的?Web?框架和庫(kù),用于構(gòu)建客戶(hù)端和/或服務(wù)器端?Web?應(yīng)用程序,重點(diǎn)關(guān)注人體工程學(xué)、簡(jiǎn)單性和優(yōu)雅性,這篇文章主要介紹了Rust使用Sauron實(shí)現(xiàn)Web界面交互,需要的朋友可以參考下
    2024-03-03
  • Rust?Atomics?and?Locks內(nèi)存序Memory?Ordering詳解

    Rust?Atomics?and?Locks內(nèi)存序Memory?Ordering詳解

    這篇文章主要為大家介紹了Rust?Atomics?and?Locks內(nèi)存序Memory?Ordering詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • Rust中的關(guān)聯(lián)類(lèi)型總結(jié)

    Rust中的關(guān)聯(lián)類(lèi)型總結(jié)

    關(guān)聯(lián)類(lèi)型是定義通用trait的一種機(jī)制。它允許在trait中定義一個(gè)或多個(gè)占位符類(lèi)型,這些類(lèi)型將在trait的實(shí)現(xiàn)中具體化。文中有詳細(xì)示例代碼供參考,需要的朋友可以閱讀一下
    2023-05-05
  • Rust Struct結(jié)構(gòu)體詳解

    Rust Struct結(jié)構(gòu)體詳解

    結(jié)構(gòu)體,是一種自定義數(shù)據(jù)類(lèi)型,允許程序員將不同類(lèi)型的數(shù)據(jù)結(jié)合起來(lái),形成相關(guān)聯(lián)的整體。Rust的結(jié)構(gòu)體還提供方法和關(guān)聯(lián)函數(shù),可以指定那些與結(jié)構(gòu)體數(shù)據(jù)相關(guān)的行為
    2022-10-10
  • 關(guān)于Rust編譯時(shí)報(bào)link.exe?not?found錯(cuò)誤問(wèn)題

    關(guān)于Rust編譯時(shí)報(bào)link.exe?not?found錯(cuò)誤問(wèn)題

    這篇文章主要介紹了Rust編譯的時(shí)候報(bào)出link.exe?not?found錯(cuò)誤問(wèn)題,解決方法是在命令行就是CMD執(zhí)行相應(yīng)的命令即可,本文給大家分解決方法,需要的朋友可以參考下
    2022-09-09
  • Rust你不認(rèn)識(shí)的所有權(quán)

    Rust你不認(rèn)識(shí)的所有權(quán)

    所有權(quán)對(duì)大多數(shù)開(kāi)發(fā)者而言是一個(gè)新穎的概念,它是 Rust 語(yǔ)言為高效使用內(nèi)存而設(shè)計(jì)的語(yǔ)法機(jī)制。所有權(quán)概念是為了讓 Rust 在編譯階段更有效地分析內(nèi)存資源的有用性以實(shí)現(xiàn)內(nèi)存管理而誕生的概念
    2023-01-01

最新評(píng)論

兴和县| 镶黄旗| 安国市| 民丰县| 和田县| 彰化市| 石屏县| 瓦房店市| 兰西县| 嘉兴市| 博罗县| 通河县| 临沂市| 资阳市| 江西省| 古蔺县| 武鸣县| 江油市| 海林市| 东丰县| 巴林左旗| 襄垣县| 安化县| 广昌县| 海林市| 泸西县| 宁河县| 镇康县| 惠州市| 丰原市| 阜新市| 祁东县| 定襄县| 柯坪县| 耒阳市| 玉溪市| 海林市| 奈曼旗| 巴林左旗| 清水河县| 安溪县|