當型別引數受型別限制時,這意味著什么Fn?
fn call<F: Fn() -> u64>(f: F) -> u64 {
f()
}
Rust Book 說帶有型別引數的函式是單態的:https ://doc.rust-lang.org/stable/book/ch10-01-syntax.html ? highlight = generic function#performance-of-code-using-泛型。
如果是這種情況,那么我希望call為它實體化的每個新閉包型別生成一個新版本。
根據 Rust 參考,每個閉包型別都是不同的,即使函式體相同:https : //doc.rust-lang.org/reference/types/closure.html。
因此,當我查看該程式的編譯輸出時,與僅使用四個實體化相比,當我call使用1799999999999999999不同的實體化呼叫時,我希望編譯工件會更大F,但事實并非如此。
(1)
// `call` is defined above
fn make_closure(n: u64) -> impl Fn() -> u64 {
move || n
}
fn main() {
let results = (0..17999999999999999999).map(make_closure).map(call);
for r in results {
println!("{}", r)
}
}
那么什么是正確的心理模型fn call<F: Fn() -> u64> 意味著什么?我應該認為沒有代碼膨脹僅僅是一種優化嗎?
但是,當閉包是根據用戶輸入構建時,我很難維持這種心理模型:
(2)
fn main() {
let mut buf = String::new();
std::io::stdin().read_line(&mut buf).unwrap();
let n = buf.trim().parse::<u64>().unwrap();
println!("{}", call(make_closure(n)));
}
那么考慮簽名的call含義的正確方法是什么?
更新
添加更多資訊,以便我們可以從評論中參考它:
(3)
該防銹參考說:
閉包運算式產生一個閉包值,該值具有無法寫出的唯一匿名型別。閉包型別大約相當于一個包含捕獲變數的結構體。
(4) 下面的第一行被 rustc 接受,第二行不被接受:
vec![make_closure(1), make_closure(2)]; // OK
vec![(|| 1), (|| 1)]; // Error: mismatched types
uj5u.com熱心網友回復:
我認為你在這里混合了概念。事實上,Rust 中的每個函式都有自己的型別,每個閉包也是如此,即使它們具有相同的主體。所以在這個例子中:
fn id0(x: u64) -> u64 { x }
fn id1(x: u64) -> u64 { x }
fn main() {
let id2 = || 1;
let id3 = || 1;
}
每次id0,id1,id2并id3有不同的型別,它們都實作Fn() -> u64特質。如果您撰寫call(id0)orcall(id1)您將獲得該函式的單態版本,該函式以靜態方式和直接方式呼叫id0or id1,而無需進行任何間接函式指標呼叫。
由于它們有一個空的捕獲集,它們也可以被強制轉換為非泛型函式型別:fn() -> u64. 但是如果你強制這樣做,那么你就會失去那個單態化:call(id0 as fn() -> u64)并且call(id1 as fn() -> u64)都呼叫call()函式的同一個實體化,使用給定的指標間接呼叫內部閉包。
然后,在您的示例中:
fn make_closure(n: u64) -> impl Fn() -> u64 {
move || n
}
只有一個關閉。你可以用任何數字呼叫這個函式,這個數字會被閉包捕獲,但這不會改變回傳值的型別。該型別在編譯時確定,始終相同。
如果您使用常量泛型引數,則另一件事是:
fn make_closure_gen<const N: u64>() -> impl Fn() -> u64 {
|| N
}
現在對于每個N你都有一個泛型函式的實體化,每個都有不同的回傳型別。但是您不能創建一個實體化 1000000 個這些函式的運行時回圈,因為它N必須是常量。(但是,您可以嘗試編譯時回圈)。
無論如何,即使您設法為同一函式創建數百萬個實體化,如果編譯器檢測到多個函式的內部代碼相同,它也可以將它們合并為一個(有時呼叫代碼重復資料洗掉),以便最終可執行檔案不會爆炸。詳細資訊因編譯器的版本而異,甚至可能取決于所使用的聯結器。它甚至可以應用于非泛型函式!
uj5u.com熱心網友回復:
就像普通型別一樣,閉包型別也可以包含多個值。move || n具有相同的型別,不管的值是什么n(閉包型別由閉包的主體決定,而不是捕獲的值是什么)。因此,17999999999999999999盡管具有不同的捕獲值 ,但您的閉包都是相同的型別n,因此只需要一個call. 如果您實際上有 2 個不同的閉包,如下面的代碼所示:
fn make_closure(n: u64) -> impl Fn() -> u64 {
move || n
}
fn make_closure2(n: u64) -> impl Fn() -> u64 {
move || n 1
}
fn call<F: Fn() -> u64>(f: F) -> u64 {
f()
}
pub fn main() {
call(make_closure(5));
call(make_closure2(5));
}
example::call在反編譯中可以看到兩個不同的版本。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/401139.html
下一篇:Java中的泛型無效
