プライベートな連絡先の最初の情報
送料メール:
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
さび クロージャは、変数に保存したり、引数として他の関数に渡すことができる匿名関数です。 1 つの場所でクロージャを作成し、別のコンテキストでクロージャ操作を実行できます。関数とは異なり、クロージャでは、定義されているスコープ内の値をキャプチャできます。
イテレータは、シーケンス内の各項目を走査し、シーケンスがいつ終了するかを決定するロジックを担当します。イテレータを使用する場合、このロジックを再実装する必要はありません。
クロージャ。変数に格納できる関数のような構造。
Rust では、クロージャは外部環境から変数をキャプチャする匿名関数です。クロージャのキャプチャ動作は、変数のタイプとクロージャがそれらの変数をどのように使用するかによって異なります。
値によるキャプチャ (By Value)
クロージャが値によって変数をキャプチャすると、変数の所有権が取得されます。これは、クロージャが作成された後は、元の変数は使用できなくなることを意味します。
fn main() {
let text = "Hello".to_string();
// 使用 move 来显式地表示闭包将获取 text 的所有权
let closure = move || println!("{}", text);
// 这里 text 不能被使用,因为其所有权已经被闭包获取
// println!("{}", text); // 这将导致编译错误
closure(); // 打印 "Hello"
}
参照によるキャプチャ (By Reference)
クロージャが参照によって変数をキャプチャする場合、その変数を借用します。これは、元の変数はまだ利用可能ですが、クロージャはそれを借用することしかできず、所有権を取得できないことを意味します。
fn main() {
let text = "Hello";
// 闭包通过引用捕获 text
let closure = || println!("{}", text);
// text 仍然可用,因为它没有被移动
println!("{}", text); // 打印 "Hello"
closure(); // 再次打印 "Hello"
}
可変キャプチャ
クロージャは可変参照をキャプチャして、元の変数の値を変更できるようにします。
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);
}
クロージャーが外された場合 mut
変更、コンパイルに失敗します。
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);
}
エラー メッセージ プロンプト: 変数を借用しているため count
、移行 closure
可変バインディングが必要です。
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
引数としてのクロージャ
クロージャーは次のように使用できます。パラメータの受け渡し他の関数に対して、環境内の変数をキャプチャします。
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);
}
この例では:
square
、参照によってキャプチャします main
機能中number
変数を使用してその二乗を計算します。call_closure
を受け入れる関数。Fn() -> i32
特性バインドされたクロージャはパラメータを受け取ります。つまり、クロージャはパラメータを受け取らず、i32
値のタイプ。call_closure
機能と意志square
クロージャは引数として渡されます。なぜならsquare
クロージャにはパラメータがありません。クロージャはパラメータとして直接渡すことができます。call_closure
。call_closure
この関数はクロージャを呼び出し、結果を出力します。関数とクロージャの間にはさらに多くの違いがあります。クロージャは常に次のようなものを必要とするわけではありません fn
パラメータの種類を指定し、関数と同様に値を返します。関数はユーザーに公開される明示的なインターフェイスの一部であるため、関数には型アノテーションが必要です。これらのインターフェイスを厳密に定義して、使用される関数と戻り値の型を全員が一貫して理解できるようにすることが重要です。対照的に、クロージャはそのような公開されたインターフェイスには使用されません。クロージャは変数に格納され、名前を付けたり、ライブラリのユーザーに公開したりすることなく使用されます。
クロージャは通常短く、任意の状況ではなく狭いコンテキストに関連しています。これらの制限されたコンテキストでは、コンパイラーは、ほとんどの変数の型を推論する方法と同様に、パラメーターと戻り値の型を確実に推論できます (ただし、コンパイラーがクロージャ型の注釈を必要とする場合はまれにあります)。
変数と同様に、明確さと明確さを高めたい場合は、型アノテーションを追加できますが、(厳密に必要ではなく) コードがより冗長になるという欠点があります。
fn main() {
let a = 100;
let add_one = |x: i32| -> i32 { x + 1 };
let b = add_one(a);
println!("{}", b);
}
型アノテーションを使用すると、クロージャの構文は関数により似たものになります。以下は、引数に 1 を追加する関数の定義と、同じ動作をするクロージャ構文の垂直比較です。対応するパーツを配置するために、ここではいくつかのスペースが追加されています。これは、パイプといくつかのオプションの構文の使用を除いて、クロージャ構文が関数構文とどのように似ているかを示しています。
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 ;
したがって、上記の例は次のように簡略化できます。
fn main() {
let a = 100;
let add_one = |x| x + 1;
let b = add_one(a);
println!("{}", b);
}
コンパイラは、クロージャ定義内の各パラメータと戻り値の具象型を推論します。
別の例を見てみましょう。
fn main() {
let a = 100i32;
let a1 = 100f32;
let closure = |x| x;
let b = closure(a);
let b1 = closure(a1);
println!("{}", b);
println!("{}", b1);
}
このクロージャ定義は型アノテーションを追加しないため、このクロージャを任意の型で呼び出すことができることに注意してください。ただし、クロージャを 2 回呼び出そうとすると、1 回目は i32 を使用し、2 回目は f32 を使用して、エラーが報告されます。クロージャは以前に「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
クロージャが、それが定義されている環境内の値への参照または所有権を取得すると (したがって、クロージャに移動されるものがあればそれに影響します)、クロージャ本体内のコードは、後でクロージャが定義されるときにその参照を定義します。または、値がどのように操作されるか (クロージャから削除されるものがあればそれに影響します)。クロージャ本体は、キャプチャされた値をクロージャの外に移動する、キャプチャされた値を変更する、値の移動も変更も行わない、またはそもそも環境から値をキャプチャしないことのいずれかを行うことができます。
クロージャが環境内の値をキャプチャして処理する方法は、クロージャが実装する特性に影響します。 トレイトは、関数と構造体が使用できるクロージャのタイプを指定する方法です。クロージャ本体が値を処理する方法に応じて、クロージャは 1 つ、2 つ、または 3 つの値を自動的かつ段階的に実装します。 Fn
特性。
FnOnce
一度呼び出すことができるクロージャに適用されます。すべてのクロージャは呼び出すことができるため、すべてのクロージャは少なくともこの特性を実装します。キャプチャされた値をクロージャ本体の外に移動するクロージャは、実装のみを実装します。FnOnce
特性は一度しか呼び出すことができないためです。FnMut
キャプチャされた値をクロージャ本体の外に移動しないが、キャプチャされた値を変更する可能性があるクロージャに適しています。このタイプのクロージャは複数回呼び出すことができます。Fn
キャプチャされた値をクロージャ本体から移動したり、キャプチャされた値を変更したりしないクロージャに適用されます。もちろん、環境から値をキャプチャしないクロージャも含まれます。このようなクロージャは、環境を変更せずに複数回呼び出すことができます。これは、クロージャが同時に複数回呼び出されるシナリオでは重要です。これを関数で使用する方法を示す例です。 FnOnce
一般的な制約としてクロージャが 1 回だけ呼び出されるようにします。
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);
}
演算結果
The value is: 42
Result of the closure: 42
この例では、ジェネリック関数を定義します。 call_once
のタイプを受け入れます。 F
パラメーターf
、で F
達成しなければならないFnOnce
特性。これはつまりf
空のパラメータを受け入れ、型を返すクロージャですT
結果として。
存在する main
関数内でクロージャを作成します。consume
をキャプチャします value
所有権の。それから、私たちは電話しますcall_once
関数、渡されるconsume
閉鎖。call_once
関数はクロージャを呼び出し、その結果を返します。なぜならconsume
クロージャが消費されましたvalue
、もう一度電話してみてください call_once
同じクロージャを渡すとコンパイル エラーが発生します。
ここに用途があります FnMut
特性の例。
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);
}
演算結果
Result after applying increment: 5
Result after applying increment again: 15
この例では、apply_mut
この関数は、F
型の可変参照&mut F
パラメータとして(つまり、increment
クロージャーは移動せず、複数回使用できます)。i32
型パラメータnum
。where
句は指定しますF
実装である必要がありますFnMut
クロージャー。i32
型パラメータを返し、次の型を返します。T
結果として。main
可変クロージャが関数内に作成されますincrement
、キャプチャされた変数を変更します count
、次に使用します apply_mut
このクロージャを呼び出す関数。すべての電話apply_mut
使用しますincrement
閉鎖、そしてincrement
クロージャが変更されますcount
価値。
Rustでは、Fn
この特性は、クロージャがキャプチャした変数の所有権を取得せず、それらの変数を変更しないことを意味します。次の例は、accept を定義する方法を示しています。Fn
関数への引数としてクロージャを使用し、同時シナリオで使用します。
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();
}
}
すべての実行がこの順序になるわけではありません。どのスレッドが最初に実行されるかは、システムの現在のスケジュールによって異なります。
演算結果
Thread 1 result: 84
Thread 2 result: 84
Thread 0 result: 84
Thread 4 result: 84
Thread 3 result: 84
この例では:
call_once
汎用パラメータを受け入れる関数F
、そして F
満足しなければなりませんFn(i32) -> T
特性の限界。これはつまりF
を受け入れるクロージャですi32
型パラメータを返し、次の型を返します。T
結果として。main
関数内で、単純なクロージャを定義します。double
を受け入れます。 i32
型パラメータx
そして戻るx * 2
結果として。map
5 つのスレッドが作成され、各スレッドがコピーされますdouble
閉じて新しいスレッドで呼び出しますcall_once
関数、クロージャを引数として渡しますcall_once
。call_once
関数が呼び出され、クロージャが実行され、結果が出力されます。join
メソッドはすべてのスレッドが完了するまで待機します。イテレータを使用すると、シーケンスの項目に対して特定の処理を実行できます。次に、要素シーケンスを処理するためのイテレータの使用と、ループとイテレータのパフォーマンスの比較を主に紹介します。 Rust では、イテレータは遅延的です。つまり、イテレータを使用するメソッドが呼び出されるまで操作は実行されません。
イテレータはすべて、と呼ばれる関数を実装します。 Iterator
標準ライブラリのトレイトで定義されています。この特性の定義は次のようになります。
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// 此处省略了方法的默认实现
}
type Item
そしてSelf::Item
、特性に関連付けられたタイプを定義します。しかし今のところ知っておくべきことは、このコードが実装方法を示しているということだけです。Iterator
特性には、Item
タイプ、これItem
タイプは次のように使用されますnext
メソッドの戻り値の型。言い換えると、Item
型はイテレータによって返される要素の型になります。
next
はいIterator
メソッド実装者が定義する必要があるのはメソッドだけです。next
イテレータ内で一度に 1 つの項目をカプセル化して返します。Some
、イテレータが終了すると戻りますNone
。
Iterator
特性には、標準ライブラリによってデフォルトで実装されるさまざまなメソッドのセットがあります。Iterator
これらのメソッドはすべて、トレイトの標準ライブラリ API ドキュメントに記載されています。一部のメソッドはその定義を呼び出しますnext
メソッドを実装する理由は次のとおりです。Iterator
特性を実装する必要がありますnext
方法の理由。
これらの通話 next
メソッドのメソッドは、呼び出しによってイテレータが消費されるため、消費アダプタと呼ばれます。
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);
}
再度使用する場合 numbers_iter
エラーが報告されます。sum
メソッドはイテレータの所有権を取得し、それを繰り返し呼び出します。next
イテレータを反復処理して、イテレータを消費します。各項目を反復処理すると、各項目が合計に加算され、反復が完了すると合計が返されます。移行sum
使用は許可されなくなりましたnumbers_iter
、電話するから sum
イテレータの所有権を取得したとき。
Iterator
イテレータ アダプタと呼ばれる、トレイトで定義された別のクラスのメソッドを使用すると、現在のイテレータを別の型のイテレータに変更できます。複数のイテレータ アダプタをチェーンで呼び出すことができます。ただし、すべての反復子は遅延型であるため、反復子のアダプター呼び出しの結果を取得するには、消費アダプター メソッドを呼び出す必要があります。
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]);
}
collect メソッドはイテレータを使用し、結果をデータ構造に収集します。なぜならmap
通過する各要素に対して実行する操作を指定できるクロージャを取得します。
多くの反復子アダプターはパラメーターとしてクロージャーを受け入れます。通常、反復子アダプターのパラメーターとして指定されたクロージャーは、その環境をキャプチャーするクロージャーになります。
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);
}
演算結果
The filtered and squared numbers are: [4, 16]
この例では、filter
そしてmap
どちらもイテレータ アダプタであり、クロージャをパラメータとして受け入れます。これらのクロージャは、その環境、つまりイテレータ内の要素をキャプチャします。存在するfilter
アダプター、クロージャー内|&x| x % 2 == 0
要素が偶数かどうかを確認するために使用され、偶数の場合は要素が保持されます。存在するmap
アダプター、クロージャー内|x| x * x
各要素を二乗するために使用されます。これらのアダプタはコンシューマであるため、元のイテレータを消費するため、再度使用することはできません。やっと、collect
アダプターは、処理された要素を新しいファイルに収集します。Vec
真ん中。
イテレータは、高レベルの抽象化として、手書きの低レベル コードとほぼ同じパフォーマンスのコードにコンパイルされます。イテレータは Rust のゼロコスト抽象化の 1 つであり、この抽象化により実行時のオーバーヘッドが発生しないことを意味します。
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);
}
演算結果
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
この例から、イテレータはまだわずかに遅いことがわかります。