2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Rost Ein Abschluss ist eine anonyme Funktion, die in einer Variablen gespeichert oder als Argument an andere Funktionen übergeben werden kann. Sie können einen Abschluss an einer Stelle erstellen und dann Abschlussvorgänge in einem anderen Kontext ausführen. Im Gegensatz zu Funktionen ermöglichen Abschlüsse die Erfassung von Werten innerhalb des Bereichs, in dem sie definiert sind.
Ein Iterator ist für die Logik verantwortlich, jedes Element in der Sequenz zu durchlaufen und zu bestimmen, wann die Sequenz endet. Wenn wir Iteratoren verwenden, müssen wir diese Logik nicht erneut implementieren.
Abschluss, eine funktionsähnliche Struktur, die in einer Variablen gespeichert werden kann.
In Rust sind Abschlüsse anonyme Funktionen, die Variablen aus der externen Umgebung erfassen. Das Erfassungsverhalten eines Abschlusses hängt von den Variablentypen und davon ab, wie der Abschluss diese Variablen verwendet.
Erfassung nach Wert (Nach Wert)
Wenn ein Abschluss eine Variable nach Wert erfasst, übernimmt er den Besitz der Variablen. Dies bedeutet, dass nach der Erstellung des Abschlusses die ursprüngliche Variable nicht mehr verwendet werden kann.
fn main() {
let text = "Hello".to_string();
// 使用 move 来显式地表示闭包将获取 text 的所有权
let closure = move || println!("{}", text);
// 这里 text 不能被使用,因为其所有权已经被闭包获取
// println!("{}", text); // 这将导致编译错误
closure(); // 打印 "Hello"
}
Erfassung durch Referenz (By Reference)
Wenn ein Abschluss eine Variable als Referenz erfasst, leiht er diese Variable aus. Dies bedeutet, dass die ursprüngliche Variable weiterhin verfügbar ist, der Abschluss sie jedoch nur ausleihen und nicht in Besitz nehmen kann.
fn main() {
let text = "Hello";
// 闭包通过引用捕获 text
let closure = || println!("{}", text);
// text 仍然可用,因为它没有被移动
println!("{}", text); // 打印 "Hello"
closure(); // 再次打印 "Hello"
}
Veränderbare Erfassung
Ein Abschluss kann eine veränderliche Referenz erfassen und so den Wert der ursprünglichen Variablen ändern.
fn main() {
let mut count = 0;
// 闭包通过可变引用捕获 count
let mut closure = || {
count += 1; // 修改 count 的值
println!("Count: {}", count);
};
closure(); // 打印 "Count: 1"
closure(); // 打印 "Count: 2"
// count 的值现在是 2
println!("Final count: {}", count);
}
Wenn der Verschluss entfernt wird mut
Änderung, Kompilierung schlägt fehl.
fn main() {
let mut count = 0;
// 闭包通过可变引用捕获 count
let closure = || {
count += 1; // 修改 count 的值
println!("Count: {}", count);
};
closure(); // 打印 "Count: 1"
closure(); // 打印 "Count: 2"
// count 的值现在是 2
println!("Final count: {}", count);
}
Fehlermeldung: Aufgrund der Ausleihe einer Variablen count
,überweisen closure
Eine veränderliche Bindung ist erforderlich.
Compiling playground v0.0.1 (/playground)
error[E0596]: cannot borrow `closure` as mutable, as it is not declared as mutable
--> src/main.rs:4:9
|
4 | let closure = || {
| ^^^^^^^ not mutable
5 | count += 1; // 修改 count 的值
| ----- calling `closure` requires mutable binding due to mutable borrow of `count`
...
8 | closure(); // 打印 "Count: 1"
| ------- cannot borrow as mutable
9 | closure(); // 打印 "Count: 2"
| ------- cannot borrow as mutable
|
help: consider changing this to be mutable
|
4 | let mut closure = || {
| +++
For more information about this error, try `rustc --explain E0596`.
error: could not compile `playground` (bin "playground") due to 1 previous error
Schließung als Argument
Verschlüsse können als verwendet werdenParameterübergabeErfassen Sie für andere Funktionen Variablen in der Umgebung.
fn main() {
// 创建一个整数变量
let number = 10;
// 创建一个闭包,它接受一个 i32 类型的参数并返回其平方
// 这里使用 || 表示这是一个闭包
let square = || number * number;
// 定义一个函数,它接受一个闭包作为参数并调用它
// 闭包作为参数需要指定其类型,这里使用 || -> i32 表示闭包没有参数并返回 i32 类型的值
fn call_closure<F>(f: F)
where
F: Fn() -> i32, // 使用 trait bound 指定闭包的签名
{
// 调用闭包并打印结果
let result = f();
println!("The result is: {}", result);
}
// 调用 `call_closure` 函数,并将闭包 `square` 作为参数传递
// 由于闭包 `square` 没有参数,我们可以直接传递
call_closure(square);
}
In diesem Beispiel:
square
, die durch Referenz erfasst main
in Funktionnumber
Variable und berechnen Sie ihr Quadrat.call_closure
Funktion, die a akzeptiertFn() -> i32
Merkmalsgebundene Abschlüsse akzeptieren Parameter, was bedeutet, dass der Abschluss keine Parameter akzeptiert und a zurückgibti32
Art des Wertes.call_closure
Funktion und Willesquare
Abschlüsse werden als Argumente übergeben.Weilsquare
Verschlüsse haben keine Parameter, sie können direkt als Parameter übergeben werdencall_closure
。call_closure
Die Funktion ruft den Abschluss auf und gibt das Ergebnis aus. Es gibt noch viele weitere Unterschiede zwischen Funktionen und Abschlüssen.Schließungen erfordern nicht immer so etwasfn
Geben Sie die Parametertypen und Rückgabewerte wie Funktionen an. Typanmerkungen sind in Funktionen erforderlich, da sie Teil der expliziten Schnittstelle sind, die dem Benutzer angezeigt wird. Es ist wichtig, diese Schnittstellen streng zu definieren, um sicherzustellen, dass jeder ein einheitliches Verständnis der verwendeten Funktionen und der Arten von Rückgabewerten hat. Im Gegensatz dazu werden für solche exponierten Schnittstellen keine Abschlüsse verwendet: Sie werden in Variablen gespeichert und verwendet, ohne sie zu benennen oder den Benutzern der Bibliothek zugänglich zu machen.
Abschlüsse sind in der Regel kurz und beziehen sich eher auf einen engen Kontext als auf eine beliebige Situation. In diesen eingeschränkten Kontexten kann der Compiler zuverlässig auf die Typen von Parametern und Rückgabewerten schließen, ähnlich wie er auf die Typen der meisten Variablen schließen kann (obwohl es seltene Fälle gibt, in denen der Compiler Abschlusstypanmerkungen benötigt).
Ähnlich wie bei Variablen können Typanmerkungen hinzugefügt werden, wenn wir die Klarheit und Klarheit erhöhen möchten, mit dem Nachteil, dass der Code ausführlicher wird (statt unbedingt erforderlich).
fn main() {
let a = 100;
let add_one = |x: i32| -> i32 { x + 1 };
let b = add_one(a);
println!("{}", b);
}
Bei Typanmerkungen ähnelt die Syntax von Abschlüssen eher der von Funktionen. Hier ist ein vertikaler Vergleich der Definition einer Funktion, die eins zu ihrem Argument hinzufügt, und der Abschlusssyntax, die das gleiche Verhalten aufweist. Hier wurden einige Leerzeichen hinzugefügt, um die entsprechenden Teile auszurichten. Dies zeigt, wie ähnlich die Abschlusssyntax der Funktionssyntax ist, mit Ausnahme der Verwendung von Pipes und einiger optionaler Syntax:
fn add_one_v1 (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;
Das obige Beispiel kann also wie folgt vereinfacht werden:
fn main() {
let a = 100;
let add_one = |x| x + 1;
let b = add_one(a);
println!("{}", b);
}
Der Compiler leitet für jeden Parameter und Rückgabewert in der Abschlussdefinition einen konkreten Typ ab.
Schauen wir uns ein anderes Beispiel an:
fn main() {
let a = 100i32;
let a1 = 100f32;
let closure = |x| x;
let b = closure(a);
let b1 = closure(a1);
println!("{}", b);
println!("{}", b1);
}
Beachten Sie, dass diese Abschlussdefinition keine Typanmerkungen hinzufügt, sodass wir diesen Abschluss mit jedem Typ aufrufen können. Wenn Sie jedoch versuchen, den Abschluss zweimal aufzurufen, das erste Mal mit i32 und das zweite Mal mit f32, wird ein Fehler gemeldet:Erwartet, da der Abschluss zuvor mit einem Argument vom Typ „i32“ aufgerufen wurde.
Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
--> src/main.rs:6:22
|
6 | let b1 = closure(a1);
| ------- ^^ expected `i32`, found `f32`
| |
| arguments to this function are incorrect
|
note: expected because the closure was earlier called with an argument of type `i32`
--> src/main.rs:5:21
|
5 | let b = closure(a);
| ------- ^ expected because this argument is of type `i32`
| |
| in this closure call
note: closure parameter defined here
--> src/main.rs:4:20
|
4 | let closure = |x| x;
| ^
For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` (bin "playground") due to 1 previous error
Sobald ein Abschluss einen Verweis auf oder den Besitz eines Werts in der Umgebung, in der er definiert ist, erfasst (und somit beeinflusst, was ggf. in den Abschluss verschoben wird), definiert der Code im Abschlusskörper den Verweis später beim Abschluss ausgewertet wird oder wie der Wert manipuliert wird (was sich darauf auswirkt, was, wenn überhaupt, aus dem Abschluss entfernt wird). Der Abschlusskörper kann Folgendes tun: einen erfassten Wert aus dem Abschluss verschieben, den erfassten Wert ändern, den Wert weder verschieben noch ändern oder den Wert überhaupt nicht aus der Umgebung erfassen.
Die Art und Weise, wie ein Abschluss Werte in der Umgebung erfasst und verarbeitet, wirkt sich auf die Merkmale aus, die der Abschluss implementiert. Merkmale sind eine Möglichkeit für Funktionen und Strukturen, die Arten von Abschlüssen anzugeben, die sie verwenden können.Abhängig davon, wie der Verschlusskörper mit den Werten umgeht, implementiert der Verschluss automatisch und inkrementell eins, zwei oder dreiFn
Eigenschaft.
FnOnce
Gilt für Abschlüsse, die einmal aufgerufen werden können. Alle Abschlüsse implementieren mindestens diese Eigenschaft, da alle Abschlüsse aufgerufen werden können.Ein Verschluss, der erfasste Werte aus dem Verschlusskörper verschiebt, implementiert nurFnOnce
Merkmal, da es nur einmal aufgerufen werden kann.FnMut
Geeignet für Verschlüsse, die den erfassten Wert nicht aus dem Verschlusskörper verschieben, den erfassten Wert jedoch möglicherweise ändern. Diese Art der Schließung kann mehrfach aufgerufen werden.Fn
Gilt für Abschlüsse, die weder den erfassten Wert aus dem Verschlusskörper verschieben noch den erfassten Wert ändern, und schließt natürlich Abschlüsse ein, die den Wert nicht aus der Umgebung erfassen. Solche Abschlüsse können mehrmals aufgerufen werden, ohne ihre Umgebung zu ändern, was in Szenarien wichtig ist, in denen Abschlüsse mehrmals gleichzeitig aufgerufen werden.Hier ist ein Beispiel, das zeigt, wie es in einer Funktion verwendet wird FnOnce
als generische Einschränkung und stellen Sie sicher, dass der Abschluss nur einmal aufgerufen wird:
fn call_once<F, T>(f: F) -> T
where
F: FnOnce() -> T, // 约束 F 为 FnOnce trait,意味着它接受一个空参数并返回 T 类型
{
f() // 调用闭包并返回结果
}
fn main() {
// 创建一个闭包,它捕获了 `value` 的所有权
let value = 42;
let consume = move || {
let result = value; // 移动 `value`
println!("The value is: {}", result);
result // 返回结果
};
// 调用 `call_once` 函数,传入闭包
let result = call_once(consume);
println!("Result of the closure: {}", result);
// 尝试再次使用 `consume` 将会导致编译错误,因为它已经消耗了 `value`
// call_once(consume);
}
Operationsergebnis
The value is: 42
Result of the closure: 42
In diesem Beispiel definieren wir eine generische Funktion call_once
, das eine Art von akzeptiert F
Parameterf
,In F
Muss erreicht werdenFnOnce
Eigenschaft.das heisstf
ist ein Abschluss, der leere Parameter akzeptiert und den Typ zurückgibtT
das Ergebnis von.
existieren main
In der Funktion erstellen wir einen Abschlussconsume
, was einfängt value
des Eigentums.Dann rufen wir ancall_once
Funktion, übergebenconsume
Schließung.call_once
Die Funktion ruft den Abschluss auf und gibt sein Ergebnis zurück.Weilconsume
Der Verschluss ist verbrauchtvalue
, versuchen Sie es noch einmal call_once
Die Übergabe desselben Abschlusses führt zu einem Kompilierungsfehler.
Hier ist eine Verwendung FnMut
Beispiele für Merkmale.
fn apply_mut<F, T>(func: &mut F, num: i32) -> T
where
F: FnMut(i32) -> T, // F 是一个可变闭包,接受一个 i32 类型的参数并返回类型为 T 的结果
{
func(num) // 调用闭包并返回结果
}
fn main() {
let mut count = 0;
// 创建一个闭包,它接受一个 i32 类型的参数并将其加到 count 上
let mut increment = |num: i32| -> i32 {
count += num;
count
};
// 使用 apply_mut 函数和 increment 闭包的引用
let result: i32 = apply_mut(&mut increment, 5);
println!("Result after applying increment: {}", result);
// 再次使用 apply_mut 函数和 increment 闭包的引用
let result: i32 = apply_mut(&mut increment, 10);
println!("Result after applying increment again: {}", result);
}
Operationsergebnis
Result after applying increment: 5
Result after applying increment again: 15
In diesem Beispiel,apply_mut
Die Funktion akzeptiert aF
veränderliche Referenz des Typs&mut F
als Parameter (also,increment
Der Verschluss lässt sich nicht verschieben und kann mehrfach verwendet werden) und ai32
Typparameternum
。where
Klausel gibt anF
Muss eine Implementierung seinFnMut
Schließung, die a akzeptierti32
Geben Sie Parameter ein und geben Sie einen Typ von zurückT
das Ergebnis von.main
In der Funktion wird ein veränderlicher Abschluss erstelltincrement
, wodurch eine erfasste Variable geändert wird count
, dann benutze apply_mut
Funktion zum Aufrufen dieses Abschlusses.jeden Anrufapply_mut
Wird benutzenincrement
Schließung undincrement
Die Schließungen werden geändertcount
Wert.
In Rust,Fn
Das Merkmal bedeutet, dass ein Abschluss weder den Besitz der Variablen übernimmt, die er erfasst, noch diese Variablen mutiert.Das folgende Beispiel zeigt, wie ein Accept definiert wirdFn
Abschluss als Argument für eine Funktion und Verwendung in gleichzeitigen Szenarien.
use std::thread;
// 定义一个函数,它接受一个实现了 Fn(i32) -> i32 的闭包,并调用它
fn call_once<F, T>(func: F) -> T
where
F: Fn(i32) -> T, // 指定 F 是一个接受 i32 并返回 T 的 Fn 闭包
{
let result = func(42); // 调用闭包,传入一个 i32 类型的值
result // 返回闭包的执行结果
}
fn main() {
// 定义一个简单的 Fn 闭包,它接受一个 i32 类型的参数并返回两倍的该值
let double = |x: i32| -> i32 {
x * 2
};
// 创建多个线程,每个线程都使用相同的闭包
let handles: Vec<_> = (0..5).map(|i| {
let func = double; // 闭包可以被复制,因为它是 Fn 类型的
thread::spawn(move || {
let result = call_once(func); // 调用 call_once 函数,并传入闭包
println!("Thread {} result: {}", i, result); // 打印结果
})
}).collect();
// 等待所有线程完成
for handle in handles {
handle.join().unwrap();
}
}
Nicht jede Ausführung erfolgt in dieser Reihenfolge. Welcher Thread zuerst ausgeführt wird, hängt von der aktuellen Planung des Systems ab.
Operationsergebnis
Thread 1 result: 84
Thread 2 result: 84
Thread 0 result: 84
Thread 4 result: 84
Thread 3 result: 84
In diesem Beispiel:
call_once
Funktion, die einen generischen Parameter akzeptiertF
,Und F
muss zufrieden seinFn(i32) -> T
Merkmalsgrenzen.das heisstF
ist ein Abschluss, der a akzeptierti32
Geben Sie Parameter ein und geben Sie einen Typ von zurückT
das Ergebnis von.main
In der Funktion definieren wir einen einfachen Abschlussdouble
, das a akzeptiert i32
Typparameterx
und zurückx * 2
das Ergebnis von.map
Es werden 5 Threads erstellt und jeder Thread wird kopiertdouble
Schließung und Aufruf in einem neuen Threadcall_once
Funktion und übergibt den Abschluss als Argument ancall_once
。call_once
Die Funktion wird aufgerufen, der Abschluss ausgeführt und das Ergebnis ausgegeben.join
Die Methode wartet, bis alle Threads abgeschlossen sind.Mit Iteratoren können Sie bestimmte Verarbeitungen an den Elementen einer Sequenz durchführen. Als nächstes stellen wir hauptsächlich die Verwendung von Iteratoren zur Verarbeitung von Elementsequenzen und den Leistungsvergleich von Schleifen vs. Iteratoren vor. In Rust sind Iteratoren faul, was bedeutet, dass keine Operation ausgeführt wird, bis die Methode aufgerufen wird, die den Iterator verbraucht.
Alle Iteratoren implementieren eine aufgerufene Funktion Iterator
ist im Merkmal der Standardbibliothek definiert. Die Definition dieses Merkmals sieht folgendermaßen aus:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// 此处省略了方法的默认实现
}
type Item
UndSelf::Item
, sie definieren den zugeordneten Typ des Merkmals.Im Moment müssen Sie jedoch nur wissen, dass dieser Code die Implementierung zeigtIterator
Merkmale erfordern, dass aItem
Geben Sie Folgendes einItem
Typ wird verwendet alsnext
Der Rückgabewerttyp der Methode. mit anderen Worten,Item
Der Typ ist der Typ des vom Iterator zurückgegebenen Elements.
next
JaIterator
Die einzigen Methodenimplementierer müssen definieren.next
Gibt jeweils ein Element im Iterator zurück, gekapselt inSome
, wenn der Iterator endet, kehrt er zurückNone
。
Iterator
Merkmale verfügen über eine Reihe verschiedener Methoden, die standardmäßig von der Standardbibliothek bereitgestellt werdenIterator
Alle diese Methoden finden Sie in der API-Dokumentation der Standardbibliothek des Merkmals.Einige Methoden rufen ihre Definition aufnext
Methode, weshalb in der UmsetzungIterator
Merkmal muss umgesetzt werdennext
methodische Gründe.
Diese Anrufe next
Die Methoden von Methoden werden als konsumierende Adapter bezeichnet, da ihr Aufruf Iteratoren verbraucht.
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// 使用into_iter()将Vec转换为消费迭代器
let numbers_iter = numbers.iter();
let mut sum: i32 = numbers_iter
// 使用sum适配器计算迭代器中所有元素的总和
.sum();
// 打印总和
println!("The sum is: {}", sum);
}
Wenn Sie es erneut verwenden numbers_iter
Es wird ein Fehler gemeldet.sum
Die Methode übernimmt den Besitz des Iterators und ruft ihn wiederholt aufnext
um über den Iterator zu iterieren und so den Iterator zu verbrauchen. Beim Durchlaufen jedes Elements addiert es jedes Element zu einer Summe und gibt die Summe zurück, wenn die Iteration abgeschlossen ist.überweisensum
Darf nicht mehr verwendet werdennumbers_iter
, weil anrufen sum
wenn es den Besitz des Iterators übernimmt.
Iterator
Eine weitere in Traits definierte Methodenklasse, sogenannte Iterator-Adapter, ermöglicht es uns, den aktuellen Iterator in einen Iterator eines anderen Typs umzuwandeln. Mehrere Iteratoradapter können in einer Kette aufgerufen werden. Da jedoch alle Iteratoren faul sind, muss eine verbrauchende Adaptermethode aufgerufen werden, um die Ergebnisse des Iterator-Adapteraufrufs zu erhalten.
fn main() {
let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x * x).collect();
assert_eq!(v2, vec![1, 4, 9]);
}
Die Collect-Methode verbraucht den Iterator und sammelt die Ergebnisse in einer Datenstruktur.Weilmap
Ruft einen Abschluss ab, der es Ihnen ermöglicht, alle Operationen anzugeben, die Sie für jedes durchlaufene Element ausführen möchten.
Viele Iteratoradapter akzeptieren Abschlüsse als Parameter, und normalerweise ist der als Iteratoradapterparameter angegebene Abschluss der Abschluss, der seine Umgebung erfasst.
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// 使用into_iter()将Vec转换为消费迭代器
let filtered_and_squared: Vec<i32> = numbers.into_iter()
// 使用filter适配器过滤元素,接受一个闭包作为参数
// 闭包捕获其环境,这里指numbers的元素
.filter(|&x| x % 2 == 0) // 保留偶数
// 使用map适配器对过滤后的元素进行变换,也接受一个闭包
.map(|x| x * x) // 对每个元素进行平方
// 使用collect适配器将结果收集到一个新的Vec中
.collect();
// 打印过滤和平方后的结果
println!("The filtered and squared numbers are: {:?}", filtered_and_squared);
}
Operationsergebnis
The filtered and squared numbers are: [4, 16]
In diesem Beispiel,filter
Undmap
Beide sind Iterator-Adapter, sie akzeptieren Abschlüsse als Parameter. Diese Abschlüsse erfassen ihre Umgebung, also die Elemente im Iterator.existierenfilter
Im Adapter, Verschluss|&x| x % 2 == 0
Wird verwendet, um zu prüfen, ob das Element eine gerade Zahl ist. Wenn ja, wird das Element beibehalten.existierenmap
Im Adapter, Verschluss|x| x * x
Wird verwendet, um jedes Element zu quadrieren. Da es sich bei diesen Adaptern um Consumer-Adapter handelt, verbrauchen sie den ursprünglichen Iterator und können daher nicht erneut verwendet werden. zu guter Letzt,collect
Der Adapter sammelt die verarbeiteten Elemente in einem neuenVec
Mitte.
Iteratoren werden als High-Level-Abstraktion mit ungefähr der gleichen Leistung in Code kompiliert wie handgeschriebener Low-Level-Code. Iteratoren sind eine der kostenlosen Abstraktionen von Rust, was bedeutet, dass die Abstraktion keinen Laufzeit-Overhead mit sich bringt.
fn main() {
let numbers1 = (0..1000000).collect::<Vec<i64>>();
let numbers2 = (0..1000000).collect::<Vec<i64>>();
let mut sum1 = 0i64;
let mut sum2 = 0i64;
// 测量for循环的性能
let start = std::time::Instant::now();
for val in numbers1 {
sum1 += val;
}
let loop_duration = start.elapsed();
// 测量迭代器的性能
let start = std::time::Instant::now();
for val in numbers2.iter() {
sum2 += val;
}
let iterator_duration = start.elapsed();
println!("Iterator took: {:?}", iterator_duration);
println!("For loop took: {:?}", loop_duration);
}
Operationsergebnis
Iterator took: 12.796012ms
For loop took: 11.559512ms
Iterator took: 12.817732ms
For loop took: 11.687655ms
Iterator took: 12.75484ms
For loop took: 11.89468ms
Iterator took: 12.812022ms
For loop took: 11.785106ms
Iterator took: 12.78293ms
For loop took: 11.528941ms
Wie Sie diesem Beispiel entnehmen können, sind Iteratoren immer noch etwas langsamer.