Rust: Vec :: drain_filterとLinkedList :: drain_filterの追跡の問題

作成日 2017ĺš´07月15日  Âˇ  119コメント  Âˇ  ソース: rust-lang/rust

    /// Creates an iterator which uses a closure to determine if an element should be removed.
    ///
    /// If the closure returns true, then the element is removed and yielded.
    /// If the closure returns false, it will try again, and call the closure
    /// on the next element, seeing if it passes the test.
    ///
    /// Using this method is equivalent to the following code:
    ///
    /// ```
    /// # let mut some_predicate = |x: &mut i32| { *x == 2 };
    /// # let mut vec = vec![1, 2, 3, 4, 5];
    /// let mut i = 0;
    /// while i != vec.len() {
    ///     if some_predicate(&mut vec[i]) {
    ///         let val = vec.remove(i);
    ///         // your code here
    ///     }
    ///     i += 1;
    /// }
    /// ```
    ///
    /// But `drain_filter` is easier to use. `drain_filter` is also more efficient,
    /// because it can backshift the elements of the array in bulk.
    ///
    /// Note that `drain_filter` also lets you mutate ever element in the filter closure,
    /// regardless of whether you choose to keep or remove it.
    ///
    ///
    /// # Examples
    ///
    /// Splitting an array into evens and odds, reusing the original allocation:
    ///
    /// ```
    /// let mut numbers = vec![1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14, 15];
    ///
    /// let evens = numbers.drain_filter(|x| *x % 2 == 0).collect::<Vec<_>>();
    /// let odds = numbers;
    ///
    /// assert_eq!(evens, vec![2, 4, 6, 8, 14]);
    /// assert_eq!(odds, vec![1, 3, 5, 9, 11, 13, 15]);
    /// ```
    fn drain_filter<F>(&mut self, filter: F) -> DrainFilter<T, F>
        where F: FnMut(&mut T) -> bool,
    { ... }

どこかに問題があると思いますが、見つかりません。 誰かオタクが私を狙ってそれを実装させました。 PR着信。

A-collections B-unstable C-tracking-issue Libs-Tracked T-libs

最も参考になるコメント

これが安定するのを妨げるものはありますか?

全てのコメント119件

たぶん、これには台所の流し台を含める必要はありませんが、範囲パラメータを持つことができるので、排水管のスーパーセットのようになります。 それに対する欠点はありますか? 範囲の境界チェックを追加することは欠点だと思います。パニックになる可能性があるもう1つのことです。 しかし、drain_filter(..、f)はできません。

近い将来、これが何らかの形で安定する可能性はありますか?

コンパイラが境界チェックを排除するのに十分賢い場合
drain_filter(.., f)の場合、これを選択します。

(そして私はあなたがそれをある方法で実装できるとかなり確信しています
最悪の場合、コンパイラは賢くなります
あなたが「機能の専門化」を持つことができる場合、
基本的にif Type::of::<R>() == Type::of::<RangeFull>() { dont;do;type;checks; return }のようなもの)

これがある程度バイクシェッドであることは知っていますが、これを$#$ drain_where drain_filterと名付けた理由は何でしたか? 私にとって、前者はVec全体が排出されることを意味しますが、結果に対してfilterも実行することを意味します(私が最初にそれを見たとき、私は次のように考えました。 .drain(..).filter() ? ")。 一方、前者は、何らかの条件が成立する要素のみを排出することを示しています。

わかりませんが、 drain_whereの方がはるかに優れており、はるかに直感的です。
それを変更するチャンスはまだありますか?

.remove_ifも事前の提案です

drain_whereがそれを最もよく説明していると思います。 ドレインと同様に値を返しますが、すべての値をドレイン/削除するのではなく、特定の条件が真の場合にのみ値を返します。

remove_ifは、条件付きバージョンのremoveによく似ています。条件が真の場合、インデックスごとに1つのアイテムを削除します。たとえば、 letters.remove_if(3, |n| n < 10);は、インデックス3の文字が10未満の場合に削除します。 。

一方、 drain_filterは少しあいまいですが、 drain 、次にfilterをより最適化された方法(filter_mapなど)で実行します。イテレータfilterは戻り、
もしそうなら、フィルターは以前に論理的に使用されるので、 filtered_drainと呼ばれるべきではありません...

標準ライブラリのどこでも_whereまたは_ifを使用する前例はありません。

@Gankroどこでも_filterを使用する前例はありますか? また、それがあいまいさの少ない用語を使用しない理由であるかどうかもわかりません。 標準ライブラリの他の場所では、すでに_untilや_whileなどのさまざまなサフィックスが使用されています。

コメントの「上記の同等の」コードは正しくありません...「ここのコード」サイトでiから1を引く必要があります。そうしないと、悪いことが起こります。

IMO問題はfilterではありません。 これを検索したばかり(そして初心者である)、 drainは他の言語と比較してかなり非標準のようです。

繰り返しになりますが、初心者の観点からすると、この問題が提案することを実行するために何かを見つけようとすると、 delete ( delete_ifのように)、 removeなります。 、 filterまたはreject 。

私は実際にfilter $を_検索_し、 drain_filterを見て、読みませんでした_検索を続けました。 drainは、私がやりたいと思っていた単純なことを表していないようだったからです。

filterまたはrejectという名前の単純な関数の方がはるかに直感的であるように思われます。

別の注意点として、これが呼び出されたベクトルを変更する必要があるとは思わない。 連鎖を防ぎます。 理想的なシナリオでは、次のようなことができるようになります。

        vec![
            "",
            "something",
            a_variable,
            function_call(),
            "etc",
        ]
            .reject(|i| { i.is_empty() })
            .join("/")

現在の実装では、結合されるのは拒否された値です。

acceptとrejectの両方を見たいのですが。 どちらも元の値を変更しません。

filterだけで連鎖を行うことができます。 drain_filterの全体的なポイントは、ベクトルを変更することです。

@rpjohnstなので、ここで検索しましたが、どこかにfilterがありませんか?

はい、 Vec Iteratorのメンバーです。

ドレインは、コンテナにのみ適用されるRustの第4の種類の所有権を表すため、新しい用語ですが、他のほとんどの言語では一般に無意味な区別です(移動セマンティクスがない場合、反復と削除を組み合わせて単一の「アトミック」操作)。

ただし、drain_filterは、他の言語が気にするスペースにドレイン用語を移動します(バックシフトの回避はすべての言語に関連するため)。

rust consume vecのグーグル結果としてドキュメントでdrain_filterに出くわしました。 デフォルトではrustの不変性のため、filterはデータを消費せず、メモリをより適切に管理できるようにアプローチする方法を思い出せなかったことを知っています。

drain_whereは素晴らしいですが、ユーザーがdrainとfilterの機能を知っている限り、このメソッドが述語フィルターに基づいてデータを排出することは明らかだと思います。

drain_filterは、それが排出され(つまり、空になり)、次にフィルタリングされることを意味しているように感じます。 一方、 drain_whereは、指定された条件が成立する要素を排出するように聞こえます(これは提案された関数が行うことです)。

linked_list::DrainFilterもDrop $を実装して、述語に一致する残りの要素を削除するべきではありませんか?

はい

イテレータをドロップすると、イテレータが最後まで実行されるのはなぜですか? これはイテレータにとって驚くべき動作だと思います。必要に応じて、明示的に実行することもできます。 一方、イテレータをmem::forget実行するとリーク増幅が発生するため、必要な数の要素だけを取り出すという逆は不可能です。

私はこの関数を頻繁に使用しており、排出したいエントリに対して常にtrueを返すことを忘れないでください。これは、 retain() / retain_mut()と比較して直感に反します。
直感的な論理レベルでは、保持したいエントリに対してtrueを返す方が理にかなっていますが、他の誰かがこのように感じていますか? (特にretain()がすでにこのように機能していることを考えると)
それをして、 drain_filter()の名前をretain_iter()またはretain_drain() (またはdrain_retain() )に変更してみませんか?
そうすれば、 retain()をより厳密に反映することにもなります。

これが私が名前を変更することを提案した理由です
drain_whereは、次のことが明らかです。

  1. ドレインの一種なので、名前にドレインを使用します

  2. whereをdrainと組み合わせて使用​​することにより、
    要素_where_述語がtrueである場合、ドレインされます。つまり、削除されて返されます。

  3. 通常、stdの他の名前と同期している場合は、
    2つの述語で構成される関数を使用して(大まかに)エミュレートできます
    「そしてその後」の方法で各述語を表す関数。
    filter_mapは、(大まかに) filter an then mapとしてエミュレートできます。 現在
    名前はそれがdrain and then filterであることを示していますが、それに近くさえありません
    完全なドレインをまったく行わないためです。

2018ĺš´2月25日、日曜日の17: [email protected]は次のように書いています。

私はこの関数を頻繁に使用しており、常に覚えておく必要があります
排出したいエントリに対してtrueを返します。
hold_mut()と比較して直感に反します。
プライマリレベルでは、エントリIに対してtrueを返す方が理にかなっています。
維持したい、他の誰かがこのように感じますか?
それをして、drain_filter()の名前をretain_filter()に変更してみませんか?

—
このスレッドにサブスクライブしているため、これを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/rust-lang/rust/issues/43244#issuecomment-368320990 、
またはスレッドをミュートします
https://github.com/notifications/unsubscribe-auth/AHR0kfwaNvz8YBwCE4BxDkeHgGxLvcWxks5tYYRxgaJpZM4OY1me
。

ただし、 drain_where()を使用すると、削除する必要のある要素に対してクロージャーがtrueを返す必要があります。これは、 retain()の反対であり、一貫性がありません。
多分retain_where ?
しかし、名前に「ドレイン」を含めるのは理にかなっていると思うので、 drain_retain()が最も理にかなっていると思います。$ drain()に似ていますが、クロージャーが戻る要素を保持します。 true 。

それは変わりませんが、あなたが真を返さなければならないことはそれを明らかにします
あなたはそうしなければなりません。

私は個人的に次のいずれかを使用します:

a。 drain_where
b。 retain_where
c。 retain

drain_retainは使用しません。
排水して保持する同じ種類のプロセスについて話しますが、反対から
視点、ドレインはあなたが削除する(そしてrを返す)保持するものについて話します
あなたが保持しているものについて話します(それがすでにstdで使用されている方法で)。

現在、 retaimはすでにstdに実装されていますが、大きな違いがあります
drainが要素を返している間に要素を破棄していること、つまり
retain (悲しいことに)名前に使用するのは不適切です(電話をかけたい場合を除く)
retain_and_returnまたは同様のもの)。

ドレインについて語るもう1つのポイントは、移行の容易さ、つまり移行のしやすさです。
drain_whereは、単語ベースの検索と置換を実行するのと同じくらい簡単です。
コードを保持するように変更する場合は、コードをさらに否定する必要があります。
使用されるすべての述語/フィルター関数。

2018ĺš´2月25日、日曜日、18: [email protected]は次のように書いています。

しかし、drain_where()を使用すると、クロージャーは引き続きtrueを返す必要があります。
削除する必要のある要素。これは、retain()の反対です。
一貫性がなくなります。
多分retain_where?
しかし、名前に「ドレイン」を含めるのは理にかなっていると思いますが、
したがって、drain_retain()が最も理にかなっていると思います。これはdrain()に似ていますが、
クロージャがtrueを返す要素を保持します。

—
このスレッドにサブスクライブしているため、これを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/rust-lang/rust/issues/43244#issuecomment-368325374 、
またはスレッドをミュートします
https://github.com/notifications/unsubscribe-auth/AHR0kfG4oZHxGfpOSK8DjXW3_2O1Eo3Rks5tYZHxgaJpZM4OY1me
。

しかし、どのくらいの頻度でdrain()からdrain_filter()移行しますか?
これまでのすべてのケースで、stdにretain_mut()がなく、要素を変更する必要があるため、 retain()からdrain_filter()に移行しました。そのため、クロージャの戻り値を反転する必要がありました。
drain()メソッドは範囲内のすべての要素を無条件に排出するのに対し、 drain_retain()はクロージャーがtrue返す要素を保持するため、 drain_retain()は理にかなっていると思います。 drain()およびretain()メソッドの効果。

申し訳ありませんが、 drain_filterからdrain_whereに移行するつもりです。

retainを使用したソリューションがあり、次に使用する必要があること
drain_filterは、私がまだ検討していない側面です。

2018ĺš´2月25日、日曜日の19: [email protected]は次のように書いています。

しかし、なぜdrain()からdrain_filter()に移行するのでしょうか?
これまでのすべてのケースで、retain()からdrain_filter()に移行しました
stdにはretain_mut()がなく、要素を変更する必要があるためです。
drain()メソッドはドレインするので、drain_retain()は理にかなっていると思います
無条件に範囲内のすべての要素に対して、drain_retain()は保持します
クロージャがtrueを返す要素。

—
このスレッドにサブスクライブしているため、これを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/rust-lang/rust/issues/43244#issuecomment-368330896 、
またはスレッドをミュートします
https://github.com/notifications/unsubscribe-auth/AHR0kSayIk_fbp5M0RsZW5pYs3hDICQIks5tYaJ0gaJpZM4OY1me
。

ああ、そうですが、 drain_filter()を使用する現在のコードでクロージャを反転する「価格」は、stdで一貫性のある直感的なAPIを取得してから、安定させる価値があると思います。
これはわずかな固定費にすぎません(関数の名前変更に伴うため、コンパイラエラーにより、クロージャを反転する必要があることがユーザーに通知される可能性があるため、バグが発生することはありません)。 、 drain_filter()を標準化するコストと比較すると、 retain()からdrain_filter()に移行するときは、常にクロージャを反転する必要があります。それを行うと、ドキュメントでそれを見つけるのを難しくするためのコストは、 retain()から来て、「 retain()のようなものを検索しますが、 &mutをそのクロージャに渡します。そのため、この関数の新しい名前の名前に「retain」が含まれているのは理にかなっているので、ドキュメントで検索するとわかります。

いくつかの事例データ:私のコードでは、常にdrain_filter()のretain_mut()アスペクトのみが必要でした(以前retain()を使用していたことがよくありました)。必要なユースケースはありませんでした。排出された値を処理します。 これは将来、他の人にとっても最も一般的なユースケースになると思います( retain()は&mutをクロージャに渡さないため、 drain_filter()はそのユースケースをカバーする必要があります、また、排出された値を処理する必要があるよりも一般的なユースケースです)。

私がdrain_retainに反対する理由は、現在stdwrtで名前が使用されている方法のためです。 コレクション:

  1. 述語を使用する関数名があり、それらに関連付けられた生成/消費の概念があります(wrt。rust、iterations)。 たとえば、 drain 、 collect 、 fold 、 all 、 take 、..。
  2. この述語には、 *_where 、 *_whileなどの修飾子が含まれる場合があります
  3. プロパティを変更する述語を使用する関数名があります( map 、 filter 、 skip 、...)

    • ここでは、要素または反復の変更であるかどうかがあいまいです( mapとfilter / skip )

  4. 変更プロパティを使用して複数の述語を連鎖させる関数名(例: filter_map

    • およそapply modifier_1 and then apply modifier_2の概念を持っていますが、これを1つのステップで実行する方が高速または柔軟です。

あなたは時々持っているかもしれません:

  1. 述語の生成/消費と述語の変更を組み合わせた関数名(例: drain_filter )

    • _しかし_ほとんどの場合、それらを修飾子と組み合わせる方が良い/混乱が少ないです(例: drain_where )

あなたは通常持っていません:

  1. 2つの生成/消費述語が1つの名前に結合されています。つまり、混乱しやすいため、 take_collectのような考えはありません。

drain_retainはちょっと意味がありますが、最後のカテゴリに分類されますが、基本的にremove and return all elements "somehow specified" and then keep all elements "somehow specified" discarding other elementsと表示されていることは推測できます。


一方、 retain_mutがないはずの理由がわかりません。 modify + retainを組み合わせる効率的な方法としてretain_mutを紹介する簡単なRFCを開くかもしれません。 retain私はそれがより速いかもしれないという予感があります
その後、この機能を安定させました。 それまでは、拡張トレイトを提供することを検討できます
iter_mut + bool-array(またはbitarray、または...)を使用して$ retain_mutを所有し、どの要素を追跡するか
取り戻す必要があります。 または、内部でdrain_filer / drain_whereを使用する独自のdrain_retainを提供します
ただし、述語を|ele| !predicate(ele)ではないものにラップします。

@dathinab

  1. ここでは、イテレータではなく、コレクションのメソッドについて説明しています。 map、filter、filter_map、skip、take_whileなどはすべてIteratorのメソッドです。 ところで、 *_whereを使用するのはどの方法ですか?
    したがって、命名スキームを、コレクションにすでに存在するメソッド( retain() 、 drain()など)と比較する必要があります。 あるイテレータを別のイテレータに変換するイテレータメソッドと混同することはありません。
  2. drain_filter()はすでに追加されており、人々はそれを使用するようにアドバイスされているため、 retain_mut()はstdに追加されないというコンセンサスが得られました。 これにより、 retain()からdrain_filter()に移行するユースケースに戻ることが非常に一般的であるため、同様の名前とAPIを使用する必要があります( trueを返すクロージャーはエントリを保持することを意味します)。。

retain_mutがすでに議論されていることに気づいていません。

stdの一般的な命名スキームについて主にwrtについて話しています。 に
コレクションには、メインの1つであるイテレータが含まれます
さびたコレクションのアクセス方法、特にそれが約の場合
複数のエントリを変更します。

  • _whereは、わずかに変更された接尾辞を表すための単なる例です
    関数。 現在stdで使用されているこの種の接尾辞は主に
    _until 、 _while 、 _then 、 _else 、 _mut 、 _back 。

drain_retainが混乱している理由は、それが混乱しているのかどうかが明確でないためです。
ドレインまたは保持ベース、ドレインベースの場合、trueを返すと削除されます
値は、保持ベースの場合、保持されます。 _whereを使用すると、
最後に、渡された関数に何が期待されるかを明確にします。

2018ĺš´2月26日月曜日、00: [email protected]は次のように書いています。

@dathinab https://github.com/dathinab

  1. ここでは、イテレータではなく、コレクションのメソッドについて説明しています。
    map、filter、filter_map、skip、take_whileなどはすべてIteratorのメソッドです。
    ところで、* _ whereを使用するのはどの方法ですか?
    したがって、命名スキームを既存のメソッドと比較する必要があります
    コレクションでは、retain()、drain()など。 との混同はありません
    イテレータを別のイテレータに変換するイテレータメソッド。
  2. コンセンサスは、retain_mut()がstdに追加されないということでした。
    なぜなら、drain_filter()はすでに追加されており、人々にアドバイスされているからです。
    それを使用します。 これにより、からの移行のユースケースに戻ります。
    非常に一般的であるため、retain()からdrain_filter()まで、
    同様の名前とAPI(trueを返すクロージャーはエントリを保持することを意味します)。

—
あなたが言及されたので、あなたはこれを受け取っています。

このメールに直接返信し、GitHubで表示してください
https://github.com/rust-lang/rust/issues/43244#issuecomment-368355110 、
またはスレッドをミュートします
https://github.com/notifications/unsubscribe-auth/AHR0kfkRAZ5OtLFZ-SciAmjHDEXdgp-0ks5tYevegaJpZM4OY1me
。

私はこの関数を頻繁に使用しており、排出したいエントリに対して常にtrueを返すことを忘れないでください。これは、retain()/ retain_mut()と比較して直感に反しているように感じます。

FWIW、ここではretainが直感に反する名前だと思います。私は通常、ベクトルから特定の要素を削除したいと思っています。 retainを使用すると、常にそのロジックを反転する必要があります。

しかし、 retain()はすでに安定しているので、それと一緒に暮らす必要があります。そして、私の直感はそれに慣れました。

@Boscop : drainも同様です。これは$# retainの逆ですが、削除された要素と、 _until 、 _whileなどのサフィックスの使用法も返します。既存の機能のわずかに変更されたバージョンである利用可能な機能。

つまり、ドレインについて説明すると、次のようになります。

_「何らかの方法で」指定されたすべての要素を削除して返し、他のすべての要素を保持します_
ここで、_ "何らかの方法で" _は、すべてのスライス可能なコレクションタイプの場合は_ "スライス" _であり、残りの場合は_ "すべて" _です。

ここで説明する関数の説明は、次の点を除いて_同じ_です。
_ "何らかの方法で" _ is _ "ここで、指定された述語はtrue" _を返します。

一方、私が保持する説明は次のとおりです。
_指定された述語がtrueを返す要素のみを保持(つまり保持)し、残りを破棄します_

(はい、retainは残りを破棄しない方法で使用された可能性がありますが、残念ながらそうではありませんでした)


retainがあれば本当に良かったと思います
&mut Tを述語に渡し、削除された値を返した可能性があります。
retainの方が直感的な名前ベースだと思うからです。

しかし、これとは別に、 drain_filter / drain_retainはどちらも最適ではないと思います
述語がエントリを保持/排出するためにtrue / falseを返す必要があるかどうかを明確にしないため、
(ドレインは、フィルター中に要素を削除することについて説明しているため、trueが削除することを示します
そして再訓練は、最後に錆びた状態で維持する要素について話します)


結局、どの名前を使用するかは重要ではありません。安定したものがあればいいのですが。

世論調査を行うこと、および/または言語チームの誰かに決定させることは、考えを前進させるための最良の方法かもしれませんか?

drain_where 、 drain_if 、またはdrain_whenのようなものは、 drain_filterよりもはるかに明確だと思います。

@tmccombsこれらの3つのうち、 drain_whereが最も理にかなっていると思います。 ( if either do the whole thing (in this case draining) or not $を意味し、 whenは一時的なものであるためです。)
drain_filterと比較すると、クロージャの戻り値はdrain_where (要素を削除する場合はtrue )と同じですが、その事実は名前によって明確/明示的にされるため、クロージャの戻り値の意味を誤って解釈するリスク。

安定するのは時間以上だと思います。 このスレッドの要約:

  • R: RangeArgumentパラメータを追加する必要がありますか?
  • ブール値を反転する必要がありますか? (現在のロジックは理にかなっていると思います。コールバックからtrueを返すと、そのアイテムがイテレーターに含まれます。)
  • ネーミング。 私はdrain_whereが好きです。)

@ガンクロ、どう思いますか?

libsチームはこれについて話し合い、コンセンサスは現時点でdrainのようなメソッドを安定させないことでした。 (既存のdrain_filterメソッドはNightlyに不安定なままでいる可能性があります。) https://github.com/rust-lang/rfcs/pull/2369は、ドロップしても何もしない別のドレインのようなイテレーターを追加することを提案しています(イテレータを最後まで消費するのとは対照的に)。

排水のさまざまな組み合わせをより小さなAPIサーフェスに一般化するための実験を行いたいと思います。

  • サブレンジ( RangeArgument 、別名RangeBounds )とコレクション全体(後者は.. 、タイプRangeFullの値を渡すことで実現できますが) )。
  • すべて(おそらくその範囲内)を排出するのに対し、ブール述語に一致する要素のみを排出する
  • ドロップ時とそうでない場合の自己消耗(コレクション内の残りの要素を残す)。

可能性としては、メソッドを汎用化することによるメソッドの「オーバーロード」やビルダーパターンなどがあります。

1つの制約は、 drainメソッドが安定していることです。 一般化できる可能性がありますが、下位互換性のある方法でのみ可能です。

排水のさまざまな組み合わせをより小さなAPIサーフェスに一般化するための実験を行いたいと思います。

チームは、この種の実験が行われることをどのように、どこで予測していますか?

方法:具体的なAPI設計を考え出し、提案します。おそらく、概念実証の実装を使用します(これは、少なくともVec::as_mut_ptrとVec::set_lenを介してツリーから実行できます)。 どこでもかまいません。 新しいRFCまたはhttps://internals.rust-lang.org/c/libsのスレッドである可能性があり、ここからリンクします。

私はこれを少し遊んでいます。 数日中に内部のスレッドを開きます。

このように機能する一般的なAPIは理にかなっていると思います。

    v.drain(a..b).where(pred)

つまり、これはビルダースタイルのAPIです。 .where(pred)が追加されていない場合、範囲全体が無条件に排出されます。
これは、現在の.drain(a..b)メソッドと.drain_filter(pred)の機能をカバーしています。

drainという名前がすでに使用されているために使用できない場合は、 drain_iterのような名前にする必要があります。

特にwhereとfilterを次のように組み合わせて使用​​する場合は、結果のイテレータのフィルタリングとの混同を避けるために、 whereメソッドに*_filterという名前を付けないでください。

    v.drain(..).where(pred1).filter(pred2)

ここでは、 pred1を使用して何を排出するか(そしてイテレーターに渡すか)を決定し、 pred2を使用して結果のイテレーターをフィルター処理します。
pred1がtrueを返すが、 pred2がfalseを返す要素は、 vから排出されますが、によって生成されません。この結合されたイテレータ。

この種のビルダースタイルのAPIアプローチについてどう思いますか?

whereはすでにキーワードであるため、関数名として使用できないことを一瞬忘れました:/

また、 drainはすでに安定しているため、名前も使用できません。

次に、全体として2番目に良いオプションは、現在のdrainを維持し、 drain_filterの名前をdrain_whereに変更して、 .drain(..).filter()との混同を避けることだと思います。

(jonhooが上で言ったように:)

このdailen_filterにdrain_whereではなく名前を付ける理由は何でしたか? 私にとって、前者はVec全体が排出されることを意味しますが、結果に対してフィルターを実行することも意味します(最初に見たとき、「これは.drain(..)。filter()だけではありません。 ? ")。 一方、前者は、何らかの条件が成立する要素のみを排出することを示しています。

内部のスレッドを開きました。
TLDRは、非自己枯渇は一般的なケースで予想されるよりも大きなワームの缶であり、 RangeBoundsパラメータを使用して後でではなく早くdrain_filterを安定させる必要があると思います。 誰かがそこで概説された問題を解決するための良い考えを持っていない限り。

編集:実験コードをアップロードしました:実験を排出します
ドレインとクリアのベンチといくつかのテストもありますが、クリーンなコードは期待できません。

このスレッドを完全に見逃しました。 このスレッドで説明されているオプションのいくつかを反映するために、少し修正してコピーして貼り付けた古いimplがあります。 物議を醸すことがないと私が思うimplの良い点の1つは、それがDoubleEndedIteratorを実装していることです。 ここでそれを表示します。

@Emerentiusですが、少なくともdrain_filterの名前をdrain_whereに変更して、要素を削除するためにクロージャーがtrueを返す必要があることを示す必要があります。

@Boscopどちらも、 true =>歩留まりの同じ「極性」を意味します。 個人的には、 drain_filterと呼ばれるかdrain_whereと呼ばれるかは関係ありません。

@Popog違いと長所と短所を要約できますか? 理想的には、めねじで終わります。 DoubleEndedIterator機能は、ゼロまたは低オーバーヘッドと互換性を持って逆方向に追加できると思います(ただし、テストはしていません)。

drain_or_retainどうですか? これは文法的に意味のあるアクションであり、どちらか一方を実行することを示します。

@askeksaしかし、クロージャーからtrueを返すことが「ドレイン」を意味するのか、「保持」を意味するのかは明確ではありません。
drain_whereのような名前では、 trueを返すとそれが排出されることは非常に明白であり、排出されない要素が保持されることは誰にとっても明らかなはずです。

排水管を制限/停止/キャンセル/中止する方法があればいいのにと思います。 たとえば、最初のN個の偶数を排出したい場合は、 vec.drain_filter(|x| *x % 2 == 0).take(N).collect() (またはその変形)を実行できると便利です。

現在実装されているため、 DrainFilterのdropメソッドは常にドレインを実行して完了します。 それを中止することはできません(少なくとも私はそれを行うトリックを見つけていません)。

その動作が必要な場合は、表示した数を追跡する状態を閉じて、falseを返し始める必要があります。 アダプターを適切に動作させるには、ドロップ時に完了するまで実行する必要があります。

drain_filterが現在実装されている方法が安全ではないことに気づきましたが、
実際には安全上の問題があります。 くつろぎ+安全を再開します。 さらに、それは簡単に中止を引き起こします、両方
そのうち、stdのメソッドが実際に持つべきではない動作です。 そしてこれを書いている間、私は気づきました現在の実装は安全ではないこと

Vecはデフォルトではアンワインドセーフではありませんが、 drain_filerの動作は
述語のパニックは驚くべきことです。理由は次のとおりです。

  1. ドロップ時にパニックになったクロージャを呼び出し続けます
    閉鎖が再びパニックになった場合、これは乗船を引き起こし、一部の人々は
    すべてのパニックがエラーカーネルパターンを使用して他の作業に参加するように、そしてそれらのために
    乗ってしまうのはかなり悪いです
  2. 潜在的に1つの値の排出を正しく続行しない場合
    すでにドロップされている値が1つ含まれていると、解放後に使用される可能性があります

この動作の例は次のとおりです。
play.rust-lang.org

2.ポイントは解決可能であるはずですが、それ自体の最初のポイントは解決可能であると思います
完了まで実行するDrainFilterの動作の再検討につながります
ドロップ時に、これを変更する理由は次のとおりです。

  • イテレータは錆びて怠惰であり、ドロップ時にイテレータを実行するのはちょっと予期しない動作です
    通常期待されるものから派生
  • drain_filterに渡された述語は、状況によってはパニックになる可能性があります(ロックなど)
    毒された)その場合、ドロップリーディング中に呼び出されたときに再びパニックになる可能性があります
    二重のパニックに陥り、したがって搭乗します。これは、エラーカーネルを使用している人にとっては非常に悪いことです。
    パターンまたは最後に制御された方法でシャットダウンしたい場合は、とにかくpanic = aboardを使用しても問題ありません
  • 述語に副作用があり、 DrainFilterを最後まで実行しないと、次のようになる可能性があります。
    ドロップしたときに実行されて完了すると、驚くべきバグが発生します(ただし、実行した可能性があります)
    他の人はそれをある点まで排出することとそれを落とすことの間で考えます)
  • 渡された述語を変更せずにこの動作をオプトアウトすることはできません。
    ラップせずに実行できない可能性がありますが、一方で、いつでも実行をオプトインできます
    イテレータを実行して完了するだけで完了します(はい、この最後の引数は少しです
    手ぶれ)

実行して完了するための引数は次のとおりです。

  • drain_filterは関数であるratainに似ているので、人々は驚かれるかもしれません。
    完了するまで実行する代わりに、 DrainFilterを「ただ」ドロップします

    • この議論は他のRFCで何度も反論されており、それが#[unused_must_use]の理由です。

      皮肉なことに、いくつかの状況ではすでに.for_each(drop)を使用することをお勧めします

      たまたまDrainFilterがドロップ時に行うことです

  • drain_filterは副作用のためだけに使用されることが多いので、冗長にする必要があります

    • そのように使用すると、 retainとほぼ等しくなります



      • ただし、 &Tを使用し続け、drain_filterは&mut Tを使用します



  • 他?
  • [編集、後で追加、THX @tmccombs ]: find 、 all 、 anyなどのアダプターと組み合わせると、ドロップ時に完了しないと非常に混乱する可能性があります。これにはかなりの理由があります。現在の動作を維持します。

それは私だけかもしれませんし、私はいくつかのポイントを逃したかもしれませんが、 Dropの動作を変更し、
#[unused_must_use]を追加するのが望ましいと思われますか?

.for_each(drop)が長すぎる場合は、代わりに、次の目的のイテレータのRFCを追加することを検討してください。
complete()のようなメソッドをイテレータに追加するという副作用があります(またはdrain()ですが、これは
完全に異なる議論です)

他?

元の理由は見つかりませんが、アダプターがDrainFilterで動作し、完全に実行されないという問題もあったことを覚えています。

https://github.com/rust-lang/rust/issues/43244#issuecomment-394405057も参照してください

良い点、たとえばfindは、最初にヒットするまでドレインをドレインします。
一致、同様のall 、 anyは短絡を行いますが、これは非常に混乱する可能性があります
に関して。 ドレイン。

うーん、多分私は私の意見を変える必要があります。 これにより、一般的な問題になる可能性があります
イテレータには副作用があり、一般的な解決策を検討する必要があるかもしれません
(この追跡の問題とは関係ありません) .allways_complete()アダプターのように。

個人的には、ドレインを最後まで実行する必要がある安全上の理由は見つかりませんでしたが、上記のいくつかの投稿で書いたように、 next()の副作用は、 take_whileなどのアダプターと最適ではない方法で相互作用しますpeekableおよびskip_while 。

これはまた、非自己排出ドレインとそのコンパニオン自己排出イターアダプターRFCに関する私のRFCと同じ問題を引き起こします。

drain_filterは簡単に中絶を引き起こす可能性があるのは事実ですが、安全性に違反する例を示すことができますか?

うん、私はすでにやった: play.rust-lang.org

これはどれですか:

#![feature(drain_filter)]

use std::panic::catch_unwind;

struct PrintOnDrop {
    id: u8
}

impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("dropped: {}", self.id)
    }
}

fn main() {
    println!("-- start --");
    let _ = catch_unwind(move || {
        let mut a: Vec<_> = [0, 1, 4, 5, 6].iter()
            .map(|&id| PrintOnDrop { id })
            .collect::<Vec<_>>();

        let drain = a.drain_filter(|dc| {
            if dc.id == 4 { panic!("let's say a unwrap went wrong"); }
            dc.id < 4
        });

        drain.for_each(::std::mem::drop);
    });
    println!("-- end --");
    //output:
    // -- start --
    // dropped: 0    <-\
    // dropped: 1       \_ this is a double drop
    // dropped: 0  _  <-/
    // dropped: 5   \------ here 4 got leaked (kind fine)  
    // dropped: 6
    // -- end --

}

しかし、それは実装の内部的な考え方であり、うまくいきませんでした。
基本的に、未解決の質問は、述語関数のpanicを処理する方法です。

  1. パニックになった要素をスキップし、リークして、デルカウンターを増やします

    • 何らかの形のパニック検出が必要

  2. 述語を呼び出す前にidxを進めないでください

    • しかし、これは、ドロップ時に同じ述語で再度呼び出すことを意味します

もう1つの質問は、ドロップ時にapiユーザー入力と見なすことができる関数を実行することをお勧めするかどうかです。
一般的には、これがfind 、 anyなどを混乱させないための唯一の方法です。

たぶん、考慮事項は次のようなものかもしれません:

  1. nextに入るときにフラグを設定し、 nextから戻る前にフラグを解除します
  2. フラグがまだ設定されている場合、ドロップ時にパニックになり、リークが発生することがわかります
    残りのアイテムまたは残りのすべてのアイテムをドロップする

    1. たとえばアークをリークした場合、予期しない副作用を伴う非常に大きなリークになる可能性があります

    2. あなたがアークとウィークを持っているなら、非常に驚​​くべきことがあります

たぶんもっと良い解決策があります。
どちらを使用しても、実装後はrustdocに文書化する必要があります。

@dathinab

ええ、私はすでにしました

漏れは望ましくありませんが、問題はなく、ここで回避するのは難しいかもしれませんが、ダブルドロップは絶対にありません。 いいキャッチ! この安全性の問題について別の問題を報告しますか?

drain_filterは、コレクションからアイテムを削除するたびに再割り当てを行いますか? または、再割り当ては1回だけで、C ++のstd::removeとstd::erase (ペアで)のように機能しますか? 割り当てが1つだけなので、このような動作をお勧めします。要素をコレクションの最後に配置し、削除して適切なサイズに縮小します。

また、なぜtry_drain_filterがないのですか? Optionタイプを返し、停止する必要がある場合はNone値を返しますか? 私は非常に大きなコレクションを持っており、必要なものをすでに手に入れているときに続けるのは無意味です。

前回コードを読んだときは、次のようなことをしました:「ギャップ」を作成しました
要素を移動し、排出されていない要素を移動する場合
ギャップが見つかったら、ギャップの始まりです。 これで、しなければならない各要素
移動(配列の外または新しい場所への移動)は1回だけ移動されます。
また、たとえばremoveのように、再割り当てされません。 解放された部分は
未使用容量の一部。

2018ĺš´8月10日金曜日、07:11 Victor [email protected]は次のように書いています:

drain_filterは、アイテムを削除するたびに再割り当てを行いますか?
コレクション? または、再割り当てを1回だけ行い、std :: removeのように機能します
およびstd :: Erase(ペアで)C ++で? 私はそのような行動を好むので
正確に1つの割り当て:要素をコレクションの最後に配置するだけです
次に、適切なサイズに縮小して削除します。

また、なぜtry_drain_filterがないのですか? オプションタイプを返すもの、および
やめるべきなら価値はありませんか? 私は非常に大きなコレクションを持っています、そしてそれは
私が必要なものをすでに手に入れているとき、私のために続けることは無意味です。

—
あなたが言及されたので、あなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/rust-lang/rust/issues/43244#issuecomment-411977001 、
またはスレッドをミュートします
https://github.com/notifications/unsubscribe-auth/AHR0kdOZm4bj6iR9Hj831Qh72d36BxQSks5uPRYNgaJpZM4OY1me
。

@rustonautありがとう。 try_drain_filterについてどう思いますか? :)

PSコードも調べたところ、希望どおりに機能しているように見えます。

反復時に要素ごとに進むため、通常は期待できます
ドロップされたときに反復を停止しますが、それも
紛らわしいので、落としたときに実際に最後まで排出されます。
(これは、二重のパニックなどの可能性のあるフードを劇的に増加させます
そのように)。

したがって、あなたのように動作する試用版を入手する可能性はほとんどありません。
予想。

公平を期すために、反復が実際に混乱する可能性がある場合は、早期打ち切りを行う
いくつかの状況、例えばthing.drain_where(|x| x.is_malformed()).any(|x| x.is_dangerus())は、すべての不正な形式のものを排出するのではなく、
これも危険です。 (現在の実装はすべての不正な形式を排出します
ドロップ時に排水を続けることによって)。

2018ĺš´8月10日金曜日、10:52 Victor [email protected]は次のように書いています:

@rustonauthttps ://github.com/rustonautありがとう。 あなたの意見は何ですか
try_drain_filterについて? :)

—
あなたが言及されたので、あなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/rust-lang/rust/issues/43244#issuecomment-412020490 、
またはスレッドをミュートします
https://github.com/notifications/unsubscribe-auth/AHR0kcEMHCayqvhI6D4LK4ITG2x5di-9ks5uPUnpgaJpZM4OY1me
。

これはもっと用途が広いと思います:

fn drain_filter_map<F>(&mut self, f: F) -> DrainFilterMap<T, F> where F: FnMut(T) -> Option<T>

こんにちは、私はHashMapのdrain_filter機能を検索していましたが、存在しません。これを見つけたときに問題を開くように求められました。 別の問題にする必要がありますか?

現在、これを安定化から妨げているものはありますか? 上で報告したように、それはまだほどけていますか?

これはかなり小さな機能のように思えますが、1年以上使用されていません。

これはもっと用途が広いと思います:

fn drain_filter_map<F>(&mut self, f: F) -> DrainFilterMap<T, F> where F: FnMut(T) -> Option<T>

これはdrain_filterとmapの構成よりも優れているとは思いません。

上で報告したように、それはまだほどけていますか?

反復が早期に停止した場合に一致するすべての要素を排出しないか、 DrainFilterのドロップでフィルタリングと最後まで排出する場合は、巻き戻し中にパニックが発生する可能性があるか、という難しい選択があるようです。
この機能はどちらにしても問題がたくさんあるので、安定させるべきではないと思います。

巻き戻し時の動作が異なることに特に問題はありますか?

可能性:

  • 正常に完了するまで実行できますが、アンワインド時に一致する要素を残します(残りのすべての要素が一致しないと見なされるようにします)。
  • 正常に完了するまで実行できますが、巻き戻し時に最後に書き込まれた位置の後でベクトルを切り捨てます(残りのすべての要素が一致すると見なされるようにします)。
  • 正常に完了するまで実行できますが、アンワインド時にベクトルを長さ0に切り捨てます。

私は考えることができる最も分かりやすい反論はつまりdrop通常によって提供さ不変に依存したコードdrain_filter最後に、VECの要素が正確にものがあることになる、ということ(条件に失敗した場合)は、 drain_filterを使用するコード(ほとんどの場合、通常の安全なコード)から任意に離れている可能性があります。

しかし、そのような場合があったとしましょう。このコードは、ユーザーがどのように記述してもバグがあります。たとえば、逆になってスワップが削除された要素を含む命令ループを作成し、条件がパニックになり、ドロップがフィルター条件がfalseであることに大きく依存している場合、コードにはまだバグがあります。 drop_filterような関数があり、そのドキュメントでこのエッジケースに注意を向けることができるので、比較すると改善のように思えます。

また、おかげで、スレッドの前半に投稿されたこの遊び場の例を見つけました。これは、現在の実装がまだ要素をダブルドロップしていることを示しています。 (だから、そのままでは絶対に安定させることはできません!)

健全性のバグについて別の問題を開く価値があるかもしれませんか? その後、それはI-unsoundとしてマークすることができます。

私の知る限り、あなたはマークを付けることも、ダブルパニックのように不健全にすることもできません。
中止するので非常に不便です。 また、私が覚えている限りでは
ダブルパニックの可能性はバグではなく、暗黙のうちに動作しますが
故意に選ばれました。

オプションは基本的に次のとおりです。

  1. ドロップ時に完了するまで実行しないでください。
  2. 完了するまで実行しますが、二重のパニックのために中止する可能性があります
  3. パニック中にドロップ時にすべての「チェックされていない」要素をドロップしてください。
  4. パニック中のドロップで完了しないでください。

問題は次のとおりです。

  1. =>多くのユースケースで予期しない動作。
  2. =>述語がパニックになる可能性がある場合、特にそれを使用する場合、予期しない中止
    要素を「ただ」削除する、つまり、返されたイテレータを使用しない。
  3. =>パニックの立ち寄りと外での予期しない違い。 ただ
    ドロップ関数で誰かを_using_drain_filterと考えてください。
  4. => 3を参照してください。

または、言い換えると、1。通常のユースケースでは混乱を招き、2。
述語がパニックになる可能性がある場合に中止する3.、4。 あなたが本当にできないようにそれを作ります
ドロップメソッドで使用しますが、そこで使用する関数はどのようになりますか
内部では使用しません。

このオプションの結果として3.、4。 行きません。 オプション2の問題は次のとおりです。
1.なので2.のものよりも珍しいものが選ばれました。

私見では、実行されないdrain + drain_filterAPIを持っている方が良いでしょう
ドロップで完了するまで+実行される一般的なイテレータコンビネータ
ドロップ時の完了+イテレータを完了するがすべてをドロップするメソッド
残りのアイテム。 問題は、ドレインがすでに安定していることです。イテレータ
コンビネータは、内部イテレータとドレインを融合する必要があるため、オーバーヘッドを追加します
最も適切な名前ではない可能性があります。

2019ĺš´5月20日月曜日、09:28 [email protected]は次のように書いています。

健全性のバグについて別の問題を開く価値があるかもしれませんか? ができる
次に、I-unsoundとしてマークされます。

—
あなたが言及されたので、あなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/rust-lang/rust/issues/43244?email_source=notifications&email_token=AB2HJEL7FS6AA2A2KF5U2S3PWJHK7A5CNFSM4DTDLGPKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKT
またはスレッドをミュートします
https://github.com/notifications/unsubscribe-auth/AB2HJEMHQFRTCH6RCQQ64DTPWJHK7ANCNFSM4DTDLGPA
。

ただし、ダブルドロップは不健全です。

健全性の問題のために#60977を作成しました

おかげで、私はダブルドロップをダブルパニックとして読むのは愚かだと感じています:man_facepalming:。

  1. =>パニックの立ち寄りと外での予期しない違い。 ただ
    ドロップ関数で誰かを_using_drain_filterと考えてください。

3.、4。 あなたが本当にできないようにそれを作ります
ドロップメソッドで使用しますが、そこで使用する関数はどのようになりますか
内部では使用しません。

これは私にとってまだ大したことではありません。

誰かがパニックになる可能性のある条件でDropimplでdrain_filterを使用する場合、問題は彼らがdrain_filterを使用することを選択したことではありません。 同様に、メソッドが内部drain_filterを使用するかどうかは関係ありません。

申し訳ありませんが、返信が早すぎました。 私はあなたが今何を意味しているのかわかると思います。 これをもう少し詳しく説明します。

さて、あなたの議論は、$ drain_filterを使用するdrop impl内のコードは、アンワインド中に実行された場合、不思議なことに壊れることがあるということです。 (これはdrain_filterのパニックではなく、 drain_filterを実行させる他のコードのパニックについてです):

impl Drop for Type {
    fn drop(&mut self) {
        self.vec.drain_filter(|x| x == 3);

        // Do stuff that assumes the vector has no 3's
        ...
    }
}

このドロップimplは、アンワインド中に突然誤動作します。

これは確かに、現在のスレッドがパニックに陥っているかどうかをDrainFilterに素朴に検出させることに対する説得力のある議論です。

drain_filterの用語は、私にとってそれ以来最もよく使われています。 すべてのアイテムを削除するためのdrainがすでにあるとすると、削除するアイテムを選択すると、 filterになります。 一緒に組み合わせると、命名は非常に一貫しているように感じます。

ダブルパニックの健全性の問題の修正により、述語にパニックが発生した場合でも、残りのVecはそのまま残ります。 アイテムはギャップを埋めるためにバックシフトされますが、それ以外の場合はそのままになります(そして、巻き戻し中にVec::dropを介してドロップされるか、パニックが発生した場合はユーザーによって処理されます)。

vec::DrainFilterを途中でドロップすると、完全に消費されたかのように動作し続けます(これはvec::Drainと同じです)。 vec::Drain::dropの間に述語がパニックになった場合、残りのアイテムは通常どおりバックシフトされますが、それ以上のアイテムはドロップされず、述語は再度呼び出されません。 述語のパニックが通常の消費中に発生するか、 vec::DrainFilterがドロップされたときに発生するかに関係なく、基本的に同じように動作します。

健全性の穴の修正が正しいと仮定すると、この機能の安定化を妨げている他の要素は何ですか?

Vec::drain_filterはLinkedList::drain_filterとは独立して安定させることができますか?

drain_filterの用語の問題は、$ drain_filterの場合、実際には戻り値と元のコレクションの2つの出力があり、どちら側が「フィルター処理されている」かが明確ではないことです。アイテムは続きます。 filtered_drainでさえもう少し明確だと思います。

「フィルタリングされた」アイテムがどちら側にあるのかは明確ではありません

Vec::drainは前例を設定します。 _削除_するアイテムの範囲を指定します。 Vec::drain_filterも同じように動作します。 _削除_するアイテムを特定します。

そして、「フィルタリングされた」アイテムがどちら側に行くのかは本当に明確ではありません

私はこれがIterator::filterにもすでに当てはまると考えているので、それを使用するたびにドキュメントを確認したり、テストを作成したりする必要があることに辞任しました。 drain_filterでも同じことを気にしません。


Rubyのselectとreject用語を選んでおけばよかったのですが、その船はずっと前から出航しています。

これについて何か進展はありますか? 名前はまだ途方に暮れている唯一のものですか?

これが安定するのを妨げるものはありますか?

DrainFilterのDrop implは、デストラクタのいずれかが述語のパニックにパニックを起こした場合にアイテムをリークするようです。 これがhttps://github.com/rust-lang/rust/issues/52267の根本的な原因です。 そのようなAPIを安定させたいと確信していますか?

気にしないでください、それはどうやらhttps://github.com/rust-lang/rust/pull/61224によって修正されました。

この追跡の問題についても少し詳しく説明します。この機能が安定してヒットするのを楽しみにしています:Dブロッカーはありますか?

cc @ Dylan-DPC

$#$ 2 drain #$のように、$ drain_filterにRangeBoundsパラメータを使用させることに賛成または反対の決定が下されたことがありますか? ベクトル全体をフィルタリングする場合は、 ..渡すのは簡単なように思われるので、おそらくそれを追加することに賛成です。

これはもっと用途が広いと思います:

fn drain_filter_map<F>(&mut self, f: F) -> DrainFilterMap<T, F> where F: FnMut(T) -> Option<T>

ほとんどの場合、より一般的なバージョンは、 Iterator::{filter_map, find_map, map_while}のように、 FnMut(T) -> Option<U>かかります。 このようにfilter_mapを一般化する価値があるかどうかはわかりませんが、検討する価値があるかもしれません。

@jethrogbが上で提案した方法を多かれ少なかれ正確に探していたので、ここに到着しました。

fn drain_filter_map<F>(&mut self, f: F) -> DrainFilterMap<T, F>
    where F: FnMut(T) -> Option<T>

私が考えていたもの(頭の中でupdateと呼んでいた)との唯一の違いは、排出イテレータを返すように考えていなかったということでしたが、それは明らかな改善のようです。所定の場所でのアイテムの更新、既存のアイテムの削除、および削除されたアイテムの呼び出し元への配信をサポートする、単一の合理的に単純なインターフェース。

ほとんどの場合、より一般的なバージョンでは、$ Iterator::{filter_map, find_map, map_while}のように、 FnMut(T) -> Option<U> $が必要になります。 このようにfilter_mapを一般化する価値があるかどうかはわかりませんが、検討する価値があるかもしれません。

関数が生成する値はVec<T>に格納されるため、関数はOption<T>を返す必要があります。

@ johnw42フォローするかどうかはわかりませんが、 Someの値はイテレータによってすぐに生成されませんか?

実際、その関数の入力値は、排出したくない場合に備えて、 T $ではなく&Tまたは&mut Tである必要があると思います。 または、関数はFnMut(T) -> Result<U, T>のようなものである可能性があります。 しかし、アイテムタイプが他のタイプにできない理由がわかりません。

@timvermeulen私たちは提案を別の方法で解釈していると思います。

私の解釈では、 Someの値はVecに格納され、$ Noneは、元の値がイテレータによって生成されることを意味します。 これにより、クロージャーはその場で値を更新するか、 Vecから値を移動することができます。 これを書いているときに、 drain_filterの観点から実装できるため、私のバージョンでは実際には何も追加されないことに気付きました。

fn drain_filter_map<F>(
    &mut self,
    mut f: F,
) -> DrainFilter<T, impl FnMut(&mut T) -> bool>
where
    F: FnMut(&T) -> Option<T>,
{
    self.drain_filter(move |value| match f(value) {
        Some(new_value) => {
            *value = new_value;
            false
        }
        None => true,
    })
}

逆に、 drain_filterの結果をマッピングするのと同じであるため、あなたの解釈はあまり役に立たないと思っていましたが、私はそれを書き込もうとしましたが、同じ理由filter_mapはそうではありません。 t filterの後にmapを呼び出すのと同じです。

@ johnw42ああ、ええ、あなたはNoneが、値がVecに留まるべきであることを意味したいと思ったのです。

したがって、人間工学的ではないかもしれませんが、 FnMut(T) -> Result<U, T>が最も一般的であるように思われます。 FnMut(&mut T) -> Option<U>は実際にはオプションではありません。これは、一般的なケースではTの所有権を取得できないためです。 FnMut(T) -> Result<U, T>とFnMut(&mut T) -> boolが唯一の選択肢だと思います。

@timvermeulen私は以前に「最も一般的な」解決策について何かを言い始めました、そして私の「最も一般的な」解決策はあなたのものとは異なりました、しかし私は同じ結論に達しました。実際には使いたくないでしょう。

上級ユーザーが上に優れた抽象化を構築できる非常に一般的な方法を作成することには、まだある程度の価値があるかもしれませんが。 私の知る限り、 drainとdrain_filterのポイントは、それらが特に人間工学に基づいたAPIであるということではなく、そうではありませんが、練習してください。これは、多くの冗長な動きがなければ(または安全でない操作を使用して)他の方法で書くことはできません。

drainを使用すると、次の優れたプロパティを取得できます。

  • 連続する要素の選択はすべて削除できます。
  • 削除されたアイテムを削除するのは、返されたイテレータを破棄するのと同じくらい簡単です。
  • 削除されたアイテムを削除する必要はありません。 発信者はそれぞれを個別に調べて、それをどうするかを選択できます。
  • Vec CopyまたはCloneをサポートする必要はありません。
  • Vec自体のメモリを割り当てたり解放したりする必要はありません。
  • Vecに残っている値は、最大で1回だけ移動されます。

drain_filterを使用すると、連続した範囲だけでなく、 Vecから任意のアイテムのセットを削除することができます。 あまり明白ではない利点は、連続する範囲のアイテムが削除された場合でも、 drainに渡す範囲を見つけるために、 Vecを介して別のパスを作成する必要がある場合、 drain_filterはパフォーマンスを向上させることができることです。その内容を検査するためのVec 。 クロージャの引数は&mut Tであるため、 Vecに残っているアイテムを更新することも可能です。 やったー!

drain_filterのようなインプレース操作で実行したい他のいくつかのことを次に示します。

  1. イテレータを介して返す前に、削除されたアイテムを変換します。
  2. 操作を早期に中止し、エラーを報告してください。
  3. アイテムを削除したり、そのままにしておく(場合によっては変更する)のではなく、アイテムを削除して新しい値(元の値のクローンなど)に置き換える機能を追加します。
  4. 削除したアイテムを複数の新しいアイテムと交換します。
  5. 現在のアイテムで何かをした後、次のアイテムをいくつかスキップして、そのままにしておきます。
  6. 現在のアイテムで何かをした後、最初にそれらを検査せずに、いくつかの次のアイテムを削除します。

そして、これがそれぞれの私の分析です:

  1. 呼び出し元はイテレータによって返されるアイテムをすでに変換できるため、これは有用なものを追加しません。 また、 Vecから削除された後にのみ値を呼び出し元に配信することにより、値のクローン化を回避するというイテレーターの目的も無効になります。
  2. 早期に中絶できることで、場合によっては漸近的な複雑さが改善される可能性があります。 APIの一部としてエラーを報告しても、キャプチャされた変数をクロージャで変更することで同じことができるため、新しいことは何も追加されません。また、イテレータが実行されるまで値が生成されないため、その方法も明確ではありません。消費されました。
  3. これは、私が思うに、いくつかの本当の一般性を追加します。
  4. これは私が最初に「キッチンシンク」オプションとして提案しようとしていたものですが、1つのアイテムを複数のアイテムに置き換えることができる場合、 Vecのアイテムのプロパティを維持することは不可能であるため、役に立たないと判断しました。 Vecを構築するよりも必ずしも効率的であるとは限らず、さらに悪い場合があります。
  5. これは、 Vecが、検査を停止せずにアイテムの大部分をスキップできるように編成されている場合に役立ちます。 以下のサンプルコードには含めませんでしたが、クロージャーを変更して、続行する前にジャンプする次のアイテムの数を指定する追加のusizeを返すことでサポートできます。
  6. これは項目5を補完するように見えますが、削除された項目をイテレータで返す必要がある場合はあまり役に立ちません。 ただし、削除するアイテムにデストラクタがなく、単にそれらを非表示にしたい場合は、それでも有用な最適化になる可能性があります。 その場合、上記のusizeは、 Keep(usize)またはDrop(usize)の選択に置き換えることができます(ここで、 Keep(0)とDrop(0)は意味的にです)同等)。

クロージャーに4つのケースの列挙型を返すようにすることで、基本的なユースケースをサポートできると思います。

fn super_drain(&mut self, f: F) -> SuperDrainIter<T>
    where F: FnMut(&mut T) -> DrainAction<T>;

enum DrainAction<T>  {
    /// Leave the item in the Vec and don't return anything through
    /// the iterator.
    Keep,

    /// Remove the item from the Vec and return it through the
    /// iterator.
    Remove,

    /// Remove the item from the Vec, return it through the iterator,
    /// and swap a new value into the location of the removed item.
    Replace(T),

    /// Leave the item in place, don't return any more items through
    /// the iterator, and don't call the closure again.
    Stop,
}

私が提示したい最後のオプションの1つは、イテレーターを完全に取り除き、アイテムを値でクロージャーに渡し、呼び出し元がアイテムをそれ自体に置き換えることでアイテムを変更しないようにすることです。

fn super_drain_by_value(&mut self, f: F)
    where F: FnMut(T) -> DrainAction<T>;

enum DrainAction<T>  {
    /// Don't replace the item removed from the Vec.
    Remove,

    /// Replace the item removed from the Vec which a new item.
    Replace(T),

    Stop,
}

このアプローチはシンプルで、すべて同じユースケースをサポートしているため、私はこのアプローチが最も気に入っています。 潜在的な欠点は、ほとんどのアイテムが所定の位置に残っている場合でも、クロージャーのスタックフレームに移動して、クロージャーが戻ったときに元に戻す必要があることです。 クロージャーが引数を返すだけで、これらの動きが確実に最適化されることを願っていますが、それが信頼できるものかどうかはわかりません。 他の人がそれを含めるのに十分気に入っている場合は、 updateが適切な名前になると思います。私が間違っていなければ、シングルパスのインプレース更新を実装するために使用できるからです。 Vecの内容。

(ところで、この号のタイトルを見るまでは、上記のリンクリストを完全に忘れていたので、完全に無視しました。リンクリストについて話していると、ポイント4〜6の分析が変わるので、別のことを考えます。 APIはリンクリストに適しています。)

@ johnw42 mem::replaceまたはmem::takeを使用して、変更可能な参照がある場合は、すでに3.を実行できます。

@ johnw42 @jplatte

(3)返されたIteratorのアイテムタイプをコレクションのアイテムタイプと異なるものにする場合にのみ、実際に意味があります。
(3)は特殊なケースです。これは、 Iteratorから要素を返し、新しい要素をVecに戻すためです。

Bikeshedding: Replace(T)の関数を逆にして、 PushOutの内部値をイテレータに「送信」する目的で、 PushOut(T)に置き換えます。 Vecの元の(パラメータ)アイテム。

Stopは、おそらくErrorタイプを返す機能を備えている必要があります(またはtry_foldのように機能しますか?)。

昨夜super_drain_by_value関数を実装し、いくつかのことを学びました。

見出しの項目は、少なくともVecの場合、ここで話していることはすべて「あるといい」カテゴリにあるはずです(根本的に新しい機能を追加するのではなく)。 Vecは、既存のAPIを介して、基本的にすべてのフィールドへの直接の読み取りおよび書き込みアクセスをすでに提供しています。 安定版では、空のVecのポインタフィールドを監視できないという小さな警告がありますが、不安定なinto_raw_partsメソッドはその制限を取り除きます。 私たちが本当に話しているのは、安全なコードによって効率的に実行できる一連の操作を拡張することです。

コード生成に関しては、簡単なケース( Vec<i32>など)では、 Vecに出入りする冗長な移動は問題ではなく、その呼び出しは次のような単純なものになることがわかりました。ノーオペレーションまたはVecの切り捨ては、単純すぎて改善できないコードに変換されます(それぞれゼロ命令と3命令)。 悪いニュースは、より難しいケースでは、私の提案とdrain_filterメソッドの両方が多くの不要なコピーを実行し、メソッドの目的を大きく損なうことです。 Vec<[u8; 1024]>に対して生成されたアセンブリコードを調べてこれをテストしました。どちらの場合も、各反復には、最適化されていないmemcpyへの2つの呼び出しがあります。 no-op呼び出しでさえ、バッファ全体を2回コピーすることになります。

人間工学の観点から見ると、一見するとかなり見栄えのする私のAPIは、実際にはそれほど良くありません。 クロージャから列挙値を返すことは、最も単純な場合を除いてかなり冗長になり、クロージャが列挙値のペアを返すという私が提案したバリアントはさらに醜いです。

また、 DrainAction::Stopを拡張して、 super_drain_by_value Option<R> #$として返されるR値を保持しようとしましたが、これはさらに悪いことです。戻り値が不要な場合、コンパイラーはRを推測できず、使用していない値のタイプに明示的に注釈を付ける必要があります。 このため、クロージャーから呼び出し元にsuper_drain_by_valueの値を返すことをサポートするのは良い考えではないと思います。 これは、 loop {}構造が値を返すことができる理由とほぼ同じですが、他の種類のループは()と評価されます。

一般性に関しては、実際には2つのケースが早期終了することに気付きました。1つは残りのVecが削除され、もう1つはそのまま残されます。 早期終了が値を持たない場合(私がそうすべきではないと思うので)、意味的にKeep(n)またはDrop(n)を返すことと同等になります。ここで、 nはまだ検討されていない項目。 ただし、 Keep / Dropを使用する場合と比較して、より単純なコードパスを使用する方が使いやすいため、早期終了は別のケースとして扱う必要があると思います。

APIをもう少し使いやすくするために、クロージャーが()を返し、ヘルパーオブジェクト(ここでは「アップデーター」と呼びます)を渡すことをお勧めします。 Vecの各要素を検査し、それに何が起こるかを制御するために使用されます。 これらのメソッドには、 borrow 、 borrow_mut 、 takeなどの使い慣れた名前を付け、 keep_next(n)やdrop_remainder()などの追加のメソッドを付けることができます。 この種のAPIを使用すると、単純なケースではクロージャーがはるかに単純になり、複雑なケースではクロージャーが複雑になりません。 アップデーターのメソッドのほとんどが値でselfを取るようにすることで、呼び出し元がtakeを複数回呼び出す、または後続の反復で何をするかについて矛盾する指示を与えるなどのことを簡単に防ぐことができます。

しかし、私たちはまだもっとうまくやることができます! 今朝、よくあることですが、この問題は関数型言語で確実に解決された問題に類似しており、類似の解決策で解決できることに気づきました。 私は「ジッパー」APIについて話しています。最初はこの短い論文でOCamlのサンプルコードで説明され、ここではHaskellコードと他の関連する論文へのリンクで説明されています。 ジッパーは、データ構造をトラバースし、特定のデータ構造がサポートする操作を使用して「インプレース」で更新するための非常に一般的な方法を提供します。 別の見方をすれば、ジッパーは、特定のタイプのデータ構造に対して操作を実行するための追加のメソッドを備えた一種のターボチャージャー付きイテレーターです。

Haskellでは、ジッパーをモナドにすることで「インプレース」セマンティクスを取得します。 Rustでは、ジッパーにVec mut参照を保持させることで、ライフタイムを使用して同じことを行うことができます。 Vecのジッパーは、上記で説明したアップデーターと非常に似ていますが、クロージャーに繰り返し渡すのではなく、 Vecがそれ自体にジッパーを作成するメソッドを提供するだけです。ジッパーは、存在する限り、 Vecに排他的にアクセスできます。 次に、呼び出し元は、配列をトラバースするループを作成し、各ステップでメソッドを呼び出して、現在のアイテムをVecから削除するか、そのままにします。 ジッパーを使用するメソッドを呼び出すことで、早期終了を実装できます。 ループは呼び出し元の制御下にあるため、ループの各反復で複数のアイテムを処理したり、ループをまったく使用せずに固定数のアイテムを処理したりすることが可能になります。

これは、ジッパーが実行できることのいくつかを示す非常に工夫された例です。

/// Keep the first 100 items of `v`.  In the next 100 items of `v`,
/// double the even values, unconditionally keep anything following an
/// even value, discard negative values, and move odd values into a
/// new Vec.  Leave the rest of `v` unchanged.  Return the odd values
/// that were removed, along with a boolean flag indicating whether
/// the loop terminated early.
fn silly(v: &mut Vec<i32>) -> (bool, Vec<i32>) {
    let mut odds = Vec::new();
    // Create a zipper, which get exclusive access to `v`.
    let mut z = v.zipper();
    // Skip over the first 100 items, leaving them unchanged.
    z.keep_next(100);
    let stopped_early = loop {
        if let Some(item /* &mut i32 */) = z.current_mut() {
            if *item < 0 {
                // Discard the value and advance the zipper.
                z.take();
            } else if *item % 2 == 0 {
                // Update the item in place.
                *item *= 2;

                // Leave the updated item in `v`.  This has the
                // side-effect of advancing `z` to the next item.
                z.keep();

                // If there's another value, keep it regardless of
                // what it is.
                if z.current().is_some() {
                    z.keep();
                }
            } else {
                // Move an odd value out of `v`.
                odds.push(z.take());
            }
            if z.position() >= 200 {
                // This consumes `z`, so we must break out of the
                // loop!
                z.keep_rest();
                break true;
            }
        } else {
            // We've reached the end of `v`.
            break false;
        }
    }
    (stopped_early, odds)

    // If the zipper wasn't already consumed by calling
    // `z.keep_rest()`, the zipper is dropped here, which will shift
    // the contents of `v` to fill in any gaps created by removing
    // values.
}

比較のために、 drain_filterを使用したほぼ同じ関数を示しますが、それは早期に停止するふりをするだけです。 ほぼ同じ量のコードですが、クロージャーによって返される値の意味が明確ではなく、可変のブールフラグを使用して、ある反復から次の反復に情報を伝達するため、私見では読みにくくなっています。バージョンは、制御フローで同じことを実現します。 削除されたアイテムは常にイテレーターによって生成されるため、出力から負の値を削除するために別のフィルターステップが必要です。つまり、1つではなく2つの場所で負の値をチェックする必要があります。 vの位置を追跡しなければならないのもちょっと醜いです。 drain_filterの実装にはその情報がありますが、呼び出し元はその情報にアクセスできません。

fn drain_filter_silly(v: &mut Vec<i32>) -> (bool, Vec<i32>) {
    let mut position: usize = 0;
    let mut keep_next = false;
    let mut stopped_early = false;
    let removed = v.drain_filter(|item| {
        position += 1;
        if position <= 100 {
            false
        } else if position > 200 {
            stopped_early = true;
            false
        } else if keep_next {
            keep_next = false;
            false
        } else if *item >= 0 && *item % 2 == 0 {
            *item *= 2;
            false
        } else {
            true
        }
    }).filter(|item| item >= 0).collect();
    (stopped_early, removed)
}

@ johnw42以前の投稿で、 scanmutクレート、特にRemover構造体を思い出しました。あなたが言及した「ジッパー」の概念は、非常に似ているようです。 これは、完全な制御が必要なときにクロージャを使用する方法よりもはるかに人間工学的であるように思われます。

いずれにせよ、これはおそらくdrain_filterを安定させる必要があるかどうかとはあまり関係がありません。これは、後でいつでも内部を交換できるためです。 drain_filter自体は、非常に便利であるため、常に非常に便利です。 安定化の前に私がまだ見たい唯一の変更は、 RangeBoundsパラメーターです。

@timvermeulen RangeBoundsパラメーターを追加するのは理にかなっていると思いますが、現在のクロージャー署名( F: FnMut(&mut T) -> bool )は保持します。
排出された要素は、 filter_mapまたは必要なものでいつでも後処理できます。
(私にとって、 retainは要素を許可しないため、クロージャーが要素の変更を許可することが非常に重要です(この間違いが発見される前に安定化されました)。

ええ、それは便利さと有用性の間の完璧なバランスのようです。

@timvermeulenええ、私はメイントピックからかなり離れていました。

元のトピックに関連していることに気付いたのは、クロージャーの戻り値が何を意味するのかを思い出すのが難しいということです。つまり、アイテムを保持するか削除するかを示しているのでしょうか。 v.drain_filter(p)はv.iter().filter(p)と同等であり、副作用があることをドキュメントが指摘しておくと便利だと思います。

filterの場合、ブール値を使用することは明確にするためにまだ理想的ではありませんが、これは非常によく知られている関数であり、述語が「これを保持する必要がありますか?」という質問に答えるのは少なくともある程度直感的です。 「これを捨てるべきか」ではなく drain_filterの場合、イテレータの観点から考えると同じロジックが適用されますが、入力Vecの観点から考えると、質問は「すべきではない」になります。持っていて?"

正確な表現については、 filterパラメーターの名前をpredicateに変更し( Iterator::filterと一致するように)、説明のどこかにこの文を追加することを提案します。

predicateの戻り値がどのように使用されるかを覚えておくと、 drain_filterはIterator::filterと同じですが、選択したものを削除するという追加の副作用があることに注意してください。 selfからのアイテム。

@ johnw42はい、良い点です。 drain_whereのような名前の方がはるかにわかりやすいと思います。

バイクシェディングに名前を付ける場合は、 すべてのコメントを読んだことを確認してください。 隠されたものでさえ。 多くのバリアントがすでに提案されています。たとえば、 https ://github.com/rust-lang/rust/issues/43244#issuecomment -331559537

しかし…それはdraintain()という名前でなければなりません! これほど美しい名前は他にありません!

私はこの問題に非常に興味があり、スレッド全体を読んだので、これが安定するのを助けることを期待して、みんなが言ったことを要約しようと思うかもしれません。 途中で自分のコメントをいくつか追加しましたが、できるだけ中立に保つようにしています。

ネーミング

これが私が提案した名前の意見のない要約です:

  • drain_filter :現在の実装で使用されている名前。 filter_mapなどの他の名前と一致します。 drain().filter()に類似しているという利点がありますが、より多くの副作用があります。
  • drain_where : trueが_out_を排出するか、_in_をフィルタリングするかを示すという利点があります。これは、他の名前では覚えにくい場合があります。 _where接尾辞のstdには前例はありませんが、同様の接尾辞の前例はたくさんあります。
  • whereはすでにキーワードであるため、 drain().where()のバリエーション。
  • drain_retain : retainと一致しますが、 retainとdrainは、クロージャによって返されるブール値の解釈が逆であるため、混乱する可能性があります。
  • filtered_drain
  • drain_if
  • drain_when
  • remove_if

パラメーター

drainとの一貫性を保つために、範囲引数を追加する価値があるかもしれません。

FnMut(&mut T) -> boolとFnMut(T) -> Result<T, U>の2つのクロージャー形式が提案されています。 後者はより柔軟性がありますが、不器用でもあります。

ブール条件( trueは「 Vecを維持する」を意味します)を逆転させてretainと一致させることが議論されましたが、 drainと一致しません。 trueは、「 Vecからの排出」を意味します)。

巻き戻し

フィルタクロージャがパニックになると、 DrainFilterイテレータが削除されます。 その後、イテレータはVecの排出を終了する必要がありますが、そのためには、フィルタクロージャを再度呼び出す必要があり、二重のパニックが発生するリスクがあります。 いくつかの解決策がありますが、それらはすべて妥協点です。

  • ドロップで排水を終了しないでください。 これは、 findやallなどのアダプターで使用するとかなり直感に反します。 さらに、イテレータが怠惰であるため、 v.drain_filter(...);イディオムが役に立たなくなります。

  • 常にドロップで排水を終了します。 これは二重のパニック(中絶につながる)のリスクがありますが、動作の一貫性を保ちます。

  • 現在巻き戻されていない場合にのみ、ドロップ時に排水を終了します。 これにより、二重のパニックが完全に修正されますが、 drain_filterの動作は予測できなくなります。デストラクタにDrainFilterをドロップすると、実際には機能しない場合があります。

  • フィルターの閉鎖がパニックにならなかった場合にのみ、落下時に排水を終了してください。 これは、 drain_filterによって行われた現在の妥協案です。 このアプローチの優れた特性は、フィルタークロージャーの「短絡」でパニックが発生することです。これは、ほぼ間違いなく非常に直感的です。

現在の実装は健全であり、 DrainFilter構造体が削除されている限り、リークすることはありません(ただし、アボートが発生する可能性があります)。 ただし、以前の実装は安全/リークフリーではありませんでした。

ドロップオンドロップ

DrainIterは、ドロップされたときにソースベクトルの排出を終了するか、 nextが呼び出されたときにのみ排出できます(遅延反復)。

ドレーンオンドロップを支持する議論:

  • drainの動作と一致します。

  • all 、 any 、 findなどの他のアダプターとうまく相互作用します。

  • vec.drain_filter(...);イディオムを有効にします。

  • レイジー機能は、 drain_lazyスタイルのメソッドまたはDrainIterぎlazy()アダプターを介して明示的に有効にすることができます(さらに、 Drainでも、下位互換性があるため、メソッドを追加します)。

怠惰な反復を支持する議論:

  • 他のほとんどすべてのイテレータと一貫性があります。

  • 「ドレインオンドロップ」機能は、 DrainIterのアダプターを介して、または一般的なIterator::exhaustingアダプターを介して明示的に有効にすることができます(RFC 2370を参照)。

私はいくつかのものを見逃したかもしれませんが、少なくともスレッドをスキミングするときにそれが新参者に役立つことを願っています。

@negamartin

drain-on-dropオプションでは、イテレータが所有値ではなくアイテムへの参照を返す必要がありませんか? そのため、特定の条件に一致するアイテムを削除して所有権を取得するメカニズムとしてdrain_filterを使用することは不可能になると思います(これは私の最初のユースケースでした)。

現在の実装の動作は、所有されている値を生成しながら、正確にドロップオンドロップであるため、私はそうは思いません。 いずれにせよ、ドレーンオンドロップがどのように要素を借用する必要があるのか​​わからないので、ドレーンオンドロップの意味については2つの異なる考えがあると思います。

明確にするために、ドレインオンドロップとは、イテレータが完全に消費されていない場合の動作のみを意味します。イテレータが完全に消費されていなくても、クロージャに一致するすべてのアイテムをドレインする必要がありますか? それとも、消費された要素までだけで、残りはそのままにしますか?

特に、次の違いがあります。

let mut v = vec![1, 5, 3, 6, 4, 7];
v.drain_where(|e| *e > 4).find(|e| *e == 6);

// Drain-on-drop
assert_eq!(v, &[1, 3, 4]);

// Lazy
assert_eq!(v, &[1, 3, 4, 7]);

アイデアを捨てるだけですが、別の可能なAPIは次のようになります。

 fn drain_filter_into<F, D>(&mut self, filter: F, drain: D)
        where F: FnMut(&mut T) -> bool, 
                   D: Extend<T>
    { ... }

他のオプションよりも柔軟性はありませんが、 DrainFilterがドロップされたときに何をするかという問題を回避します。

私には、このすべてがretain_mut() (クロージャーに渡される可変参照を含むretain() )のように見えなくなっているように感じます。これは、何よりもまず提供することを目的としていました。 。 フィルター付きドレン物の設計に加えて、今のところretain_mut()を提供できますか? それとも私は何かが足りないのですか?

@BartMassey

それが何よりもまず提供することを意図したものです。

そうではないと思います。 私は特にdrain_filterを使用して、フィルター基準に基づいてアイテムの所有権を取得します。 DrainとDrainFilterはアイテムを譲りますが、保持はしません。

@negamartin

明確にするために、私がドレーンオンドロップと言うとき、私はイテレータが完全に消費されていないときの動作のみを意味します

ああ、わかりました。 それは私の間違いです。 私はあなたの定義を誤解しました。 私はそれを「落とされるまで何もvecから削除されない」と解釈しましたが、それは実際には意味がありません。

怠惰な反復を支持する議論

drainと一致している必要があると思います。 Iterator::exhausting RFCは受け入れられず、 drainとdrain_filterが一見反対のドレイン動作をした場合は非常に奇妙になります。

@negamartin

drain_filter:現在の実装で使用されている名前。 filter_mapなどの他の名前と一致します。 drain().filter()に類似しているという利点がありますが、より多くの副作用があります。

これは類似していません(そのため、 retain_mut / drain_filterが必要です):
drain().filter()は、フィルタークロージャーがfalseを返す要素でさえも排出します!

#RFC2870のlibチームによるコメントに小さな行があることに気づきました。

可能性としては、メソッドを汎用化することによるメソッドの「オーバーロード」やビルダーパターンなどがあります。

以前の具象型をまだ受け入れる場合、メソッドをジェネリックにすることは下位互換性がありますか? もしそうなら、私はそれが前進するための最良の方法であると信じています。

(イテレータのメソッドは通常、動作変更者ではなくアダプタであるため、ビルダーパターンはイテレータでは少し直感的ではありません。さらに、前例はありません。たとえば、 chunksとchunks_exactは2つの別個のメソッドです。 、 chunks().exact()コンボではありません。)

いいえ、型推論として私が知っている限り、現在の設計ではありません。
以前は機能していましたが、タイプのあいまいさのために失敗する可能性があります。 デフォルトの遺伝学
関数の型は役に立ちますが、正しく実行するのは非常に難しいです。

2020ĺš´6月12日金曜日、21:21 [email protected]は次のように書いています。

#RFC2870のlibチームによるコメントの小さな行に気づきました
https://github.com/rust-lang/rfcs/pull/2369 :

可能性としては、メソッドを汎用化することでメソッドを「オーバーロード」することが含まれる場合があります。
またはビルダーパターン。

それでも受け入れる場合、メソッドをジェネリックにすることは下位互換性がありますか?
以前のコンクリートタイプ? もしそうなら、それが最善の方法だと思います
前方。

(ビルダーパターンは、イテレータでは少し直感的ではありません。
イテレータは通常、動作変更者ではなく、アダプタです。 その上、あります
たとえば、chunksとchunks_exactは2つの別個のものです。
チャンク().exact()コンボではなく、メソッド。)

—
あなたが言及されたので、あなたはこれを受け取っています。
このメールに直接返信し、GitHubで表示してください
https://github.com/rust-lang/rust/issues/43244#issuecomment-643444213 、
または購読を解除する
https://github.com/notifications/unsubscribe-auth/AB2HJELPWXNXJMX2ZDA6F63RWJ53FANCNFSM4DTDLGPA
。

以前の具象型をまだ受け入れる場合、メソッドをジェネリックにすることは下位互換性がありますか? もしそうなら、私はそれが前進するための最良の方法であると信じています。

いいえ、場合によっては型推論が破られるためです。 たとえばfoo.method(bar.into())は、具体的な型の引数では機能しますが、ジェネリック型の引数では機能しません。

現在実装されているdrain_filterは非常に便利だと思います。 そのまま安定させることができますか? 将来、より良い抽象化が発見されたとしても、それらが導入されるのを妨げるものは何もありません。

drain_filter()で何が起こっても、 retain_mut()追加するには、どのようなプロセスを開始する必要がありますか? 要件は異なっているように思われ、 drain_filter()で何が起こっても、 retain_mut()はまだ有用であると思われます。

@BartMasseyの新しい不安定なライブラリAPIの場合、実装を使用してPRを作成することは問題ないと思います。 この機能を実装するための手順は、 https://rustc-dev-guide.rust-lang.org/implementing_new_features.htmlとhttps://rustc-dev-guide.rust-lang.org/getting-started.htmlにあります。変更をテストするための#building-and-testing-

今日はHashMapとBTreeMapのAPIの違いに苦労してきましたが、さまざまなコレクションが一貫したAPIを維持するために努力することが重要だと思います。センス、この時点で常にそうであるとは限らない何か。

たとえば、String、Vec、HashMap、HashSet、BinaryHeap、VecDequeにはretainメソッドがありますが、LinkedListとBTreeMapにはありません。 retainは、ランダムな削除が非常にコストのかかる操作であるベクトルよりも、LinkedListまたはMapの方が自然な方法のように見えるため、特に奇妙だと思います。

そして、もう少し深く掘り下げると、さらに複雑になります。 HashMap::retainのクロージャには可変参照の値が渡されますが、他のコレクションには不変参照が渡されます(Stringは単純なcharを取得します)。 )。

drain_filterのような新しいAPIが追加され、1 /がretainと重複しているように見え、2 /がすべてのコレクションで同時に安定化されていないことがわかります。

  • HashMap::drain_filterはアップストリームリポジトリにありますが、Rustぎstd AFAIKにはまだ付属していません(ドキュメントには表示されません)
  • BTreeMap::drain_filter 、 Vec::drain_filter 、 LinkedList::drain_filterはRustの標準にありますが、機能はゲートされています
  • VecDeque::drain_filterはまったく存在しないようで、ドキュメントには表示されません
  • String::drain_filterも存在しません

これらの機能を実装するための最良の方法、またはdrain_filter 、 retain 、またはその両方が必要な場合、私は強い意見を持っていませんが、これらのAPIはコレクション間で一貫性を保つ必要があると強く信じています。

そしておそらくもっと重要なことに、異なるコレクションの同様のメソッドは同じセマンティクスを持つ必要があります。 retainの現在の実装がIMOに違反していること。

このページは役に立ちましたか?
0 / 5 - 0 評価