https://mp.weixin.qq.com/s/XKPG64DBoQQF9_aTmwI7LQ
說明:Rust 2021 Edition 計划是官方博客准備在 4月30 號提前發布的,目前是預先發布到了官方博客的 GitHub 倉庫中,為了一睹為快,我就提前把它發布出來。 本文為原創翻譯
原文: The Plan for the Rust 2021 Edition[1]
我們很高興地宣布,Rust語言的第三版次(Edition) Rust 2021 edition 計划於今年10月發布。Rust 2021 包含許多細微的變化,但仍有望在實踐中對 Rust 產生很大的影響。
“譯注:
這里把 Edition 譯為 「版次」,是特意為了在中文中和 「版本」區別開來。
一般情況下,「版次」代表 Edition,而「版本」特指語義化版本。如果不做這樣的區分,都用「版本」就會很亂。
為什么不用「版」呢,因為「版」對應發行版本,對應 Stable/Nightly/Beta 發行版。
什么是「版次( Edition)」?
Rust 1.0 的發布確立了 “無停滯的穩定性”[2] 作為 Rust 交付的核心。從 1.0 發行版開始,Rust 的規則是,一旦某個功能在穩定版(Stable)上發布,我們將致力於在所有將來的發行版中都支持該功能。
但是,有時候在 Rust 的語法層面中進行一些小的更改,版次是有用的,否則這些更改將無法向后兼容。最明顯的例子是引入一個新的關鍵字,它會使變量等現有名稱無效。即使這樣的更改不會“感覺到”向后不兼容,它們仍然有可能破壞現有代碼。如果要進行此類更改,人們會很快發現現有程序停止編譯。
版次(Edition)是我們用來把這種不可能變成可能的機制。當我們希望發布一個向后不兼容的功能時,我們會將其作為新的Rust 版次的一部分發布。版次是可選的(Opt-in),因此,現有的 Crate 除非將其明確遷移到新版次,否則不會看到這些更改。Cargo 創建的新的 Crate 始終默認使用最新版次。
版次不會分裂生態系統
版次的最重要規則是,一個版次中的 Crate 可以與其他版次中編譯的 Crate 無縫地互操作。這確保了遷移到較新版次的決定是 Crate 可以做出的“私人”決定,而不影響其他人,除了它影響所需的 rustc 語義版本(version)之外(類似於使用任何新功能)。
Crate 互操作性的要求對我們在一個版次中可以進行的更改種類有一定的限制。通常,一個版次中發生的更改往往是“很薄的一層”。不管版次如何,所有 Rust 代碼最終都會在編譯器中編譯為相同的內部表示形式。
“譯注:版次(Edition)之間的差異,最終會在 MIR 層面消除。
版次遷移很容易,而且很大程度上是自動化的
我們的目標是使 Crate 輕松升級到新版次。每當我們發布新版次時,我們也會發布工具來自動進行遷移。工具不一定是完美的:它可能無法涵蓋所有極端情況,並且仍然可能需要手動更改。該工具盡力避免對語義的更改,這些更改可能影響代碼的正確性或性能。
除工具外,我們還維護一個《版次遷移指南(Edition Migration Guide)》[3],其中涵蓋了版次中的更改。該指南將描述更改,並提供指向人們可以在其中了解更多信息的指南。它還將涵蓋人們應注意的任何極端情況或細節。該指南既可以作為該版次的概述,也可以作為人們在使用自動化工具時遇到問題的快速疑難解答參考。最終版次列表將成為 Rust 2021 的一部分。所有這些摘要總結如下。
Rust 2021 計划進行哪些更改?
在過去的幾個月中,Rust 2021工作組已經就新版次中包含的內容提出了許多建議。我們很高興宣布最終候選名單。每個功能都必須滿足兩個條件才能進入此清單。
首先,它們必須得到相應 Rust 團隊的批准。
第二,它們的實現必須考慮周全,以使我們確信,它們能按計划的里程碑及時完成。
增補 Prelude
標准庫的 Prelude [4]是一個模塊,該模塊包含了標准庫中其他每個模塊必須自動導入的所有內容。它包含了常用的語言項(Item),比如 Option
、Vec
、drop
和 Clone
。
Rust編譯器會優先處理任何手動導入的項(Item),使其優先於 Prelude 中的項(Item),以確保在 Prelude 中添加的內容不會破壞任何現有代碼。例如,如果您有一個名為 example
的 Crate 或 模塊,其中包含pub struct Option ;
,則使用example::*;
。這樣就能明確引用 example
中的Option
,而不是標准庫中的Option
。
但是,在 Prelude 中添加 trait 可以以微妙的方式破壞現有代碼。比如,x.try_into()
,在使用MyTryInto
trait 中的方法進行調用時,如果還導入了std
的TryInto
,則這個調用可能會變得模棱兩可,並且無法編譯,因為它提供了具有相同名稱的方法。這就是我們尚未將TryInto
添加到 Prelude 的原因,因為有很多代碼會破壞這種方式。
作為解決方案,Rust 2021 將使用新的 Prelude。除了以下三個新增功能外,其余與當前的功能相同:
-
std::convert::TryInto [5] -
std::convert::TryFrom [6] -
std::iter::FromIterator [7]
仍然需要等待庫團隊(Library team)來批准這三條,但應該很快批准。
默認 Cargo Feature 解析器(Resolver)
從Rust 1.51.0開始,Cargo 支持了可選的新的 Feature 解析器[8],可以通過Cargo.toml
中的resolver ="2"
激活該功能。
從 Rust 2021 開始,這將是默認設置。也就是說,在Cargo.toml
中寫入edition ="2021"
會暗含 resolver ="2"
。
新的 Feature 解析器不再合並所有請求的功能,這些功能將以多種方式依賴於 Crate。有關詳細信息,請參見 `Rust 1.51` 的公告[9]。
數組(Array)支持 IntoIterator
在Rust 1.53
之前,只有對數組的引用才實現 IntoIterator
。這意味着您可以遍歷&[1、2、3]
和&mut [1、2、3]
,但不能直接遍歷[1、2、3]
。
for &e in &[1, 2, 3] {} // Ok :)
for e in [1, 2, 3] {} // Error :(
這是一個長期存在的問題,但是解決方案並不像看起來那樣簡單。僅添加trait
實現會破壞現有代碼。array.into_iter()
現在已可編譯,由於方法調用語法的工作原理,該函數隱式調用(&array).into_iter()
。添加trait
實現將改變含義。
通常,我們將這種類型的破壞(breakage)(添加trait
實現)分類為“輕微(minor)”和“可接受(minor)”。但是在這種情況下,有太多的代碼會被它破壞。
多次建議“僅在 Rust 2021 中為數組實現IntoIterator”。但是,這根本不可能。您不能在一個版次中存在trait
實現,而在另一個版次中則不能存在,因為版次可以混合使用。
因此,我們決定在所有版次中添加trait
實現(從Rust 1.53.0開始),但添加一個小技巧以避免在Rust 2021之前損壞。在 Rust 2015 和 2018 代碼中,編譯器仍將解析array.into_iter()
為(&array).into_iter()
,就好像trait
實現不存在一樣。這僅適用於.into_iter()
方法調用語法。它不會影響任何其他語法,例如[1、2、3]
中的e
或iter.zip([1、2、3])
。這些將開始在所有版次中使用。
遺憾的是,這需要上述小技巧以避免破損,但我們對這種如何將兩個版次之間的差異保持在最低限度的解決方案感到非常滿意。
閉包中不相關的捕獲
閉包(Closure) [10]會自動從上下文捕獲其引用的任何內容。例如,|| a + 1
會自動從周圍的上下文中捕獲對a
的引用。
當前,即使僅使用一個字段,也將影響整個結構。例如,|| a.x +1
捕獲對a
的引用,而不僅僅是a.x
。在某些情況下,這是一個問題。當結構的某個字段已被借用(可變)或移出時,其他字段將無法再用於閉包中,因為這將捕獲整個結構,而該結構不再可用。
let a = SomeStruct::new();
drop(a.x); // Move out of one field of the struct
println!("{}", a.y); // Ok: Still use another field of the struct
let c = || println!("{}", a.y); // Error: Tries to capture all of `a`
c();
從 Rust 2021 開始,閉包將僅捕獲其使用的字段。因此,以上示例在 Rust 2021 中可以很好地進行編譯。
此新行為僅在新版次中才被激活,因為它可以更改字段的 drop 順序。對於所有版次更改,都可以進行自動遷移。Cargo fix --edition
將能夠更新與此相關的閉包。也可以通過在閉包插入 let _ =&a;
來強制閉包像以前一樣捕獲整個結構。
Panic 宏的一致性
panic!()
宏是 Rust 中最常見的宏之一。但是,它有一些微妙的驚喜[11],我們不能僅僅因為向后兼容而進行更改。
panic!("{}", 1); // Ok, panics with the message "1"
panic!("{}"); // Ok, panics with the message "{}"
panic!()
宏僅在使用多個參數調用時才使用字符串格式。當使用單個參數調用時,它甚至不會查看該參數。
let a = "{";
println!(a); // Error: First argument must be a format string literal
panic!(a); // Ok: The panic macro doesn't care
(它甚至接受諸如panic!(123)
之類的非字符串,這是罕見的,很少有用。)
當隱式格式參數[12]將被穩定時,這尤其是一個問題。該功能將使println!("hello {name}")
成為 println!(" hello {}",name)
的簡寫形式。但是,panic!("hello {name}")
不能按預期工作,因為panic!()
不會將單個參數作為格式字符串處理。
為了避免這種混亂的情況,Rust 2021 提供了更一致的panic!()
宏。新的panic!()
宏將不再接受任意表達式作為唯一參數。就像println!()
一樣,它將始終將第一個參數作為格式字符串處理。
另外,Rust 2021 中的core::panic!()
和std::panic!()
相同。當前,這兩者之間存在一些歷史差異,當打開或關閉#![no_std]
時,這是很明顯的。
保留語法
為了將來為某些新語法騰出空間,我們決定為前綴的標識符和文字保留語法:prefix#identifier
,prefix" string"
,prefix'c'
和prefix#123
,其中prefix
可以是任何標識符。(除了已經具有含義的含義,例如b''
和r“”
。)
這是一個重大變化,因為宏當前可以接受hello"world"
,它們將被視為兩個單獨的標記:hello
和"world"
。(自動)修復很簡單。只需插入一個空格:hello "world"
。
除了將它們轉換為標記化錯誤外,RFC 尚未將含義附加到任何前綴。為特定的前綴分配含義留給將來的建議,由於現在保留了這些前綴,因此不會破壞更改。
這些是您將來可能會看到的一些新前綴:
f""
是格式字符串的簡寫形式。例如,f"hello {name}"
是等效的format_args!()
調用的簡寫形式。
c""
或z""
用於以N
結尾的C
字符串。
k#keyword
允許編寫當前版次中尚不存在的關鍵字。例如,雖然async
在 2015 edition
中不是關鍵字,但使用此前綴可以使我們在2015 edition
中接受k#async
,而不必等待2018 edition
將async
保留為關鍵字。
代碼質量檢查(Lint)
使用 Rust 2021,許多現有的 Lint 正成為 Crate 中的硬錯誤,在舊版次中,這些 Lint 將仍然是警告。
-
bare_trait_objects
:在Rust 2021中,必須使用dyn
關鍵字來標識“ trait 對象”。 -
ellipsis_inclusive_range_patterns
:Rust 2021中包含范圍模式的...
語法將是一個硬錯誤;新語法為..=
,與表達式一致。
我們可能會在此列表中添加更多Lint。
macro_rules
中的 或(Or) 模式
從 Rust 1.53.0 開始,模式(pattern)[13]被擴展以支持|
用於嵌套在模式中的任何位置。例如,現在可以寫Some(1 | 2)
代替Some(1) | Some(2)
。由於以前根本不允許這樣做,所以這不是一個重大變化。
但是,此更改也會影響`macro_rules`宏[14]。這樣的宏可以使用:pat
片段說明符接受模式。當前,:pat
不匹配|
,因為在 Rust 1.53 之前,並非所有模式(在所有嵌套級別)都可以包含|
。接受像A | B
這樣的模式的宏,例如`match!()`[15]使用類似$($_:pat)|+
的東西。因為我們不想破壞任何現有的宏,所以我們沒有將 Rust 1.53.0 中的:pat
的含義更改為包括|
。
相反,我們將在 Rust 2021 中進行該更改。在新版本中,:pat
片段說明符將匹配A | B
。
由於有時仍然希望匹配不帶|
的單個模式變量,因此添加了指定的片段:pat_param
以保留較舊的行為。該名稱旨在表示使用這種模式的主要用於閉合參數。
就是說,到目前為止,我們的工作如期進行,許多困難的部分已經解決,這要歸功於所有為 Rust 2021 做出貢獻的人們。
接下來是什么?
2021 版次的計划里程碑如下:
-
✅ 今天:功能集已最終確定。 -
🚧 5月17日:在 Nightly 中完成實現,包括遷移(正在進行中) -
⌛ 6月15日:《版次遷移指南》和其他文檔完成 -
⌛ 7月1日:呼吁進行公開測試 -
⌛ 9月1日:先在 Nightly 中穩定 2021 版次 -
⌛ 10月21日:隨着Rust 1.56.0的發布,穩定 2021 版次。
在撰寫本文時,我們正在按時完成這些截止日期,並且不會預見任何問題。但是,Rust是一個由志願者運行的項目。我們優先考慮在 Rust 上工作的每個人的個人福祉,而不是我們設定的任何截止日期和期望。這可能意味着如果需要的話,會延遲版次的發布,或者放棄一項事實證明過於困難或壓力太大而無法及時完成的功能。
如果您想繼續,可以在 Rust 2021 Edition 電子表格[16]或項目板[17]上跟蹤狀態。
感謝閱讀!
參考資料
The Plan for the Rust 2021 Edition: https://github.com/m-ou-se/blog.rust-lang.org/blob/1cbd1ee944b1c1f3e369ad70f0d8fc4181f0e4f2/posts/2021-04-30-edition-2021.md
[2]“無停滯的穩定性”: https://blog.rust-lang.org/2014/10/30/Stability.html
[3]《版次遷移指南(Edition Migration Guide)》: https://doc.rust-lang.org/edition-guide/
[4]標准庫的 Prelude : https://doc.rust-lang.org/stable/std/prelude/index.html
[5]std::convert::TryInto: https://doc.rust-lang.org/stable/std/convert/trait.TryInto.html
[6]std::convert::TryFrom: https://doc.rust-lang.org/stable/std/convert/trait.TryFrom.html
[7]std::iter::FromIterator: https://doc.rust-lang.org/stable/std/iter/trait.FromIterator.html
[8]可選的新的 Feature 解析器: https://doc.rust-lang.org/cargo/reference/resolver.html#feature-resolver-version-2
[9]Rust 1.51
的公告: https://blog.rust-lang.org/2021/03/25/Rust-1.51.0.html#cargos-new-feature-resolver
閉包(Closure) : https://doc.rust-lang.org/book/ch13-01-closures.html
[11]微妙的驚喜: https://github.com/rust-lang/rfcs/blob/master/text/3007-panic-plan.md
[12]隱式格式參數: https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html
[13]模式(pattern): https://doc.rust-lang.org/stable/reference/patterns.html
[14]macro_rules
宏: https://doc.rust-lang.org/stable/reference/macros-by-example.html
match!()
: https://doc.rust-lang.org/1.51.0/std/macro.matches.html
Rust 2021 Edition 電子表格: https://docs.google.com/spreadsheets/d/1chZ2SL9T444nvU9al1kQ7TJMwC3IVQQV2xIv1HWGQ_k/edit#gid=1034375760
[17]項目板: https://github.com/orgs/rust-lang/projects/7