le mie informazioni di contatto
Posta[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Ruggine Una chiusura è una funzione anonima che può essere salvata in una variabile o passata come argomento ad altre funzioni. È possibile creare una chiusura in un posto e quindi eseguire operazioni di chiusura in un contesto diverso. A differenza delle funzioni, le chiusure consentono di acquisire valori nell'ambito in cui sono definiti.
Un iteratore è responsabile della logica di attraversare ogni elemento della sequenza e di determinare quando termina la sequenza. Quando si utilizzano gli iteratori, non è necessario reimplementare questa logica.
Chiusura, una struttura simile a una funzione che può essere memorizzata in una variabile.
In Rust, le chiusure sono funzioni anonime che catturano variabili dall'ambiente esterno. Il comportamento di acquisizione di una chiusura dipende dai tipi di variabili e dal modo in cui la chiusura utilizza tali variabili.
Cattura per valore (per valore)
Quando una chiusura acquisisce una variabile in base al valore, ne assume la proprietà. Ciò significa che dopo aver creato la chiusura, la variabile originale non può più essere utilizzata.
fn main() {
let text = "Hello".to_string();
// 使用 move 来显式地表示闭包将获取 text 的所有权
let closure = move || println!("{}", text);
// 这里 text 不能被使用,因为其所有权已经被闭包获取
// println!("{}", text); // 这将导致编译错误
closure(); // 打印 "Hello"
}
Cattura per riferimento (per riferimento)
Quando una chiusura cattura una variabile per riferimento, prende in prestito quella variabile. Ciò significa che la variabile originale è ancora disponibile, ma la chiusura può solo prenderla in prestito, non acquisirne la proprietà.
fn main() {
let text = "Hello";
// 闭包通过引用捕获 text
let closure = || println!("{}", text);
// text 仍然可用,因为它没有被移动
println!("{}", text); // 打印 "Hello"
closure(); // 再次打印 "Hello"
}
Cattura mutevole
Una chiusura può acquisire un riferimento mutabile, consentendogli di modificare il valore della variabile originale.
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);
}
Se la chiusura viene rimossa mut
Modifica, compilazione fallisce.
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);
}
Viene visualizzato un messaggio di errore: A causa del prestito di una variabile count
,trasferimento closure
È richiesto il legame mutabile.
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
chiusura come parametro
Le chiusure possono essere utilizzate comePassaggio dei parametriPer altre funzioni, acquisisci variabili nell'ambiente.
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 questo esempio:
square
, che cattura per riferimento main
in funzionenumber
variabile e calcolarne il quadrato.call_closure
funzione, che accetta aFn() -> i32
Le chiusure legate ai tratti accettano parametri, il che significa che la chiusura non accetta parametri e restituisce ai32
tipo di valore.call_closure
funzione e volontàsquare
Le chiusure vengono passate come argomenti.Perchésquare
Le chiusure non hanno parametri, possono essere passate direttamente come parametri acall_closure
。call_closure
La funzione richiama la chiusura e stampa il risultato. Ci sono molte più differenze tra funzioni e chiusure.Le chiusure non sempre richiedono qualcosa di similefn
Specificare i tipi di parametri e restituire valori come funzioni. Le annotazioni di tipo sono richieste nelle funzioni perché fanno parte dell'interfaccia esplicita esposta all'utente. È importante definire rigorosamente queste interfacce per garantire che tutti abbiano una comprensione coerente delle funzioni utilizzate e dei tipi di valori restituiti. Al contrario, le chiusure non vengono utilizzate per tali interfacce esposte: vengono archiviate in variabili e utilizzate senza nominarle o esporle agli utenti della libreria.
Le chiusure sono generalmente brevi e si riferiscono a un contesto ristretto piuttosto che a qualsiasi situazione arbitraria. In questi contesti ristretti, il compilatore può dedurre in modo affidabile i tipi di parametri e valori restituiti, in modo simile a come può dedurre i tipi della maggior parte delle variabili (sebbene vi siano rari casi in cui il compilatore richiede annotazioni sul tipo di chiusura).
Analogamente alle variabili, è possibile aggiungere annotazioni di tipo se desideriamo aumentare la chiarezza e la chiarezza, con lo svantaggio di rendere il codice più dettagliato (rispetto a quanto strettamente necessario).
fn main() {
let a = 100;
let add_one = |x: i32| -> i32 { x + 1 };
let b = add_one(a);
println!("{}", b);
}
Con le annotazioni di tipo, la sintassi delle chiusure è più simile alle funzioni. Ecco un confronto verticale tra la definizione di una funzione che aggiunge uno al suo argomento e la sintassi di chiusura che ha lo stesso comportamento. Qui sono stati aggiunti alcuni spazi per allineare le parti corrispondenti. Questo mostra come la sintassi di chiusura sia simile alla sintassi delle funzioni, ad eccezione dell'uso di pipe e di alcune sintassi opzionali:
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 ;
Quindi l'esempio sopra può essere semplificato in:
fn main() {
let a = 100;
let add_one = |x| x + 1;
let b = add_one(a);
println!("{}", b);
}
Il compilatore deduce un tipo concreto per ogni parametro e valore restituito nella definizione di chiusura.
Diamo un’occhiata a un altro esempio:
fn main() {
let a = 100i32;
let a1 = 100f32;
let closure = |x| x;
let b = closure(a);
let b1 = closure(a1);
println!("{}", b);
println!("{}", b1);
}
Tieni presente che questa definizione di chiusura non aggiunge alcuna annotazione di tipo, quindi possiamo chiamare questa chiusura con qualsiasi tipo. Ma se provi a chiamare la chiusura due volte, la prima volta usando i32 e la seconda volta usando f32, verrà segnalato un errore:Previsto, poiché la chiusura era stata precedentemente chiamata con un argomento di tipo "i32".
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
Una volta che una chiusura acquisisce un riferimento o la proprietà di un valore nell'ambiente in cui è definita (influenzando così ciò che, se presente, viene spostato nella chiusura), il codice all'interno del corpo della chiusura definisce il riferimento successivamente quando la chiusura viene valutato. O come viene manipolato il valore (che influisce su cosa, se non altro, viene rimosso dalla chiusura). Il corpo della chiusura può eseguire una delle seguenti operazioni: spostare un valore acquisito fuori dalla chiusura, modificare il valore acquisito, non spostare né modificare il valore oppure non acquisire mai il valore dall'ambiente in primo luogo.
Il modo in cui una chiusura cattura e gestisce i valori nell'ambiente influenza i tratti implementati dalla chiusura. I tratti sono un modo per le funzioni e le strutture di specificare i tipi di chiusure che possono utilizzare.A seconda di come il corpo della chiusura gestisce i valori, la chiusura ne implementa automaticamente e in modo incrementale uno, due o treFn
tratto.
FnOnce
Si applica alle chiusure che possono essere chiamate una volta. Tutte le chiusure implementano almeno questa caratteristica perché tutte le chiusure possono essere chiamate.Una chiusura che sposta i valori catturati fuori dal corpo della chiusura viene solo implementataFnOnce
tratto perché può essere chiamato solo una volta.FnMut
Adatto per chiusure che non spostano il valore catturato fuori dal corpo della chiusura, ma possono modificare il valore catturato. Questo tipo di chiusura può essere richiamata più volte.Fn
Si applica alle chiusure che non spostano il valore catturato fuori dal corpo della chiusura né modificano il valore catturato e ovviamente includono chiusure che non catturano il valore dall'ambiente. Tali chiusure possono essere chiamate più volte senza modificare l'ambiente, il che è importante negli scenari in cui le chiusure vengono chiamate più volte contemporaneamente.Ecco un esempio che mostra come usarlo in una funzione FnOnce
come vincolo generico e garantire che la chiusura venga chiamata solo una volta:
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);
}
risultato dell'operazione
The value is: 42
Result of the closure: 42
In questo esempio definiamo una funzione generica call_once
, che accetta un tipo di F
Parametrif
,In F
Deve essere raggiuntoFnOnce
tratto.questo significaf
è una chiusura che accetta parametri vuoti e restituisce il tipoT
il risultato di.
esistere main
Nella funzione creiamo una chiusuraconsume
, che cattura value
di proprietà.Quindi chiamiamocall_once
funzione, passataconsume
Chiusura.call_once
La funzione chiama la chiusura e restituisce il risultato.Perchéconsume
La chiusura è stata consumatavalue
, prova a chiamare di nuovo call_once
Passare nella stessa chiusura comporterà un errore di compilazione.
Ecco un utilizzo FnMut
Esempi di tratti.
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);
}
risultato dell'operazione
Result after applying increment: 5
Result after applying increment again: 15
In questo esempio,apply_mut
La funzione accetta aF
riferimento mutabile di tipo&mut F
come parametro (quindi,increment
La chiusura non verrà spostata e potrà essere utilizzata più volte) e ai32
parametri di tiponum
。where
clausola specificaF
Deve essere un'implementazioneFnMut
chiusura, che accetta ai32
parametri di tipo e restituisce un tipo diT
il risultato di.main
Nella funzione viene creata una chiusura modificabileincrement
, che modifica una variabile catturata count
, quindi utilizzare apply_mut
funzione per chiamare questa chiusura.ogni chiamataapply_mut
Useràincrement
chiusura eincrement
Le chiusure verranno modificatecount
valore.
Nella ruggine,Fn
La caratteristica significa che una chiusura non assume la proprietà delle variabili che cattura, né le modifica.L'esempio seguente mostra come definire un'accettazioneFn
Chiusura come argomento di una funzione e utilizzo in scenari simultanei.
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();
}
}
Non tutte le esecuzioni sono in questo ordine. Quale thread viene eseguito per primo dipende dalla pianificazione corrente del sistema.
risultato dell'operazione
Thread 1 result: 84
Thread 2 result: 84
Thread 0 result: 84
Thread 4 result: 84
Thread 3 result: 84
In questo esempio:
call_once
funzione, che accetta un parametro genericoF
,E F
deve essere soddisfattoFn(i32) -> T
limiti dei tratti.questo significaF
è una chiusura che accetta ai32
parametri di tipo e restituisce un tipo diT
il risultato di.main
Nella funzione definiamo una chiusura semplicedouble
, che accetta a i32
parametri di tipox
e ritornox * 2
il risultato di.map
Vengono creati 5 thread e ogni thread viene copiatodouble
Chiudi e chiamalo in un nuovo threadcall_once
funzione, passando la chiusura come argomentocall_once
。call_once
Viene chiamata la funzione, viene eseguita la chiusura e il risultato viene stampato.join
Il metodo attende il completamento di tutti i thread.Gli iteratori consentono di eseguire determinate elaborazioni sugli elementi di una sequenza. Successivamente, introduciamo principalmente l'uso degli iteratori per elaborare sequenze di elementi e il confronto delle prestazioni degli iteratori loop VS. In Rust, gli iteratori sono pigri, il che significa che nessuna operazione viene eseguita finché non viene chiamato il metodo che consuma l'iteratore.
Tutti gli iteratori implementano una funzione chiamata Iterator
è definito nel tratto della libreria standard. La definizione di questo tratto è la seguente:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// 此处省略了方法的默认实现
}
type Item
ESelf::Item
, definiscono il tipo associato al tratto.Ma per ora tutto ciò che devi sapere è che questo codice mostra come implementareIterator
I tratti richiedono che aItem
tipo, questoItem
il tipo è usato comenext
Il tipo di valore restituito del metodo. in altre parole,Item
Il tipo sarà il tipo dell'elemento restituito dall'iteratore.
next
SÌIterator
Gli unici implementatori del metodo devono definire.next
Restituisce un elemento alla volta nell'iteratore, incapsulato inSome
, quando l'iteratore termina, ritornaNone
。
Iterator
i tratti hanno una serie di metodi diversi implementati per impostazione predefinita forniti dalla libreria standard;Iterator
Tutti questi metodi si trovano nella documentazione API della libreria standard del tratto.Alcuni metodi richiamano la loro definizionenext
metodo, motivo per cui nell'implementazioneIterator
caratteristica è necessaria per essere implementatanext
ragioni di metodo.
Queste chiamate next
I metodi dei metodi sono chiamati adattatori di consumo perché la loro chiamata consuma gli iteratori.
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);
}
Se lo usi di nuovo numbers_iter
Verrà segnalato un errore.sum
Il metodo assume la proprietà dell'iteratore e lo chiama ripetutamentenext
per scorrere l'iteratore, consumando così l'iteratore. Mentre esegue l'iterazione su ciascun elemento, aggiunge ogni elemento a una somma e restituisce la somma al termine dell'iterazione.trasferimentosum
Non è più consentito l'usonumbers_iter
, perché chiama sum
quando assume la proprietà dell'iteratore.
Iterator
Un'altra classe di metodi definiti nei tratti, chiamati adattatori iteratori, ci consente di modificare l'iteratore corrente in un iteratore di tipo diverso. È possibile richiamare più adattatori iteratori in una catena. Tuttavia, poiché tutti gli iteratori sono pigri, è necessario chiamare un metodo dell'adattatore che utilizza per ottenere i risultati della chiamata dell'adattatore dell'iteratore.
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]);
}
Il metodo Collect utilizza l'iteratore e raccoglie i risultati in una struttura dati.Perchémap
Ottiene una chiusura che consente di specificare le operazioni che si desidera eseguire su ciascun elemento che attraversa.
Molti adattatori iteratori accettano chiusure come parametri e solitamente la chiusura specificata come parametro dell'adattatore iteratore sarà la chiusura che cattura il relativo ambiente.
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);
}
risultato dell'operazione
The filtered and squared numbers are: [4, 16]
In questo esempio,filter
Emap
Entrambi sono adattatori iteratori, accettano chiusure come parametri. Queste chiusure catturano il loro ambiente, ovvero gli elementi nell'iteratore.esisterefilter
Nell'adattatore, chiusura|&x| x % 2 == 0
Utilizzato per verificare se l'elemento è un numero pari, in tal caso l'elemento verrà mantenuto.esisteremap
Nell'adattatore, chiusura|x| x * x
Utilizzato per quadrare ciascun elemento. Poiché questi adattatori sono consumer, consumano l'iteratore originale e pertanto non possono essere riutilizzati. infine,collect
L'adattatore raccoglie gli elementi elaborati in un nuovoVec
mezzo.
Gli iteratori, in quanto astrazione di alto livello, vengono compilati nel codice con all'incirca le stesse prestazioni del codice di basso livello scritto a mano. Gli iteratori sono una delle astrazioni a costo zero di Rust, il che significa che l'astrazione non introduce alcun sovraccarico di runtime.
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);
}
risultato dell'operazione
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
Come puoi vedere da questo esempio, gli iteratori sono ancora leggermente più lenti.