重新思考 Rust 的函數聲明語法

https://theincredibleholk.org/blog/2023/12/15/rethinking-rusts-function-declaration-syntax/

Rust 的函數聲明語法重新思考

在#t-lang 的討論中,我們就在 Rust 中聲明函數的可能的新語法進行了有趣的討論。
提出了許多很酷的想法,當我仔細考慮它們時,我意識到許多想法可以很好地結合在一起,可以以向後兼容的方式引入,從而為我們提供一些很酷的新功能。
當這些想法在我腦海中仍然新鮮且我對它們感到興奮時,我希望將它們寫在一個地方。

Rust 的頂層函數看起來類似於這樣:

fn foo(x: i32) -> i32 {
x + 1
}

在 Rust 2018 中,我們增加了 async fn:

async fn foo(x: i32) -> i32 {
x + 1
}

雖然這個看起來沒有什麼特別有趣的地方,但一個 async 函數可以讓您在其中使用 await 的能力。
它還秘密地將返回類型從 i32 更改為 impl Future
許多人認為這是一個錯誤,現在由於我們在特性中擁有了 async 函數,所以這開始引起問題,因為沒有方法可以將額外的約束,例如 Send,添加到返回類型。
無論如何,async fn foo 基本上只是一種語法糖,可以被縮寫為:

fn foo(x: i32) -> impl Future {
async { x + 1 }
}

很可能 Rust 將來會增加一大堆在 fn 前面可以添加的新關鍵字。
例如,nightly Rust 剛剛支持了 gen fn 和 async gen fn。
類似地縮寫,這些通過將返回類型封裝在 impl Iterator 或 impl AsyncIterator 中以及將主體封裝在 gen{}或 async gen{}中。

我們可以增加的另一個糖是 try fn,實際上就是今天討論串開始的。
根據我們迄今為止的模式,我們期望能夠寫出以下內容:

try fn foo() -> i32 {
let x = read_number()?;
x
}
並且擴展到:

fn foo() -> impl Try {
try {
let x = read_number()?;
x
}
}
問題在於,我們需要給出 Residual 類型的提示。
明顯的做法是在函數標題中添加類似於 try fn foo() -> i32 throws E 的東西。
但是,如果您曾經看過標準庫中 Try 實現的 Residual 類型,就知道這些看起來可能非常混亂,並且不是特別直觀。
例如,要創建一個返回 Option 的函數,我們需要寫:

try fn foo() -> i32 throws Option {
let x = read_number()?;
x
}
這將給編譯器足夠的信息找到適用 Option 的 Try 實現。
但請注意,我們也可以簡單地寫出 fn foo() -> Option ,這樣更短,您不必弄清楚為什麼我的易出錯函數中有一個 Infallible。

在這一點上,Lukas Wirth 觀察到他們寧願看到對函數的簡化形式,函數體包含一個單獨的表達式。
如果我們這樣做,我們可以將 try fn 寫成:

fn foo() -> Option = try {
let x = read_number()?;
x
}
這樣非常酷。

這也邀請我們重新考慮 async fn。
我們可以改寫成:

fn foo() -> impl Future = async {
let x = read_number().await;
x
}
不錯,但是 impl Future 有點冗長。
我們可以制定一些規則,讓您寫 impl Future ,這實際上是我們通常會用口語表達的方式。
但是,joboet 和 pitaj 指出,我們可以對 Trait -> Type 的形式進行特殊處理,將其簡化為 Trait

因此,如果我們組合一些這些想法,我們可以寫出類似的函數:

fn foo() -> impl Future -> i32 = async {
let x = read_number().await;
x
}

我認為這展示了很大的潛力。
不過,我想更加通用化。
我們可以創建一組屬性,表示與 trait 關鍵字簡寫一起使用的相關類型。
例如,像這樣定義 Future 和 Iterator trait:

trait Future {
#[keyword(return)]
type Output;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll;
}

trait Iterator {
#[keyword(yields)]
type Item;

fn next(&mut self) -> Option;
}

這將使我們將 Future 指定為 Future -> T 和 Iterator 類似。

我們甚至可以組合它們:

trait Coroutine {
#[keyword(yields)]
type Yield;

#[keyword(return)]
type Return;

fn resume(self: Pin<&mut Self>, arg: R) -> CoroutineState;
}

fn coroutine() -> impl Coroutine <()> -> bool yields i32 = || {
yield 42;
true
}

這也能消除對 Fn * trait 的特殊處理,我們可以向用戶公開這些功能,因此庫可以在自己的 trait 中使用這些糖。

在這一點上,我想要退一步,考慮一下普通的 fn 函數。
請注意,以下兩者是等價的:

fn foo() -> i32 {
let number = read_number();
number
}

fn foo() -> i32 = {
let number = read_number();
number
}

一種思維方式是,我們已經使 = 選件。
但是,我希望用一種不同的方式來思考。
假設我們將 = 形式視為標準的函數聲明語法。
然後,如果函數主體由一個單獨的塊構成,我們可以使用壓縮語法。
對於常規的{ }塊,看起來像我們習慣的函數聲明語法。
但是對於帶有關鍵字的塊,比如 async { }或 try { },我們說關鍵字會移到函數標題的最前面。
此外,每個塊都有一個特性 trait 與之相關聯,所以當我們使用塊簡寫進行函數聲明時,我們還會在返回類型周圍包裝一個 impl Trait。

這裡有一些例子:

// async ////////////////////////////////////////

async fn foo() -> i32 = {
let number = read_number().await;
number
}

// 會被縮寫為:

fn foo() = impl Future = async {
let number = read_number().await;
number
}

// gen //////////////////////////////////////////

gen fn foo() -> i32 = {
yield 1;
yield 2;
}

// 會被縮寫為:

fn foo() = impl Iterator = gen {
yield 1;
yield 2;
}

// 假設`Iterator`被定義為:

trait Iterator {
#[keyword(return)]
type Item;
fn next(&mut self) -> Option;
}

// async gen ////////////////////////////////////

async gen fn foo() -> i32 = {
yield 1;
yield 2;
}

// 會被縮寫為:

fn foo() = impl AsyncIterator = async gen {
yield 1;
yield 2;
}

我沒有提及 try,因為它很複雜。
您可能可以做類似於:

try fn foo() -> i32 throws Option = {
let number = read_number()?;
number
}

但對於 try 使用者,他們通常想知道具體類型。
因此,我期望大多數用戶更喜歡被展開的形式:

fn foo() -> Option = try {
let number = read_number()?;
number
}

請注意,“將關鍵字前移到”變換建變換不起作用,因為這個函數返回具體類型,而我在此提出的是 將關鍵字前面始終添加 impl Trait 而不是具體類型。

無論如何,我對這個想法感到非常興奮。
它感覺像一種處理塊、特性和函數之間聯繫的一致方式。
它與我們迄今為止的語法兼容,但在我們目前缺少它的情況下,它給了我們更多表達性。

1
您今天已經可以做不安全函數和常數函數,但它們的展開方式與這裡提出的其他關鍵字不同。↩

2
當然,我今天才開始思考這個問題,寫出了一篇博客文章,所以也許星期一時,我會討厭它。↩

Rust
通過 Eric Holk https://ift.tt/fQ4WrNp
2023 年 12 月 16 日 上午 11:12

via Eric Holk

December 16, 2023 at 11:12AM

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *