- Feature Name: const-control-flow
- Start Date: 2018-01-11
- RFC PR: rust-lang/rfcs#2342
- Rust Issue: rust-lang/rust#49146
- Translators: [@CYBAI]
- Commit: The commit link this page based on
- Updated: 2020-09-05
總結
透過此功能,可以在常數求值(const evaluation)中使用 if 及 match 並使他們被延遲求值。簡單來說,這項功能允許我們寫 if x < y { y - x } else { x - y };即使當使用非負型別時在 x < y 的 else 分支會報溢位錯誤 (overflow error)。
動機
在常數宣告中使用條件式對於建立像是 NonZero::new 的 const fn 及 直譯判定(interpreting assertions)來說很重要。
教學式解說
如果你寫
#![allow(unused)] fn main() { let x: u32 = ...; let y: u32 = ...; let a = x - y; let b = y - x; if x > y { // do something with a } else { // do something with b } }
這支程式永遠都會 panic(除非 x 和 y 同時是 0)因為不管是 x - y 或是 y - x 都會造成溢位。為了解決此問題,我們必須把 let a 及 let b 個別搬進 if 及 else 中。
#![allow(unused)] fn main() { let x: u32 = ...; let y: u32 = ...; if x > y { let a = x - y; // do something with a } else { let b = y - x; // do something with b } }
當改用常數時,上面的寫法就會出現新問題:
#![allow(unused)] fn main() { const X: u32 = ...; const Y: u32 = ...; const FOO: SomeType = if X > Y { const A: u32 = X - Y; ... } else { const B: u32 = Y - X; ... }; }
A 和 B 會比 FOO 先被求值,因為常數在定義上就是「常數」,所以不應被求值順序影響。這項假設在有錯誤的情況下並不成立,因為錯誤屬於副作用(side effects),因此不純(pure)。
為了解決此問題,我們必須把中介常數消掉並改為直接對 X - Y 及 Y - X 求值。
#![allow(unused)] fn main() { const X: u32 = ...; const Y: u32 = ...; const FOO: SomeType = if X > Y { let a = X - Y; ... } else { let b = Y - X; ... }; }
技術文件式解說
if 或是在 variant 沒有欄位的 enums 上做 match 會在 HIR -> MIR 階段時,被轉譯成 switchInt 終止器(terminator)。Mir 直譯器現在將會針對那些終止器求值(之前就可以了)。
在 variant 沒有欄位的 enums 上做 match 會被轉譯成 switch,表示他會被檢查 discriminant 或是在 packed enums(例如 Option<&T>)的情況會運算 discriminant(這個情況 discriminant 沒有特別的記憶體位址,但他會把所有的零視為 None,並把其他的值都當作 Some)。當進入 match 的分支時,匹配上的值基本上會被 transmute 成 enum 的 variant 型別,如此一來可以允許其他程式碼來存取該 enum 的欄位。
缺點
這項功能容易造成任意「常數」值(如:size_of::<T>() 或是特定的平台常數)編譯失敗。
原理及替代方案
利用中介 const fns 來破壞立即常數求值(eager const evaluation)
如果寫成
#![allow(unused)] fn main() { const X: u32 = ...; const Y: u32 = ...; const AB: u32 = if X > Y { X - Y } else { Y - X }; }
X - Y 或是 Y - X 其中一方有可能會報錯,這時必須加入中介 const fn
#![allow(unused)] fn main() { const X: u32 = ...; const Y: u32 = ...; const fn foo(x: u32, y: u32) -> u32 { if x > y { x - y } else { y - x } } const AB: u32 = foo(X, Y); }
const fn 的 x 和 y 參數未知,無法做常數求值(const evaluate)。當提供此 const fn 參數並求值時,只會對相應的分支求值。