Rust: Masalah pelacakan untuk Vec::drain_filter dan LinkedList::drain_filter

Dibuat pada 15 Jul 2017  ·  119Komentar  ·  Sumber: 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,
    { ... }

Saya yakin ada masalah untuk ini di suatu tempat, tetapi saya tidak dapat menemukannya. Seseorang kutu buku menembak saya untuk mengimplementasikannya. PR masuk.

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

Komentar yang paling membantu

Apakah ada sesuatu yang menghalangi ini untuk distabilkan?

Semua 119 komentar

Mungkin ini tidak perlu menyertakan wastafel dapur, tetapi _bisa_ memiliki parameter range, sehingga seperti superset saluran pembuangan. Ada kekurangan untuk itu? Saya kira menambahkan batas memeriksa jangkauan adalah kelemahannya, itu hal lain yang bisa membuat panik. Tapi drain_filter(.., f) tidak bisa.

Apakah ada kemungkinan ini akan stabil dalam beberapa bentuk dalam waktu yang tidak terlalu lama?

Jika kompiler cukup pintar untuk menghilangkan pemeriksaan batas
dalam kasus drain_filter(.., f) saya akan memilih untuk melakukan ini.

(Dan saya cukup yakin Anda dapat menerapkannya dengan cara
yang membuat kompiler cukup pintar, yang terburuk
jika Anda dapat memiliki "spesialisasi dalam fungsi",
pada dasarnya sesuatu seperti if Type::of::<R>() == Type::of::<RangeFull>() { dont;do;type;checks; return } )

Saya tahu ini adalah bikeshedding sampai batas tertentu, tetapi apa alasan di balik penamaan ini drain_filter daripada drain_where ? Bagi saya, yang pertama menyiratkan bahwa seluruh Vec akan terkuras, tetapi kami juga menjalankan filter atas hasil (ketika saya pertama kali melihatnya, saya berpikir: "bagaimana ini bukan hanya .drain(..).filter() ?"). Yang pertama di sisi lain menunjukkan bahwa kita hanya menguras elemen di mana beberapa kondisi berlaku.

Tidak tahu, tetapi drain_where terdengar jauh lebih baik dan jauh lebih intuitif.
Apakah masih ada kesempatan untuk mengubahnya?

.remove_if telah menjadi saran sebelumnya juga

Saya pikir drain_where menjelaskannya dengan sangat baik. Seperti menguras itu mengembalikan nilai, tetapi tidak menguras/menghapus semua nilai tetapi hanya seperti di mana kondisi yang diberikan benar.

remove_if terdengar sangat mirip dengan versi bersyarat dari remove yang hanya menghapus satu item dengan indeks jika kondisinya benar misalnya letters.remove_if(3, |n| n < 10); menghapus huruf pada indeks 3 jika < 10 .

drain_filter di sisi lain sedikit ambigu, apakah itu drain lalu filter dengan cara yang lebih optimal (seperti filter_map) atau apakah menguras sehingga iterator dikembalikan sebanding dengan iterator filter akan kembali,
dan jika demikian, bukankah seharusnya itu disebut filtered_drain karena filter digunakan secara logis sebelumnya ...

Tidak ada preseden untuk menggunakan _where atau _if di manapun di pustaka standar.

@Gankro apakah ada preseden untuk menggunakan _filter di mana saja? Saya juga tidak tahu apakah itu alasan untuk tidak menggunakan terminologi yang kurang ambigu? Tempat lain di perpustakaan standar sudah menggunakan berbagai sufiks seperti _until dan _while .

Kode "kata setara" dalam komentar tidak benar... Anda harus minus satu dari i di situs "kode Anda di sini", atau hal buruk terjadi.

IMO bukan filter itu masalahnya. Baru saja mencari ini (dan menjadi seorang pemula), drain tampaknya cukup non-standar dibandingkan dengan bahasa lain.

Sekali lagi, hanya dari perspektif pemula, hal-hal yang akan saya cari jika mencoba menemukan sesuatu untuk melakukan apa yang diusulkan masalah ini adalah delete (seperti pada delete_if ), remove , filter atau reject .

Saya sebenarnya _mencari_ filter , melihat drain_filter dan _terus mencari_ tanpa membaca karena drain tampaknya tidak mewakili hal sederhana yang ingin saya lakukan.

Sepertinya fungsi sederhana bernama filter atau reject akan jauh lebih intuitif.

Pada catatan terpisah, saya tidak merasa ini harus mengubah vektor yang dipanggil. Ini mencegah rantai. Dalam skenario yang ideal, seseorang ingin dapat melakukan sesuatu seperti:

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

Dengan implementasi saat ini, apa yang akan digabungkan adalah nilai-nilai yang ditolak.

Saya ingin melihat accept dan reject . Tak satu pun dari yang mengubah nilai aslinya.

Anda sudah dapat melakukan hal berantai dengan filter saja. Inti dari drain_filter adalah untuk mengubah vektor.

@rpjohnst jadi saya mencari di sini , apakah saya kehilangan filter di suatu tempat?

Ya, itu adalah anggota Iterator , bukan Vec .

Drain adalah terminologi baru karena mewakili jenis kepemilikan keempat di Rust yang hanya berlaku untuk wadah, sementara juga secara umum menjadi perbedaan yang tidak berarti di hampir semua bahasa lain (dengan tidak adanya semantik perpindahan, tidak perlu menggabungkan iterasi dan penghapusan ke dalam operasi ""atom" tunggal).

Meskipun drain_filter memindahkan terminologi drain ke ruang yang akan diperhatikan oleh bahasa lain (karena menghindari backshift relevan di semua bahasa).

Saya menemukan drain_filter dalam dokumen sebagai hasil google untuk rust consume vec . Saya tahu bahwa karena kekekalan secara default dalam karat, filter tidak mengkonsumsi data, hanya tidak dapat mengingat cara mendekatinya sehingga saya dapat mengelola memori dengan lebih baik.

drain_where bagus, tetapi selama pengguna mengetahui apa yang dilakukan drain dan filter , saya pikir jelas bahwa metode ini menguras data berdasarkan filter predikat.

Saya masih merasa seolah-olah drain_filter menyiratkan bahwa itu mengalir (yaitu, mengosongkan) dan kemudian menyaring. drain_where di sisi lain terdengar seperti menguras elemen di mana kondisi yang diberikan berlaku (yang dilakukan oleh fungsi yang diusulkan).

Bukankah seharusnya linked_list::DrainFilter mengimplementasikan Drop juga, untuk menghapus elemen yang tersisa yang cocok dengan predikat?

Ya

Mengapa tepatnya menjatuhkan iterator menyebabkannya berjalan sampai akhir? Saya pikir itu perilaku yang mengejutkan untuk seorang iterator dan itu juga bisa, jika diinginkan, dilakukan secara eksplisit. Kebalikan dari hanya mengambil elemen sebanyak yang Anda butuhkan tidak mungkin di sisi lain karena mem::forget ing iterator mengalami amplifikasi kebocoran.

Saya telah sering menggunakan fungsi ini dan saya selalu harus ingat untuk mengembalikan true untuk entri yang ingin saya habiskan, yang terasa kontra-intuitif dibandingkan dengan retain() / retain_mut() .
Pada tingkat logis intuitif, akan lebih masuk akal untuk mengembalikan true untuk entri yang ingin saya simpan, apakah ada orang lain yang merasakan hal ini? (Terutama mengingat retain() sudah bekerja dengan cara ini)
Mengapa tidak melakukannya, dan ganti nama drain_filter() menjadi retain_iter() atau retain_drain() (atau drain_retain() )?
Maka itu juga akan mencerminkan retain() lebih dekat!

Inilah mengapa saya mengusulkan untuk mengganti namanya menjadi
drain_where karena kemudian jelas bahwa:

  1. Ini adalah bentuk saluran air jadi kami menggunakan saluran pembuangan atas nama

  2. Dengan menggunakan where dalam kombinasi dengan drain jelas bahwa
    elemen _where_ predikatnya benar dikuras, yaitu dihapus dan dikembalikan

  3. Ini lebih sinkron dengan nama lain di std, biasanya jika Anda memiliki
    fungsi yang terdiri dari dua predikat Anda dapat menirunya (kira-kira) dengan menggunakan
    fungsi yang mewakili masing-masing predikat dengan cara "dan kemudian", mis
    filter_map dapat ditiru (kira-kira) sebagai filter an then map . Sekarang
    nama menunjukkan itu drain and then filter , tetapi bahkan tidak dekat dengan itu
    karena tidak melakukan pengurasan lengkap sama sekali.

Pada hari Minggu, 25 Februari 2018, 17:04 Boscop [email protected] menulis:

Saya telah sering menggunakan fungsi ini dan saya harus selalu ingat untuk
return true untuk entri yang ingin saya tiriskan, yang terasa
kontra-intuitif dibandingkan dengan retain_mut().
Pada tingkat dasar, akan lebih masuk akal untuk mengembalikan nilai true untuk entri I
mau pertahankan, apakah ada yang merasa seperti ini?
Mengapa tidak melakukannya, dan ganti nama drain_filter() menjadi retain_filter()?


Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/43244#issuecomment-368320990 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AHR0kfwaNvz8YBwCE4BxDkeHgGxLvcWxks5tYYRxgaJpZM4OY1me
.

Tetapi dengan drain_where() penutupan masih harus mengembalikan true untuk elemen yang harus dihapus, yang merupakan kebalikan dari retain() yang membuatnya tidak konsisten..
Mungkin retain_where ?
Tapi saya pikir Anda benar bahwa masuk akal untuk memiliki nama "menguras", jadi saya pikir drain_retain() paling masuk akal: Ini seperti drain() tetapi mempertahankan elemen tempat penutupan kembali true .

Meskipun tidak berubah, bahwa Anda harus kembali benar, itu menjelaskan bahwa
Anda harus melakukannya.

Saya pribadi akan menggunakan:

sebuah. drain_where
B. retain_where
C. retain

Saya tidak akan menggunakan drain_retain .
Tiriskan dan pertahankan berbicara tentang jenis proses yang sama tetapi dari kebalikannya
perspektif, tiriskan berbicara tentang apa yang Anda hapus (dan kembalikan) pertahankan
berbicara tentang apa yang Anda simpan (dalam cara yang sudah digunakan di std).

Saat ini retaim sudah diimplementasikan di std dengan perbedaan utama
bahwa itu membuang elemen sementara drain mengembalikannya, yang membuat
retain (sayangnya) tidak cocok untuk digunakan dalam nama (kecuali jika Anda ingin menelepon
itu retain_and_return atau serupa).

Poin lain yang berbicara tentang pengurasan adalah kemudahan migrasi, yaitu bermigrasi
ke drain_where semudah menjalankan pencarian berbasis kata dan ganti di
kode, sementara mengubahnya untuk mempertahankan akan membutuhkan negasi tambahan
semua predikat/fungsi filter yang digunakan.

Pada Minggu, 25 Februari 2018, 18:01 Boscop [email protected] menulis:

Tetapi dengan drain_where() penutupannya masih harus mengembalikan true untuk
elemen yang harus dihapus, yang merupakan kebalikan dari retain() yang
membuatnya tidak konsisten..
Mungkin retain_where?
Tapi saya pikir Anda benar bahwa masuk akal untuk memiliki "menguras" dalam nama,
jadi saya pikir drain_retain() paling masuk akal: Ini seperti drain() tapi
mempertahankan elemen di mana penutupan mengembalikan true.


Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/43244#issuecomment-368325374 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AHR0kfG4oZHxGfpOSK8DjXW3_2O1Eo3Rks5tYZHxgaJpZM4OY1me
.

Tetapi seberapa sering Anda akan bermigrasi dari drain() ke drain_filter() ?
Dalam semua kasus sampai sekarang, saya bermigrasi dari retain() ke drain_filter() karena tidak ada retain_mut() di std dan saya perlu mengubah elemen! Jadi saya harus membalikkan nilai pengembalian penutupan ..
Saya pikir drain_retain() masuk akal karena metode drain() menguras semua elemen dalam rentang tanpa syarat, sedangkan drain_retain() mempertahankan elemen di mana penutupan mengembalikan true , menggabungkan efek dari metode drain() dan retain() .

Maaf maksud saya untuk bermigrasi dari drain_filter ke drain_where .

Bahwa Anda memiliki solusi menggunakan retain dan kemudian perlu menggunakan
drain_filter adalah aspek yang belum saya pertimbangkan.

Pada Minggu, 25 Februari 2018, 19:12 Boscop [email protected] menulis:

Tetapi mengapa Anda bermigrasi dari drain() ke drain_filter()?
Dalam semua kasus sampai sekarang, saya bermigrasi dari retain() ke drain_filter()
karena tidak ada retain_mut() di std dan saya perlu mengubah elemennya!
Saya pikir drain_retain() masuk akal karena metode drain() terkuras
tanpa syarat semua elemen dalam rentang, sedangkan drain_retain() mempertahankan
elemen di mana penutupan mengembalikan true.


Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/43244#issuecomment-368330896 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AHR0kSayIk_fbp5M0RsZW5pYs3hDICQIks5tYaJ0gaJpZM4OY1me
.

Ah ya, tapi saya pikir "harga" untuk membalikkan penutupan dalam kode saat ini yang menggunakan drain_filter() sepadan, untuk mendapatkan API yang konsisten dan intuitif di std dan kemudian di stabil.
Ini hanya biaya tetap yang kecil (dan diringankan oleh fakta bahwa itu akan sejalan dengan penggantian nama fungsi, sehingga kesalahan kompiler dapat memberi tahu pengguna bahwa penutupan harus dibalik, sehingga tidak akan secara diam-diam memperkenalkan bug) , dibandingkan dengan biaya standarisasi drain_filter() dan kemudian orang selalu harus membalikkan penutupan saat bermigrasi dari retain() ke drain_filter() .. (di atas biaya mental mengingat lakukan itu, dan biaya untuk membuatnya lebih sulit untuk menemukannya di dokumen, berasal dari retain() dan mencari "sesuatu seperti retain() tetapi meneruskan &mut ke penutupannya, yang itulah mengapa saya pikir masuk akal bahwa nama baru dari fungsi ini memiliki "mempertahankan" dalam namanya, sehingga orang menemukannya ketika mencari di dokumen).

Beberapa data anekdotal: Dalam kode saya, saya selalu hanya membutuhkan aspek retain_mut() dari drain_filter() (sering kali mereka menggunakan retain() sebelumnya), saya tidak pernah menggunakan kasus di mana saya perlu memproses nilai-nilai yang dikeringkan. Saya pikir ini juga akan menjadi kasus penggunaan paling umum untuk orang lain di masa mendatang (karena retain() tidak meneruskan &mut ke penutupannya sehingga drain_filter() harus mencakup kasus penggunaan itu , juga, dan ini adalah kasus penggunaan yang lebih umum daripada perlu memproses nilai yang terkuras).

Alasan mengapa saya menentang drain_retain adalah karena cara nama saat ini digunakan di std wrt. koleksi:

  1. anda memiliki nama fungsi menggunakan predikat yang memiliki konsep produksi/pemakaian yang terkait dengannya (wrt. rust, iterasi). Misalnya drain , collect , fold , all , take , ...
  2. predikat ini terkadang memiliki pengubah misalnya *_where , *_while
  3. anda memiliki nama fungsi menggunakan predikat yang memiliki properti modifikasi ( map , filter , skip , ...)

    • di sini tidak jelas apakah itu modifikasi elemen atau iterasi ( map vs. filter / skip )

  4. nama fungsi merangkai beberapa predikat menggunakan properti yang dimodifikasi, misalnya filter_map

    • memiliki konsep kira-kira apply modifier_1 and then apply modifier_2 , hanya saja lebih cepat atau lebih fleksibel melakukan ini dalam satu langkah

Anda terkadang mungkin memiliki:

  1. nama fungsi yang menggabungkan predikat yang memproduksi/mengkonsumsi dengan predikat yang memodifikasi (misalnya drain_filter )

    • _but_ seringkali lebih baik/tidak membingungkan untuk menggabungkannya dengan pengubah (misalnya drain_where )

Anda biasanya tidak memiliki:

  1. dua dari predikat penghasil/pemakai digabungkan menjadi satu nama, yaitu kami tidak memiliki pemikiran seperti take_collect karena mudah membingungkan

drain_retain agak masuk akal tetapi termasuk dalam kategori terakhir, sementara Anda mungkin dapat menebak apa yang dikatakannya pada dasarnya remove and return all elements "somehow specified" and then keep all elements "somehow specified" discarding other elements .


Di satu sisi saya tidak tahu mengapa tidak boleh ada retain_mut mungkin membuka RFC cepat memperkenalkan retain_mut sebagai cara yang efisien untuk menggabungkan modify + retain Saya punya firasat itu mungkin lebih cepat
stabil maka fungsi ini. Sampai saat itu Anda dapat mempertimbangkan untuk menulis sifat ekstensi yang menyediakan
anda memiliki retain_mut menggunakan iter_mut + bool-array (atau bitarray, atau...) untuk melacak elemen mana
harus dicabut. Atau sediakan drain_retain yang secara internal menggunakan drain_filer / drain_where
tetapi membungkus predikat menjadi not |ele| !predicate(ele) .

@dathinab

  1. Kita berbicara tentang metode pada koleksi di sini, bukan di Iterator. peta, filter, filter_map, lewati, take_while dll adalah semua metode di Iterator. Btw, metode apa yang Anda maksud yang menggunakan *_where ?
    Jadi kita harus membandingkan skema penamaan dengan metode yang sudah ada pada koleksi, misalnya retain() , drain() . Tidak ada kebingungan dengan metode Iterator yang mengubah satu iterator menjadi iterator lain.
  2. AFAIK konsensusnya adalah bahwa retain_mut() tidak akan ditambahkan ke std karena drain_filter() sudah akan ditambahkan dan orang-orang disarankan untuk menggunakannya. Yang membawa kita kembali ke kasus penggunaan migrasi dari retain() ke drain_filter() menjadi sangat umum, jadi itu harus memiliki nama dan API yang mirip (penutupan mengembalikan true berarti menyimpan entri )..

Saya belum mengetahui bahwa retain_mut sudah dibahas.

Kita berbicara tentang skema penamaan umum di std terutama wrt. ke
koleksi, yang memang menyertakan Iterator karena mereka adalah salah satu yang utama
mengakses metode untuk koleksi yang berkarat, terutama ketika itu tentang
memodifikasi lebih dari satu entri.

  • _where hanyalah contoh sufiks untuk mengekspresikan sedikit modifikasi
    fungsi. Sufiks semacam ini yang saat ini digunakan di std terutama adalah
    _until , _while , _then , _else , _mut dan _back .

Alasan mengapa drain_retain membingungkan adalah karena tidak jelas apakah itu
tiriskan atau pertahankan, jika berbasis drainase, pengembalian true akan dihapus
nilainya, jika dipertahankan berdasarkan itu akan menyimpannya. Menggunakan _where membuatnya di
terakhir jelas apa yang diharapkan dari fungsi yang diteruskan.

Pada Senin, 26 Februari 2018, 00:25 Boscop [email protected] menulis:

@dathinab https://github.com/dathinab

  1. Kita berbicara tentang metode pada koleksi di sini, bukan di Iterator.
    peta, filter, filter_map, lewati, take_while dll adalah semua metode di Iterator.
    Btw, metode apa yang Anda maksud yang menggunakan *_where?
    Jadi kita harus membandingkan skema penamaan dengan metode yang sudah ada
    pada koleksi, misalnya retain(), drain(). Tidak ada kebingungan dengan
    Metode iterator yang mengubah iterator menjadi iterator lain.
  2. AFAIK konsensusnya adalah retain_mut() tidak akan ditambahkan ke std
    karena drain_filter() sudah akan ditambahkan dan orang-orang disarankan
    untuk menggunakan itu. Yang membawa kita kembali ke kasus penggunaan migrasi dari
    retain() hingga drain_filter() menjadi sangat umum, jadi seharusnya memiliki a
    nama dan API yang mirip (penutupan mengembalikan true berarti menyimpan entri)..


Anda menerima ini karena Anda disebutkan.

Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/43244#issuecomment-368355110 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AHR0kfkRAZ5OtLFZ-SciAmjHDEXdgp-0ks5tYevegaJpZM4OY1me
.

Saya telah sering menggunakan fungsi ini dan saya selalu harus ingat untuk mengembalikan true untuk entri yang ingin saya tiriskan, yang terasa kontra-intuitif dibandingkan dengan retain()/retain_mut().

FWIW, saya pikir retain adalah nama kontra-intuitif di sini. Saya biasanya menemukan diri saya ingin menghapus elemen tertentu dari sebuah vektor, dan dengan retain saya selalu harus membalikkan logika itu.

Tapi retain() sudah stabil, jadi kita harus menjalaninya.. Dan intuisi saya terbiasa dengan itu..

@Boscop : dan begitu juga drain yang merupakan kebalikan dari retain tetapi juga mengembalikan elemen yang dihapus dan penggunaan sufiks seperti _until , _while untuk membuat fungsi yang tersedia yang hanya merupakan versi sedikit modifikasi dari fungsi yang ada.

Maksud saya jika saya menggambarkan drain, itu akan menjadi seperti:

_hapus dan kembalikan semua elemen yang ditentukan "dalam beberapa cara", simpan semua elemen lainnya_
di mana _"dalam beberapa cara"_ adalah _"dengan mengiris"_ untuk semua jenis koleksi yang dapat diiris dan _"semua"_ untuk sisanya.

Deskripsi untuk fungsi yang dibahas di sini adalah _yang sama_ kecuali itu
_"dalam beberapa cara"_ adalah _" di mana predikat yang diberikan mengembalikan true"_.

Di satu sisi deskripsi yang akan saya berikan adalah:
_hanya mempertahankan (yaitu menyimpan) elemen di mana predikat yang diberikan mengembalikan nilai true, membuang sisanya_

(ya, retain bisa digunakan dengan cara yang tidak membuang sisanya, sayangnya tidak)


Saya pikir akan sangat menyenangkan jika retain memiliki
meneruskan &mut T ke predikat dan mungkin mengembalikan nilai yang dihapus.
Karena menurut saya retain adalah basis nama yang lebih intuitif.

Tetapi terlepas dari ini, saya juga berpikir bahwa keduanya drain_filter / drain_retain adalah suboptimal
karena mereka tidak menjelaskan apakah predikat harus mengembalikan true/false untuk mempertahankan/menguras entri.
(tiriskan menunjukkan benar menghapusnya karena berbicara tentang menghapus elemen saat memfilter
dan latih ulang berbicara tentang elemen mana yang harus disimpan, akhirnya berkarat)


Pada akhirnya tidak _yang_ penting nama mana yang digunakan, alangkah baiknya jika distabilkan.

Melakukan polling dan/atau membiarkan seseorang dari tim bahasa memutuskan mungkin cara terbaik untuk memajukan pemikiran?

Saya pikir sesuatu seperti drain_where , drain_if , atau drain_when , jauh lebih jelas daripada drain_filter .

@tmccombs Dari 3 itu, saya pikir drain_where paling masuk akal. (Karena if menyiratkan either do the whole thing (in this case draining) or not dan when bersifat sementara.)
Dibandingkan dengan drain_filter penutupan nilai kembali sama dengan drain_where ( true untuk menghapus elemen) tetapi fakta itu dibuat lebih jelas/eksplisit dengan nama, sehingga menghilangkan risiko secara tidak sengaja menafsirkan arti dari nilai pengembalian penutupan secara salah.

Saya pikir ini lebih dari waktu untuk menstabilkan. Ringkasan utas ini:

  • Haruskah parameter R: RangeArgument ditambahkan?
  • Haruskah nilai boolean dibalik? (Saya pikir logika saat ini masuk akal: mengembalikan true dari panggilan balik menyebabkan item itu dimasukkan ke dalam iterator.)
  • Penamaan. (Saya suka drain_where .)

@Gankro , bagaimana menurutmu?

Tim libs membahas ini dan konsensusnya adalah untuk tidak menstabilkan lebih banyak metode seperti drain saat ini. (Metode drain_filter yang ada dapat tetap di Nightly sebagai tidak stabil.) https://github.com/rust-lang/rfcs/pull/2369 mengusulkan untuk menambahkan iterator mirip saluran lain yang tidak melakukan apa pun saat dijatuhkan (sebagai lawan mengkonsumsi iterator sampai akhir).

Kami ingin melihat eksperimen untuk mencoba menggeneralisasi ke permukaan API yang lebih kecil berbagai kombinasi pengeringan:

  • Sub-rentang (melalui RangeArgument alias RangeBounds ) vs seluruh koleksi (meskipun yang terakhir dapat dicapai dengan melewatkan .. , nilai tipe RangeFull ).
  • Menguras semuanya (mungkin dalam kisaran itu) vs hanya elemen yang cocok dengan predikat boolean
  • Melelahkan sendiri saat jatuh vs tidak (meninggalkan elemen lainnya dalam koleksi).

Kemungkinan mungkin termasuk "membebani berlebihan" metode dengan membuatnya generik, atau pola pembangun.

Salah satu kendalanya adalah metode drain stabil. Ini mungkin dapat digeneralisasi, tetapi hanya dengan cara yang kompatibel ke belakang.

Kami ingin melihat eksperimen untuk mencoba menggeneralisasi ke permukaan API yang lebih kecil berbagai kombinasi pengeringan:

Bagaimana dan di mana tim memperkirakan jenis eksperimen ini terjadi?

Caranya: buat dan usulkan desain API yang konkret, mungkin dengan implementasi proof-of-concept (yang dapat dilakukan di luar pohon melalui setidaknya Vec::as_mut_ptr dan Vec::set_len ). Dimana tidak terlalu penting. Bisa berupa RFC baru atau utas di https://internals.rust-lang.org/c/libs , dan tautkan dari sini.

Saya sudah bermain-main dengan ini sebentar. Saya akan membuka utas tentang internal di hari-hari berikutnya.

Saya pikir API umum yang berfungsi seperti ini masuk akal:

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

Jadi ini adalah API gaya pembangun: Jika .where(pred) tidak ditambahkan, itu akan menguras seluruh rentang tanpa syarat.
Ini mencakup kemampuan metode .drain(a..b) saat ini serta .drain_filter(pred) .

Jika nama drain tidak dapat digunakan karena sudah digunakan, nama tersebut harus mirip seperti drain_iter .

Metode where tidak boleh diberi nama *_filter untuk menghindari kebingungan dengan memfilter iterator yang dihasilkan, terutama ketika where dan filter digunakan dalam kombinasi seperti ini:

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

Di sini, ia akan menggunakan pred1 untuk memutuskan apa yang akan dikuras (dan diteruskan di iterator) dan pred2 digunakan untuk memfilter iterator yang dihasilkan.
Elemen apa pun yang pred1 mengembalikan true tetapi pred2 mengembalikan false untuk masih akan terkuras dari v tetapi tidak akan dihasilkan oleh iterator gabungan ini.

Apa pendapat Anda tentang pendekatan API gaya pembangun semacam ini?

Untuk sesaat saya lupa bahwa where tidak dapat digunakan sebagai nama fungsi karena sudah menjadi kata kunci :/

Dan drain sudah stabil jadi namanya juga tidak bisa digunakan..

Lalu saya pikir opsi keseluruhan terbaik kedua adalah mempertahankan drain saat ini dan mengganti nama drain_filter menjadi drain_where , untuk menghindari kebingungan dengan .drain(..).filter() .

(Seperti yang dikatakan jonhoo di atas :)

apa alasan di balik penamaan drain_filter ini daripada drain_where? Bagi saya, yang pertama menyiratkan bahwa seluruh Vec akan terkuras, tetapi kami juga menjalankan filter atas hasil (ketika saya pertama kali melihatnya, saya berpikir: "bagaimana ini bukan hanya .drain(..).filter() ?"). Yang pertama di sisi lain menunjukkan bahwa kita hanya menguras elemen di mana beberapa kondisi berlaku.

Saya telah membuka utas di internal .
TLDR adalah menurut saya non-selfexhaustion adalah kaleng cacing yang lebih besar dari yang diharapkan dalam kasus umum dan bahwa kita harus menstabilkan drain_filter lebih cepat daripada nanti dengan parameter RangeBounds . Kecuali seseorang memiliki ide bagus untuk memecahkan masalah yang diuraikan di sana.

Sunting: Saya telah mengunggah kode eksperimental saya: tiriskan eksperimen
Ada juga menguras dan membersihkan bangku dan beberapa tes tapi jangan berharap kode bersih.

Benar-benar ketinggalan di utas ini. Saya memiliki impl lama yang telah saya perbaiki sedikit dan salin tempel untuk mencerminkan beberapa opsi yang dijelaskan di utas ini. Satu hal yang menyenangkan tentang impl yang menurut saya tidak kontroversial adalah implementasinya DoubleEndedIterator . Lihat di sini .

@Emerentius tapi setidaknya kita harus mengganti nama drain_filter menjadi drain_where , untuk menunjukkan bahwa penutupan harus mengembalikan true untuk menghapus elemen!

@Boscop Keduanya menyiratkan 'polaritas' yang sama dari true => hasil. Saya pribadi tidak peduli apakah itu disebut drain_filter atau drain_where .

@Popog Bisakah Anda meringkas perbedaan dan pro & kontra? Idealnya di utas internal. Saya pikir fungsionalitas DoubleEndedIterator dapat ditambahkan ke belakang secara kompatibel dengan nol atau overhead rendah (tapi saya belum mengujinya).

Bagaimana dengan drain_or_retain ? Ini adalah tindakan yang bermakna secara tata bahasa, dan itu menandakan bahwa itu melakukan satu atau yang lain.

@askeksa Tapi itu tidak menjelaskan apakah mengembalikan true dari penutupan berarti "menguras" atau "menahan".
Saya pikir dengan nama seperti drain_where , sangat jelas bahwa mengembalikan true mengurasnya, dan harus jelas bagi semua orang bahwa elemen yang tidak dikeringkan dipertahankan.

Akan lebih baik jika ada cara untuk membatasi/menghentikan/membatalkan/membatalkan saluran pembuangan. Misalnya, jika saya ingin menguras N angka genap pertama, alangkah baiknya jika hanya melakukan vec.drain_filter(|x| *x % 2 == 0).take(N).collect() (atau beberapa varian dari itu).

Seperti yang saat ini diterapkan, DrainFilter 's drop metode akan selalu menjalankan pengurasan sampai selesai; itu tidak dapat dibatalkan (setidaknya saya belum menemukan trik apa pun yang akan melakukan itu).

Jika Anda menginginkan perilaku itu, Anda harus menutup beberapa status yang melacak berapa banyak yang telah Anda lihat dan mulai mengembalikan false. Berjalan hingga selesai saat drop diperlukan untuk membuat adaptor berperilaku wajar.

Saya baru menyadari bahwa cara drain_filter saat ini diterapkan bukan untuk bersantai dengan aman, tetapi
sebenarnya wrt bahaya keamanan. bersantai + melanjutkan keselamatan. Selain itu dengan mudah menyebabkan aborsi, keduanya
di antaranya adalah perilaku yang seharusnya tidak dimiliki oleh metode di std. Dan saat menulis ini saya perhatikanbahwa implementasinya saat ini tidak aman

Saya tahu bahwa Vec secara default tidak melepas aman, tetapi perilaku drain_filer ketika
predikat panik sangat mengejutkan karena:

  1. itu akan terus memanggil penutupan yang panik saat jatuh
    jika penutupan panik lagi ini akan menyebabkan kapal dan beberapa orang
    seperti semua panik berada di atas pekerjaan lain dengan pola kernel kesalahan dan untuk mereka
    berakhir dengan kapal cukup buruk
  2. jika tidak akan melanjutkan pengurasan dengan benar berpotensi satu nilai
    dan berisi satu nilai yang sudah turun berpotensi mengarah ke penggunaan setelah gratis

Contoh perilaku ini ada di sini:
play.rust-lang.org

Sementara poin 2. harus bisa dipecahkan, saya pikir poin pertama itu sendiri seharusnya
mengarah pada pertimbangan ulang perilaku DrainFilter untuk dijalankan hingga selesai
saat dijatuhkan, alasan untuk mengubah ini meliputi:

  • iterator malas dalam karat, menjalankan iterator saat menjatuhkan adalah perilaku yang agak tidak terduga
    berasal dari apa yang biasanya diharapkan
  • predikat yang diteruskan ke drain_filter mungkin panik dalam beberapa keadaan (mis
    diracuni) dalam hal ini kemungkinan besar akan panik lagi ketika dipanggil selama drop leading
    menjadi panik ganda dan karena itu naik, yang cukup buruk bagi siapa saja yang menggunakan kernel kesalahan
    pola atau akhirnya ingin menutup dengan cara yang terkontrol, tidak apa-apa jika Anda menggunakan panic=aboard
  • jika Anda memiliki efek samping dalam predikat dan tidak menjalankan DrainFilter hingga selesai, Anda mungkin mendapatkan
    bug mengejutkan ketika dijalankan hingga selesai saat dijatuhkan (tetapi Anda mungkin telah melakukannya
    yang lain berpikir antara mengeringkannya ke suatu titik dan menjatuhkannya)
  • Anda tidak dapat menyisih dari perilaku ini tanpa mengubah predikat yang diberikan padanya, yang Anda
    mungkin tidak dapat melakukannya tanpa membungkusnya, di sisi lain Anda selalu dapat memilih untuk menjalankan
    itu sampai selesai dengan hanya menjalankan iterator sampai selesai (ya argumen terakhir ini sedikit
    gelombang tangan)

Argumen untuk menjalankan hingga selesai meliputi:

  • drain_filter mirip dengan ratain yang merupakan fungsi, jadi orang mungkin akan terkejut ketika mereka
    "hanya" drop DrainFilter alih-alih menjalankannya sampai selesai

    • argumen ini ditentang berkali-kali di RFC lain dan itulah sebabnya #[unused_must_use]

      ada, yang dalam beberapa situasi sudah merekomendasikan untuk menggunakan .for_each(drop) yang ironisnya

      kebetulan apa yang dilakukan DrainFilter saat jatuh

  • drain_filter sering digunakan karena efek sampingnya saja, jadi verbose

    • menggunakannya dengan cara itu membuatnya kira-kira sama dengan retain



      • tetapi tetap gunakan &T , drain_filter menggunakan &mut T



  • yang lain??
  • [EDIT, ADDED LATER, THX @tmccombs ]: tidak menyelesaikan drop bisa sangat membingungkan ketika dikombinasikan dengan adaptor seperti find , all , any yang saya cukup alasan untuk menjaga perilaku saat ini.

Mungkin hanya saya atau saya melewatkan beberapa poin tetapi mengubah perilaku Drop dan
menambahkan #[unused_must_use] tampaknya lebih disukai?

Jika .for_each(drop) terlalu panjang, kami mungkin mempertimbangkan untuk menambahkan RFC untuk iterator yang dimaksudkan untuk
ada efek samping menambahkan metode seperti complete() ke iterator (atau juga drain() tapi ini
adalah diskusi yang berbeda lengkap)

yang lain??

Saya tidak dapat menemukan alasan aslinya, tetapi saya ingat ada juga beberapa masalah dengan adaptor yang bekerja dengan DrainFilter yang tidak berjalan sampai selesai.

Lihat juga https://github.com/rust-lang/rust/issues/43244#issuecomment -394405057

Poin bagus, misalnya find akan menyebabkan pengurasan hanya sampai mencapai yang pertama
cocok, serupa all , any melakukan korsleting, yang bisa sangat membingungkan
wrt. mengeringkan.

Hm, mungkin saya harus mengubah pendapat saya. Melalui ini mungkin menjadi masalah umum
dengan iterator yang memiliki efek samping dan mungkin kita harus mempertimbangkan solusi umum
(terlepas dari masalah pelacakan ini) seperti adaptor .allways_complete() .

Saya pribadi tidak menemukan alasan keamanan mengapa pengurasan perlu dijalankan sampai selesai tetapi seperti yang saya tulis di sini beberapa posting di atas, efek samping pada next() berinteraksi secara suboptimal dengan adaptor seperti take_while , peekable dan skip_while .

Ini juga memunculkan masalah yang sama dengan RFC saya pada non-selfexhausting drain dan pendampingnya selfexhausting iter adapter RFC .

Benar bahwa drain_filter dapat dengan mudah menyebabkan pembatalan tetapi dapatkah Anda menunjukkan contoh di mana itu melanggar keamanan?

Ya, saya sudah melakukannya: play.rust-lang.org

Yang mana ini:

#![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 --

}

Tapi itu pemikiran internal implementasi, yang salah.
Pada dasarnya pertanyaan terbuka adalah bagaimana menangani panic dari fungsi predikat:

  1. lewati elemen yang membuat panik, bocorkan dan tingkatkan penghitung del

    • membutuhkan beberapa bentuk deteksi panik

  2. jangan majukan idx sebelum memanggil predikat

    • tapi ini berarti on drop akan memanggilnya lagi dengan predikat yang sama

Pertanyaan lain adalah apakah ide yang baik untuk menjalankan fungsi yang dapat dilihat sebagai input pengguna api pada drop
secara umum, tetapi ini adalah satu-satunya cara untuk tidak membuat find , any , dll. berperilaku membingungkan.

Mungkin pertimbangannya bisa seperti:

  1. setel bendera saat memasukkan next , hapus sebelum kembali dari next
  2. saat jatuh jika bendera masih dipasang, kami tahu kami panik dan karenanya bocor
    item yang tersisa ATAU jatuhkan semua item yang tersisa

    1. bisa menjadi kebocoran yang cukup besar dengan efek samping yang tidak terduga jika Anda misalnya membocorkan Arc

    2. bisa sangat mengejutkan jika Anda memiliki Arc dan Weak's

Mungkin ada solusi yang lebih baik.
Melalui apa pun itu harus didokumentasikan dalam rustdoc setelah diimplementasikan.

@dathinab

Ya, saya sudah melakukannya

Kebocoran tidak diinginkan tetapi baik-baik saja dan mungkin sulit dihindari di sini, tetapi penurunan ganda jelas tidak. Tangkapan yang bagus! Apakah Anda ingin melaporkan masalah terpisah tentang masalah keamanan ini?

Apakah drain_filter melakukan realokasi setiap kali menghapus item dari koleksi? Atau realokasi hanya sekali dan berfungsi seperti std::remove dan std::erase (berpasangan) di C++? Saya lebih suka perilaku seperti itu karena tepat satu alokasi: kami hanya menempatkan elemen kami di akhir koleksi dan kemudian menghapus menyusut ke ukuran yang tepat.

Juga, mengapa tidak ada try_drain_filter ? Manakah yang mengembalikan tipe Option , dan nilai None jika kita harus berhenti? Saya memiliki koleksi yang sangat besar dan tidak ada artinya melanjutkan bagi saya ketika saya sudah mendapatkan apa yang saya butuhkan.

Terakhir kali saya mengambil kode itu melakukan sesuatu seperti: membuat "celah"
saat memindahkan elemen dan memindahkan elemen yang tidak dikeringkan ke
mulai dari celah ketika menemukan satu. Dengan ini setiap elemen yang harus
dipindahkan (baik keluar atau ke tempat baru dalam array) hanya dipindahkan sekali.
Juga seperti misalnya remove itu tidak mengalokasikan kembali. Bagian yang dibebaskan hanya menjadi
bagian dari kapasitas yang tidak terpakai.

Pada Jumat, 10 Agustus 2018, 07:11 Victor Polevoy [email protected] menulis:

Apakah drain_filter melakukan realokasi setiap kali menghapus item dari
koleksi? Atau realokasi hanya sekali dan berfungsi seperti std::remove
dan std::hapus (berpasangan) di C++? Saya lebih suka perilaku seperti itu karena
tepat satu alokasi: kami hanya menempatkan elemen kami di akhir koleksi
dan kemudian menghapus menyusut ke ukuran yang tepat.

Juga, mengapa tidak ada try_drain_filter ? Yang mengembalikan tipe Opsi, dan
Tidak ada nilai jika kita harus berhenti? Saya memiliki koleksi yang sangat besar dan itu
tidak ada artinya untuk melanjutkan bagi saya ketika saya sudah mendapatkan apa yang saya butuhkan.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/43244#issuecomment-411977001 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AHR0kdOZm4bj6iR9Hj831Qh72d36BxQSks5uPRYNgaJpZM4OY1me
.

@rustonaut terima kasih. Apa pendapat Anda tentang try_drain_filter ? :)

PS Baru saja melihat kodenya juga, sepertinya berfungsi seperti yang kita inginkan.

Ini memajukan elemen demi elemen saat iterasi, jadi biasanya Anda bisa mengharapkan
itu untuk menghentikan iterasi ketika dijatuhkan, tetapi itu dianggap terlalu
membingungkan sehingga benar-benar terkuras sampai akhir saat dijatuhkan.
(Yang secara drastis meningkatkan kemungkinan kepanikan dan hal-hal ganda
seperti itu).

Jadi sepertinya Anda tidak akan mendapatkan versi percobaan yang berperilaku seperti Anda
mengharapkan.

Untuk keadilan, tiriskan penghentian awal saat iterasi benar-benar dapat membingungkan
beberapa situasi misalnya thing.drain_where(|x| x.is_malformed()).any(|x| x.is_dangerus()) tidak akan menguras semua format yang salah tetapi hanya sampai salah satu dari
ditemukan yang juga berbahaya. (Impl. saat ini menguras semua format yang salah
dengan melanjutkan pengeringan saat jatuh).

Pada Jumat, 10 Agustus 2018, 10:52 Victor Polevoy [email protected] menulis:

@rustonaut https://github.com/rustonaut terima kasih. Apa pendapat Anda
tentang try_drain_filter? :)


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/43244#issuecomment-412020490 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AHR0kcEMHCayqvhI6D4LK4ITG2x5di-9ks5uPUnpgaJpZM4OY1me
.

Saya pikir ini akan lebih serbaguna:

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

Hai, saya sedang mencari fungsi drain_filter untuk HashMap tetapi tidak ada, dan diminta untuk membuka masalah ketika saya menemukan yang ini. Apakah harus dalam edisi terpisah?

Apakah ada yang saat ini menghalangi ini dari stabilisasi? Apakah masih bersantai-tidak aman seperti yang dilaporkan di atas?

Ini tampak seperti fitur yang cukup kecil, dan telah mengalami limbo selama lebih dari setahun.

Saya pikir ini akan lebih serbaguna:

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

Saya tidak berpikir ini lebih baik daripada komposisi drain_filter dan map .

Apakah masih bersantai-tidak aman seperti yang dilaporkan di atas?

Tampaknya ada pilihan sulit antara tidak menguras semua elemen yang cocok jika iterasi berhenti lebih awal, atau potensi kepanikan selama pelepasan jika penyaringan dan pengeringan sampai akhir dilakukan dengan setetes DrainFilter .
Saya pikir fitur ini penuh dengan masalah dan karenanya tidak boleh distabilkan.

Apakah ada masalah khusus dengan membuatnya berperilaku berbeda saat bersantai?

Kemungkinan:

  • Itu bisa berjalan hingga selesai secara normal, tetapi meninggalkan elemen yang cocok saat melepas (sehingga semua elemen yang tersisa dianggap tidak cocok).
  • Itu bisa berjalan hingga selesai secara normal, tetapi potong vektor setelah posisi tertulis terakhir saat melepas (sehingga semua elemen yang tersisa dianggap cocok).
  • Itu bisa berjalan hingga selesai secara normal, tetapi potong vektor menjadi panjang 0 saat bersantai.

Argumen tandingan yang paling dapat dipahami yang dapat saya pikirkan adalah kode drop yang bergantung pada invarian yang biasanya disediakan oleh drain_filter (bahwa, pada akhirnya, elemen dalam vec akan persis seperti yang gagal kondisi) mungkin secara sewenang-wenang jauh dari kode (kemungkinan besar normal, kode aman) yang menggunakan drain_filter .

Namun, anggaplah ada kasus seperti itu. Kode ini akan menjadi buggy tidak peduli bagaimana pengguna menulisnya. Misalnya jika mereka menulis loop imperatif yang berjalan terbalik dan elemen yang dihapus swap, maka jika kondisinya bisa panik dan impl drop mereka sangat bergantung pada kondisi filter yang salah, kodenya masih memiliki bug. Memiliki fungsi seperti drop_filter yang dokumentasinya dapat menarik perhatian pada kasus tepi ini tampaknya merupakan peningkatan dalam perbandingan.

Juga, terima kasih, saya menemukan contoh taman bermain ini diposting sebelumnya di utas yang menunjukkan bahwa implementasi saat ini masih menjatuhkan elemen ganda. (jadi itu pasti tidak bisa distabilkan apa adanya!)

Mungkin perlu membuka masalah terpisah untuk bug kesehatan? Itu kemudian dapat ditandai sebagai I-unsound.

Sejauh yang saya tahu Anda tidak dapat Menandai atau tidak sehat seperti panik ganda _adalah suara_
hanya sangat tidak nyaman karena dibatalkan. Juga sejauh yang saya ingat
kemungkinan panik ganda bukanlah bug tetapi perilaku secara implisit tetapi
dipilih secara sadar.

Pilihannya pada dasarnya adalah:

  1. Jangan berlari sampai selesai saat jatuh.
  2. Jalankan sampai selesai tetapi berpotensi batal karena kepanikan ganda
  3. Jatuhkan semua elemen "yang tidak dicentang" saat panik.
  4. Jangan menyelesaikan saat jatuh saat panik.

Masalah-masalah tersebut adalah:

  1. => Perilaku tak terduga dalam banyak kasus penggunaan.
  2. => Tak terduga batal jika predikatnya bisa panik, apalagi kalau dipakai
    untuk "hanya" menghapus elemen, yaitu Anda tidak menggunakan iterator yang dikembalikan.
  3. => Perbedaan tak terduga antara masuk dan keluar dari kepanikan. Hanya
    pertimbangkan seseorang _using_ drain_filter dalam fungsi drop.
  4. => Lihat 3.

Atau dengan kata lain 1. menyebabkan kebingungan dalam kasus penggunaan normal, 2. dapat menyebabkan
batal jika predikat bisa panic 3,,4. buatlah agar kamu tidak bisa benar-benar
menggunakannya dalam metode drop, tetapi bagaimana Anda sekarang menggunakan fungsi yang Anda gunakan di sana
tidak menggunakannya secara internal.

Sebagai hasil dari opsi ini 3,4. tidak pergi. Masalah dengan opsi 2. adalah
lebih jarang maka yang di 1. jadi 2. dipilih.

IMHO akan lebih baik memiliki API drain+drain_filter yang tidak berjalan
hingga selesai pada drop + kombinator iterator umum yang berjalan ke
penyelesaian pada drop + metode yang menyelesaikan iterator tetapi hanya menjatuhkan semua
item yang tersisa. Soalnya drainnya udah stabil, iterator
kombinator menambahkan overhead karena perlu menggabungkan iterator bagian dalam dan tiriskan
mungkin bukan nama yang paling tepat.

Pada Senin, 20 Mei 2019, 09:28 Ralf Jung [email protected] menulis:

Mungkin perlu membuka masalah terpisah untuk bug kesehatan? Yang dapat
kemudian ditandai sebagai I-tidak sehat.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/43244?email_source=notifications&email_token=AB2HJEL7FS6AA2A2KF5U2S3PWJHK7A5CNFSM4DTDLGPKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW4ZLOVORX5WZW2JKTDNORX5WZW2JKTDNORX5WSW2JKTNVORX5WSW2J63LNMVX
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AB2HJEMHQFRTCH6RCQQ64DTPWJHK7ANCNFSM4DTDLGPA
.

Double- tetes tidak sehat sekalipun.

Dibuat #60977 untuk masalah kesehatan

Terima kasih, saya merasa bodoh karena membaca double drop as double panic :man_facepalming: .

  1. => Perbedaan tak terduga antara masuk dan keluar dari kepanikan. Hanya
    pertimbangkan seseorang _using_ drain_filter dalam fungsi drop.

3,,4. buatlah agar kamu tidak bisa benar-benar
menggunakannya dalam metode drop, tetapi bagaimana Anda sekarang menggunakan fungsi yang Anda gunakan di sana
tidak menggunakannya secara internal.

Ini bagi saya masih bukan masalah besar.

Jika seseorang menggunakan drain_filter dalam Drop impl dengan kondisi yang bisa membuat panik, masalahnya bukan karena mereka memilih untuk menggunakan drain_filter ; Demikian juga, tidak masalah apakah suatu metode menggunakan drain_filter secara internal;

Maaf, saya menjawab terlalu dini. Saya pikir saya mengerti apa yang Anda maksud sekarang. Saya akan memikirkan ini sedikit lagi.

Baiklah, jadi argumen Anda adalah bahwa kode di dalam impl drop yang menggunakan drain_filter dapat rusak secara misterius jika kebetulan dijalankan selama bersantai. (ini bukan tentang kepanikan drain_filter , tetapi tentang kepanikan kode lain yang menyebabkan drain_filter dijalankan):

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 penurunan ini akan tiba-tiba berperilaku buruk saat bersantai.

Ini memang argumen yang meyakinkan untuk melarang DrainFilter mendeteksi secara naif jika utas saat ini panik.

Terminologi drain_filter paling bermanfaat bagi saya. Mengingat bahwa kita sudah memiliki drain untuk menghapus semua item, memilih item mana yang akan dihapus adalah filter . Saat dipasangkan bersama penamaannya terasa sangat konsisten.

Perbaikan untuk masalah kesehatan double-panic meninggalkan sisa Vec dalam hal kepanikan pada predikat. Item digeser kembali untuk mengisi celah, tetapi sebaliknya dibiarkan saja (dan dijatuhkan melalui Vec::drop selama bersantai atau ditangani oleh pengguna jika panik tertangkap).

Menjatuhkan vec::DrainFilter sebelum waktunya terus berperilaku seolah-olah itu dikonsumsi sepenuhnya (yang sama dengan vec::Drain ). Jika predikat panik selama vec::Drain::drop , item yang tersisa digeser kembali secara normal, tetapi tidak ada item lebih lanjut yang dijatuhkan dan predikat tidak dipanggil lagi. Pada dasarnya berperilaku sama terlepas dari apakah kepanikan pada predikat terjadi selama konsumsi normal atau ketika vec::DrainFilter dijatuhkan.

Dengan asumsi bahwa perbaikan lubang kesehatan sudah benar, apa lagi yang menahan stabilisasi fitur ini?

Bisakah Vec::drain_filter distabilkan secara independen dari LinkedList::drain_filter ?

Masalah dengan drain_filter terminologi, adalah bahwa dengan drain_filter , sebenarnya ada dua output: nilai kembalian, dan koleksi asli, dan tidak terlalu jelas sisi mana yang "difilter" item berjalan. Saya pikir bahkan filtered_drain sedikit lebih jelas.

tidak begitu jelas di sisi mana item yang "difilter" itu berada

Vec::drain menetapkan preseden. Anda menentukan rentang item yang ingin Anda _hapus_. Vec::drain_filter beroperasi dengan cara yang sama. Anda mengidentifikasi item yang ingin Anda _hapus_.

dan tidak begitu jelas di sisi mana item yang "difilter" itu berada

Saya berpendapat bahwa ini sudah berlaku untuk Iterator::filter , jadi saya mengundurkan diri karena harus melihat dokumen / menulis tes setiap kali saya menggunakannya. Saya tidak keberatan sama untuk drain_filter .


Saya berharap kami telah memilih istilah Ruby select dan reject , tetapi kapal itu telah lama berlayar.

Ada kemajuan dalam hal ini? Apakah hanya nama yang masih simpang siur?

Apakah ada sesuatu yang menghalangi ini untuk distabilkan?

Sepertinya impl DrainFilter 's Drop akan membocorkan item jika salah satu destruktornya membuat panik predikatnya panik. Ini adalah akar penyebab https://github.com/rust-lang/rust/issues/52267. Apakah kita yakin ingin menstabilkan API seperti itu?

Nevermind, itu diperbaiki oleh https://github.com/rust-lang/rust/pull/61224 rupanya.

Akan sedikit membahas masalah pelacakan ini, ingin melihat fitur ini stabil :D Apakah ada pemblokir?

cc @Dylan-DPC

Apakah keputusan pernah dibuat untuk mendukung atau menentang drain_filter mengambil parameter RangeBounds , seperti yang dilakukan drain ? Melewati .. tampaknya cukup mudah ketika Anda ingin memfilter seluruh vektor, jadi saya mungkin lebih suka menambahkannya.

Saya pikir ini akan lebih serbaguna:

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

Hampir, versi yang lebih umum akan membutuhkan FnMut(T) -> Option<U> , seperti yang dilakukan Iterator::{filter_map, find_map, map_while} . Saya tidak tahu apakah layak untuk menggeneralisasi filter_map dengan cara ini, tetapi mungkin perlu dipertimbangkan.

Saya tiba di sini karena saya kurang lebih mencari metode yang disarankan @jethrogb di atas:

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

Satu-satunya perbedaan dari apa yang ada dalam pikiran saya (yang saya sebut update di kepala saya) adalah bahwa saya tidak berpikir untuk membuatnya mengembalikan iterator yang menguras, tetapi itu tampak seperti peningkatan yang jelas, karena memberikan antarmuka tunggal yang cukup sederhana yang mendukung pembaruan item di tempat, menghapus item yang ada, dan mengirimkan item yang dihapus ke pemanggil.

Hampir, versi yang lebih umum akan membutuhkan FnMut(T) -> Option<U> , seperti yang dilakukan Iterator::{filter_map, find_map, map_while} . Saya tidak tahu apakah layak untuk menggeneralisasi filter_map dengan cara ini, tetapi mungkin perlu dipertimbangkan.

Fungsi harus mengembalikan Option<T> karena nilai yang dihasilkan disimpan dalam Vec<T> .

@johnw42 Saya tidak yakin saya mengikuti, bukankah nilai Some akan segera dihasilkan oleh iterator?

Sebenarnya, saya kira nilai input dari fungsi itu masih perlu &T atau &mut T alih-alih T jika Anda tidak ingin mengurasnya. Atau mungkin fungsinya bisa seperti FnMut(T) -> Result<U, T> . Tapi saya tidak mengerti mengapa tipe item tidak bisa menjadi tipe lain.

@timvermeulen Saya pikir kami menafsirkan proposal secara berbeda.

Cara saya menafsirkannya, nilai Some disimpan kembali ke Vec , dan None berarti nilai asli dihasilkan oleh iterator. Itu memungkinkan penutupan untuk memperbarui nilai di tempat, atau memindahkannya dari Vec . Saat menulis ini, saya menyadari bahwa versi saya tidak benar-benar menambahkan apa pun karena Anda dapat mengimplementasikannya dalam 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,
    })
}

Sebaliknya, saya pikir interpretasi Anda tidak terlalu berguna karena itu setara dengan memetakan hasil drain_filter , tetapi saya mencoba untuk menulisnya, dan tidak, karena alasan yang sama filter_map tidak' t setara dengan memanggil filter diikuti oleh map .

@johnw42 Ah, ya, saya pikir Anda ingin None berarti bahwa nilainya harus tetap di Vec .

Jadi sepertinya FnMut(T) -> Result<U, T> akan menjadi yang paling umum, meskipun mungkin tidak terlalu ergonomis. FnMut(&mut T) -> Option<U> sebenarnya bukan pilihan karena itu tidak memungkinkan Anda untuk mengambil kepemilikan T dalam kasus umum. Saya pikir FnMut(T) -> Result<U, T> dan FnMut(&mut T) -> bool adalah satu-satunya pilihan.

@timvermeulen Saya mulai mengatakan sesuatu sebelumnya tentang solusi "paling umum", dan solusi "paling umum" saya berbeda dari solusi Anda, tetapi saya mencapai kesimpulan yang sama, yaitu mencoba membuat fungsi terlalu umum menghasilkan sesuatu yang Anda tidak akan benar-benar ingin menggunakan.

Meskipun mungkin masih ada beberapa nilai dalam membuat metode yang sangat umum sehingga pengguna tingkat lanjut dapat membuat abstraksi yang lebih bagus di atasnya. Sejauh yang saya tahu, maksud dari drain dan drain_filter bukanlah karena mereka adalah API yang sangat ergonomis--mereka tidak--tetapi mereka mendukung kasus penggunaan yang terjadi di latihan, dan yang tidak dapat ditulis dengan cara lain tanpa banyak gerakan yang berlebihan (atau menggunakan operasi yang tidak aman).

Dengan drain , Anda mendapatkan properti bagus berikut:

  • Setiap pemilihan elemen yang berdekatan dapat dihapus.
  • Menjatuhkan item yang dihapus semudah membuang iterator yang dikembalikan.
  • Item yang dihapus tidak harus dijatuhkan; penelepon dapat memeriksa masing-masing secara individual dan memilih apa yang harus dilakukan dengannya.
  • Isi Vec tidak perlu mendukung Copy atau Clone .
  • Tidak ada memori untuk Vec itu sendiri yang perlu dialokasikan atau dibebaskan.
  • Nilai yang tersisa di Vec dipindahkan paling banyak satu kali.

Dengan drain_filter , Anda memperoleh kemampuan untuk menghapus set item yang berubah-ubah dari Vec daripada hanya rentang yang berdekatan. Keuntungan yang kurang jelas adalah bahwa meskipun rentang item yang berdekatan dihapus, drain_filter masih dapat menawarkan peningkatan kinerja jika menemukan rentang untuk diteruskan ke drain akan melibatkan pembuatan pass terpisah atas Vec untuk memeriksa isinya. Karena argumen penutupannya adalah &mut T , item yang tersisa di Vec bahkan dapat diperbarui. Hore!

Berikut adalah beberapa hal lain yang mungkin ingin Anda lakukan dengan operasi di tempat seperti drain_filter :

  1. Ubah item yang dihapus sebelum mengembalikannya melalui iterator.
  2. Batalkan operasi lebih awal dan laporkan kesalahan.
  3. Alih-alih hanya menghapus item atau membiarkannya di tempatnya (sementara mungkin mengubahnya), tambahkan kemampuan untuk menghapus item dan menggantinya dengan nilai baru (yang mungkin merupakan tiruan dari nilai asli, atau sesuatu yang lain sama sekali).
  4. Ganti item yang dihapus dengan beberapa item baru.
  5. Setelah melakukan sesuatu dengan item saat ini, lewati beberapa item berikut, biarkan di tempatnya.
  6. Setelah melakukan sesuatu dengan item saat ini, hapus beberapa item berikut tanpa memeriksanya terlebih dahulu.

Dan inilah analisis saya masing-masing:

  1. Ini tidak menambahkan sesuatu yang berguna karena pemanggil sudah dapat mengubah item saat dikembalikan oleh iterator. Itu juga mengalahkan tujuan iterator, yaitu untuk menghindari nilai kloning dengan mengirimkannya ke pemanggil hanya setelah mereka dihapus dari Vec .
  2. Mampu menggugurkan lebih awal berpotensi meningkatkan kompleksitas asimtotik dalam beberapa kasus. Melaporkan kesalahan sebagai bagian dari API tidak menambahkan hal baru karena Anda dapat melakukan hal yang sama dengan membuat penutupan mengubah variabel yang ditangkap, dan tidak jelas bagaimana melakukannya, karena nilainya tidak akan dihasilkan sampai setelah iterator memiliki telah dikonsumsi.
  3. Ini, saya pikir, menambahkan beberapa generalisasi nyata.
  4. Inilah yang awalnya akan saya usulkan sebagai opsi "wastafel dapur", tetapi saya memutuskan itu tidak membantu, karena jika satu item dapat diganti dengan beberapa item, tidak mungkin untuk mempertahankan properti item itu di Vec dipindahkan paling banyak sekali, dan mungkin perlu mengalokasikan ulang buffer. Jika Anda perlu melakukan sesuatu seperti itu, itu tidak selalu lebih efisien daripada hanya membangun Vec baru, dan itu bisa lebih buruk.
  5. Ini dapat membantu jika Vec diatur sedemikian rupa sehingga Anda dapat melewatkan sebagian besar item tanpa berhenti untuk memeriksanya. Saya tidak memasukkannya ke dalam kode contoh saya di bawah, tetapi ini dapat didukung dengan mengubah penutupan untuk mengembalikan usize tambahan yang menentukan berapa banyak item berikut yang harus dilompati sebelum melanjutkan.
  6. Ini tampaknya melengkapi item 5, tetapi tidak terlalu berguna jika Anda masih perlu mengembalikan item yang dihapus melalui iterator. Ini mungkin masih merupakan pengoptimalan yang berguna, jika item yang Anda hapus tidak memiliki destruktor dan Anda hanya ingin menghilangkannya. Dalam hal ini, usize di atas dapat diganti dengan pilihan Keep(usize) atau Drop(usize) (di mana Keep(0) dan Drop(0) secara semantik setara).

Saya pikir kami dapat mendukung kasus penggunaan penting dengan meminta penutupan mengembalikan enum dengan 4 kasus:

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,
}

Satu opsi terakhir yang ingin saya tunjukkan adalah menyingkirkan iterator sepenuhnya, meneruskan item ke penutupan berdasarkan nilai, dan mengizinkan penelepon untuk membiarkan item tidak berubah dengan menggantinya dengan dirinya sendiri:

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,
}

Saya paling suka pendekatan ini karena sederhana dan mendukung semua kasus penggunaan yang sama. Kelemahan potensial adalah bahwa meskipun sebagian besar item dibiarkan di tempatnya, mereka masih perlu dipindahkan ke bingkai tumpukan penutup dan kemudian dipindahkan kembali saat penutupan kembali. Orang akan berharap gerakan itu dapat dioptimalkan dengan andal ketika penutupan baru saja mengembalikan argumennya, tetapi saya tidak yakin apakah itu sesuatu yang harus kita andalkan. Jika orang lain cukup menyukainya untuk memasukkannya, saya pikir update akan menjadi nama yang bagus untuk itu, karena jika saya tidak salah, itu dapat digunakan untuk mengimplementasikan pembaruan satu kali di tempat dari sebuah konten Vec .

(BTW, saya benar-benar mengabaikan daftar tertaut di atas karena saya lupa sepenuhnya sampai saya melihat judul masalah ini. Jika kita berbicara tentang daftar tertaut, itu mengubah analisis poin 4-6, jadi saya pikir yang berbeda API akan sesuai untuk daftar tertaut.)

@johnw42 Anda sudah dapat melakukan 3. jika Anda memiliki referensi yang dapat diubah, dengan menggunakan mem::replace atau mem::take .

@johnw42 @jplatte

(3) hanya benar-benar masuk akal jika kita mengizinkan tipe item dari Iterator yang dikembalikan berbeda dari tipe item koleksi.
(3) adalah kasus khusus, karena Anda berdua mengembalikan elemen dari Iterator , dan memasukkan elemen baru kembali ke Vec .

Bikeshedding: Saya akan membalikkan fungsi Replace(T) dan menggantinya dengan PushOut(T) , dengan tujuan untuk "mengirimkan" nilai dalam PushOut ke iterator, sambil menjaga item (parameter) asli di Vec .

Stop mungkin harus membawa kemampuan untuk mengembalikan tipe Error (atau bekerja sedikit seperti try_fold ?).

Saya menerapkan fungsi super_drain_by_value saya tadi malam, dan saya belajar beberapa hal.

Item judul mungkin harus itu, setidaknya wrt Vec , semua yang kita bicarakan di sini adalah dalam kategori "bagus untuk dimiliki" (sebagai lawan menambahkan kemampuan yang secara fundamental baru), karena Vec pada dasarnya sudah menyediakan akses baca dan tulis langsung ke semua bidangnya melalui API yang ada. Dalam versi stabil, ada peringatan kecil bahwa Anda tidak dapat mengamati bidang penunjuk dari Vec yang kosong, tetapi metode into_raw_parts yang tidak stabil menghapus batasan itu. Apa yang sebenarnya kita bicarakan adalah memperluas rangkaian operasi yang dapat dilakukan secara efisien dengan kode aman.

Dalam hal pembuatan kode, saya menemukan bahwa dalam kasus-kasus mudah (misalnya Vec<i32> ), perpindahan masuk dan keluar yang berlebihan dari Vec bukanlah masalah, dan menyebut jumlah itu untuk hal-hal sederhana seperti no-op atau pemotongan Vec diubah menjadi kode yang terlalu sederhana untuk diperbaiki (masing-masing nol dan tiga instruksi). Berita buruknya adalah untuk kasus yang lebih sulit, baik proposal saya dan metode drain_filter melakukan banyak penyalinan yang tidak perlu, sebagian besar mengalahkan tujuan metode. Saya menguji ini dengan melihat kode Majelis yang dihasilkan untuk Vec<[u8; 1024]> , dan dalam kedua kasus, setiap iterasi memiliki dua panggilan ke memcpy yang tidak dioptimalkan. Bahkan panggilan tanpa operasi akhirnya menyalin seluruh buffer dua kali!

Dalam hal ergonomi, API saya, yang terlihat cukup bagus pada pandangan pertama, tidak begitu bagus dalam praktiknya; mengembalikan nilai enum dari penutupan menjadi sangat bertele-tele dalam semua kasus kecuali yang paling sederhana, dan varian yang saya usulkan di mana penutupan mengembalikan sepasang nilai enum bahkan lebih buruk.

Saya juga mencoba memperluas DrainAction::Stop untuk membawa nilai R yang dikembalikan dari super_drain_by_value sebagai Option<R> , dan itu bahkan lebih buruk, karena dalam (mungkin khas) kasus di mana nilai yang dikembalikan tidak diperlukan, kompiler tidak dapat menyimpulkan R dan Anda harus secara eksplisit membubuhi keterangan jenis nilai yang bahkan tidak Anda gunakan. Untuk alasan ini, menurut saya bukan ide yang baik untuk mendukung pengembalian nilai dari penutupan ke pemanggil super_drain_by_value ; kira-kira analog dengan mengapa konstruk loop {} dapat mengembalikan nilai, tetapi jenis loop lainnya dievaluasi menjadi () .

Mengenai umum, saya menyadari sebenarnya ada dua kasus untuk penghentian dini: satu di mana sisa Vec dijatuhkan, dan yang lain dibiarkan di tempatnya. Jika penghentian prematur tidak membawa nilai (seperti yang saya pikir seharusnya tidak), itu menjadi secara semantik setara dengan mengembalikan Keep(n) atau Drop(n) , di mana n adalah jumlah barang yang belum diperiksa. Namun, saya pikir penghentian dini harus diperlakukan sebagai kasus terpisah, karena dibandingkan dengan menggunakan Keep / Drop , lebih mudah digunakan melalui jalur kode yang lebih sederhana.

Untuk membuat API sedikit lebih ramah, saya pikir opsi yang lebih baik adalah membuat penutupan mengembalikan () dan meneruskannya sebagai objek pembantu (yang akan saya rujuk di sini sebagai "pembaru") yang dapat digunakan untuk memeriksa setiap elemen dari Vec dan mengontrol apa yang terjadi padanya. Metode ini dapat memiliki nama yang familiar seperti borrow , borrow_mut , dan take , dengan metode tambahan seperti keep_next(n) atau drop_remainder() . Menggunakan jenis API ini, penutupan jauh lebih sederhana dalam kasus sederhana dan tidak lebih kompleks dalam kasus kompleks. Dengan membuat sebagian besar metode pembaru mengambil nilai self , mudah untuk mencegah penelepon melakukan hal-hal seperti memanggil take lebih dari sekali, atau memberikan instruksi yang bertentangan tentang apa yang harus dilakukan dalam iterasi berikutnya.

Tapi kita masih bisa berbuat lebih baik! Saya menyadari pagi ini bahwa, seperti yang sering terjadi, masalah ini serupa dengan masalah yang telah diselesaikan secara definitif dalam bahasa fungsional, dan kita dapat menyelesaikannya dengan solusi analog. Saya berbicara tentang API "ritsleting", pertama kali dijelaskan dalam makalah singkat ini dengan kode sampel di OCaml, dan dijelaskan di sini dengan kode Haskell dan tautan ke makalah lain yang relevan. Ritsleting menyediakan cara yang sangat umum untuk melintasi struktur data dan memperbaruinya "di tempat" menggunakan operasi apa pun yang didukung oleh struktur data tertentu. Cara lain untuk memikirkannya adalah bahwa ritsleting adalah semacam iterator turbocharged dengan metode tambahan untuk melakukan operasi pada jenis struktur data tertentu.

Di Haskell, Anda mendapatkan semantik "di tempat" dengan membuat ritsleting menjadi monad; di Rust, Anda dapat melakukan hal yang sama menggunakan masa pakai dengan membuat ritsleting menahan referensi mut ke Vec . Ritsleting untuk Vec sangat mirip dengan pembaru yang saya jelaskan di atas, kecuali bahwa alih-alih meneruskannya berulang kali ke penutupan, Vec hanya menyediakan metode untuk membuat ritsleting sendiri, dan ritsleting memiliki akses eksklusif ke Vec selama itu ada. Penelepon kemudian bertanggung jawab untuk menulis loop untuk melintasi array, memanggil metode di setiap langkah untuk menghapus item saat ini dari Vec , atau membiarkannya di tempatnya. Terminasi dini dapat diimplementasikan dengan memanggil metode yang menggunakan ritsleting. Karena loop berada di bawah kendali pemanggil, menjadi mungkin untuk melakukan hal-hal seperti memproses lebih dari satu item dalam setiap iterasi dari loop, atau menangani sejumlah item tanpa menggunakan loop sama sekali.

Berikut adalah contoh yang sangat dibuat-buat yang menunjukkan beberapa hal yang dapat dilakukan ritsleting:

/// 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.
}

Sebagai perbandingan, inilah fungsi yang kurang lebih sama menggunakan drain_filter , kecuali hanya berpura-pura berhenti lebih awal. Ini tentang jumlah kode yang sama, tetapi IMHO jauh lebih sulit untuk dibaca karena arti dari nilai yang dikembalikan oleh penutupan tidak jelas, dan menggunakan flag boolean yang bisa berubah untuk membawa informasi dari satu iterasi ke iterasi berikutnya, di mana ritsleting versi mencapai hal yang sama dengan aliran kontrol. Karena item yang dihapus selalu dihasilkan oleh iterator, kita memerlukan langkah filter terpisah untuk menghapus nilai negatif dari output, yang berarti kita perlu memeriksa nilai negatif di dua tempat, bukan satu. Agak jelek juga bahwa ia harus melacak posisi di v ; implementasi drain_filter memiliki informasi itu, tetapi pemanggil tidak memiliki akses ke sana.

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 Posting Anda sebelumnya mengingatkan saya pada scanmut crate , khususnya Remover struct , dan konsep "ritsleting" yang Anda sebutkan tampaknya sangat mirip! Itu memang tampak jauh lebih ergonomis daripada metode yang menutup saat Anda menginginkan kontrol total.

Either way, ini mungkin tidak terlalu relevan dengan apakah drain_filter harus distabilkan, karena kita selalu dapat menukar internal nanti. drain_filter itu sendiri akan selalu sangat berguna karena kemudahannya. Satu-satunya perubahan yang masih ingin saya lihat sebelum stabilisasi adalah parameter RangeBounds .

@timvermeulen Saya pikir masuk akal untuk menambahkan parameter RangeBounds , tetapi pertahankan tanda tangan penutupan saat ini ( F: FnMut(&mut T) -> bool ).
Anda selalu dapat melakukan pasca-proses elemen yang terkuras dengan filter_map atau apa pun yang Anda inginkan.
(Bagi saya, sangat penting bahwa penutupan memungkinkan mutasi elemen, karena retain tidak mengizinkannya (distabilkan sebelum kesalahan ini ditemukan).)

Ya, itu tampaknya menjadi keseimbangan sempurna antara kenyamanan dan kegunaan.

@timvermeulen Ya, saya menyimpang agak jauh dari topik utama.

Satu hal yang saya perhatikan yang relevan dengan topik aslinya adalah agak sulit untuk mengingat apa arti nilai pengembalian dari penutupan--apakah dikatakan apakah akan menyimpan item atau menghapusnya? Saya pikir akan sangat membantu bagi para dokumen untuk menunjukkan bahwa v.drain_filter(p) setara dengan v.iter().filter(p) dengan efek samping.

Dengan filter , menggunakan nilai boolean masih kurang ideal untuk kejelasan, tetapi ini adalah fungsi yang sangat terkenal, dan IMHO setidaknya agak intuitif bahwa predikat menjawab pertanyaan "haruskah saya menyimpan ini?" daripada "haruskah saya membuang ini?" Dengan drain_filter , logika yang sama berlaku jika Anda memikirkannya dari perspektif iterator, tetapi jika Anda memikirkannya dari perspektif input Vec , pertanyaannya menjadi "haruskah saya TIDAK Simpan ini?"

Adapun kata-kata yang tepat, saya mengusulkan untuk mengganti nama parameter filter menjadi predicate (agar cocok dengan Iterator::filter ) dan menambahkan kalimat ini di suatu tempat dalam deskripsi:

Untuk mengingat bagaimana nilai kembalian predicate digunakan, mungkin perlu diingat bahwa drain_filter identik dengan Iterator::filter dengan efek samping tambahan dari menghapus yang dipilih item dari self .

@ johnw42 Ya, poin bagus. Saya pikir nama seperti drain_where akan jauh lebih jelas.

Jika Anda akan masuk ke penamaan bikeshedding; pastikan Anda telah membaca semua komentar; bahkan yang tersembunyi. Banyak varian telah diusulkan, misalnya https://github.com/rust-lang/rust/issues/43244#issuecomment -331559537

Tapi… itu harus diberi nama draintain() ! Tidak ada nama lain yang seindah!

Saya cukup tertarik dengan masalah ini, dan saya membaca seluruh utasnya, jadi sebaiknya saya mencoba merangkum apa yang dikatakan semua orang, dengan harapan membantu ini menjadi stabil. Saya telah menambahkan beberapa komentar saya sendiri selama ini, tetapi saya mencoba untuk membuatnya senetral mungkin.

Penamaan

Berikut adalah ringkasan tanpa opini dari nama-nama yang saya lihat diusulkan:

  • drain_filter : Nama yang digunakan dalam implementasi saat ini. Konsisten dengan nama lain seperti filter_map . Memiliki keuntungan analog dengan drain().filter() , tetapi dengan lebih banyak efek samping.
  • drain_where : Memiliki manfaat untuk menunjukkan apakah true menghasilkan pengeringan _out_ atau penyaringan _in_, yang mungkin sulit diingat dengan nama lain. Tidak ada preseden dalam std untuk sufiks _where , tetapi ada banyak preseden untuk sufiks serupa.
  • Variasi dari drain().where() , karena where sudah menjadi kata kunci.
  • drain_retain : Konsisten dengan retain , tetapi retain dan drain memiliki interpretasi yang berlawanan dari nilai boolean yang dikembalikan oleh penutupan, yang mungkin membingungkan.
  • filtered_drain
  • drain_if
  • drain_when
  • remove_if

Parameter

Mungkin ada baiknya menambahkan argumen rentang untuk konsistensi dengan drain .

Dua format penutupan telah disarankan, FnMut(&mut T) -> bool dan FnMut(T) -> Result<T, U> . Yang terakhir lebih fleksibel, tetapi juga lebih kikuk.

Membalikkan kondisi boolean ( true berarti "tetap di Vec ") agar konsisten dengan retain telah dibahas, tetapi kemudian tidak akan konsisten dengan drain ( true berarti "menguras dari Vec ").

Melepaskan

Saat penutupan filter panik, iterator DrainFilter dihapus. Iterator kemudian harus menyelesaikan menguras Vec , tetapi untuk melakukannya ia harus memanggil penutupan filter lagi, yang berisiko menimbulkan kepanikan ganda. Ada beberapa solusi, tetapi semuanya adalah kompromi:

  • Jangan selesai menguras air saat jatuh. Ini cukup berlawanan dengan intuisi ketika digunakan dengan adaptor seperti find atau all . Selain itu, ini membuat idiom v.drain_filter(...); tidak berguna karena iteratornya malas.

  • Selalu selesaikan pengurasan saat jatuh. Ini berisiko kepanikan ganda (yang mengakibatkan pembatalan), tetapi membuat perilaku konsisten.

  • Selesaikan pengurasan hanya saat jatuh jika saat ini tidak dilepas. Ini memperbaiki kepanikan ganda sepenuhnya, tetapi membuat perilaku drain_filter tidak dapat diprediksi: memasukkan DrainFilter ke dalam destruktor mungkin _kadang-kadang_ tidak benar-benar melakukan tugasnya.

  • Selesaikan pengurasan hanya jika penutup filter tidak panik. Ini adalah kompromi saat ini yang dibuat oleh drain_filter . Properti yang bagus dari pendekatan ini adalah kepanikan dalam penutupan filter "hubung singkat", yang bisa dibilang cukup intuitif.

Perhatikan bahwa implementasi saat ini baik-baik saja dan tidak pernah bocor selama struct DrainFilter dijatuhkan (walaupun dapat menyebabkan pembatalan). Implementasi sebelumnya tidak aman/bebas kebocoran.

Tiriskan-on-drop

DrainIter dapat menyelesaikan pengurasan vektor sumber saat dijatuhkan, atau hanya dapat terkuras saat next dipanggil (iterasi malas).

Argumen yang mendukung drain-on-drop:

  • Konsisten dengan perilaku drain .

  • Berinteraksi dengan baik dengan adaptor lain seperti all , any , find , dll...

  • Mengaktifkan idiom vec.drain_filter(...); .

  • Fungsi malas dapat diaktifkan secara eksplisit melalui drain_lazy -metode gaya atau adaptor lazy() pada DrainIter (dan bahkan pada Drain , karena kompatibel dengan menambahkan metode).

Argumen yang mendukung iterasi malas:

  • Konsisten dengan hampir semua iterator lainnya.

  • Fungsionalitas "drain-on-drop" dapat diaktifkan secara eksplisit melalui adaptor pada DrainIter , atau bahkan melalui adaptor umum Iterator::exhausting (lihat RFC #2370 ).

Saya mungkin melewatkan beberapa hal, tetapi setidaknya saya harap ini membantu pendatang baru saat membaca sekilas utas.

@negamartin

Bukankah opsi drain-on-drop mengharuskan iterator mengembalikan referensi ke item alih-alih nilai yang dimiliki? Saya pikir itu akan membuat tidak mungkin menggunakan drain_filter sebagai mekanisme untuk menghapus dan mengambil kepemilikan item yang cocok dengan kondisi tertentu (yang merupakan kasus penggunaan asli saya).

Saya rasa tidak, karena perilaku implementasi saat ini justru menguras-on-drop sambil menghasilkan nilai yang dimiliki. Either way, saya tidak melihat bagaimana drain-on-drop akan membutuhkan elemen pinjaman, jadi saya pikir kita memiliki dua ide berbeda tentang apa artinya drain-on-drop.

Untuk lebih jelasnya, ketika saya mengatakan drain-on-drop, maksud saya hanya perilaku ketika iterator tidak sepenuhnya dikonsumsi: Haruskah semua item yang cocok dengan penutupan dikeringkan bahkan jika iterator tidak sepenuhnya dikonsumsi? Atau hanya sampai elemen yang dikonsumsi, membiarkan sisanya tidak tersentuh?

Secara khusus, ini adalah perbedaan antara:

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]);

Hanya membuang ide, tetapi API lain yang mungkin bisa berupa:

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

Ini kurang fleksibel daripada opsi lain, tetapi menghindari masalah apa yang harus dilakukan ketika DrainFilter dijatuhkan.

Rasanya bagi saya seperti semua ini bagi saya semakin tidak terlihat seperti retain_mut() ( retain() dengan referensi yang bisa berubah diteruskan ke penutupan), yang pertama dan terutama dimaksudkan untuk diberikan . Bisakah kami menyediakan retain_mut() untuk saat ini selain mengerjakan desain saluran pembuangan yang disaring? Atau apakah saya melewatkan sesuatu?

@BartMassey

yang pertama dan terutama dimaksudkan untuk diberikan.

Saya tidak berpikir itu masalahnya. Saya secara khusus menggunakan drain_filter untuk mengambil kepemilikan item berdasarkan kriteria filter. Drain dan DrainFilter menghasilkan item sedangkan retain tidak.

@negamartin

Untuk memperjelas, ketika saya mengatakan drain-on-drop, maksud saya hanya perilaku ketika iterator tidak sepenuhnya dikonsumsi

Oke. Itu kesalahan saya. Saya salah memahami definisi Anda. Saya telah menafsirkannya sebagai "tidak ada yang dihapus dari vec sampai dijatuhkan", yang sebenarnya tidak masuk akal.

Argumen yang mendukung iterasi malas

Saya pikir itu perlu konsisten dengan drain . Iterator::exhausting RFC tidak diterima dan akan sangat aneh jika drain dan drain_filter memiliki perilaku pengurasan yang tampaknya berlawanan.

@negamartin

drain_filter: Nama yang digunakan dalam implementasi saat ini. Konsisten dengan nama lain seperti filter_map. Memiliki keuntungan analog dengan drain().filter() , tetapi dengan lebih banyak efek samping.

Itu tidak analog (itulah mengapa kita membutuhkan retain_mut / drain_filter ):
drain().filter() akan menguras bahkan elemen-elemen yang mengembalikan penutupan filter false !

Saya baru saja melihat baris kecil dalam komentar oleh tim lib di #RFC 2870 :

Kemungkinan mungkin termasuk "membebani berlebihan" metode dengan membuatnya generik, atau pola pembangun.

Apakah kompatibel ke belakang untuk membuat metode generik jika masih menerima tipe beton sebelumnya? Jika demikian, saya percaya itu akan menjadi jalan terbaik ke depan.

(Pola pembangun agak tidak intuitif dengan iterator, karena metode pada iterator biasanya adaptor, bukan pengubah perilaku. Selain itu, tidak ada preseden, misalnya chunks dan chunks_exact adalah dua metode terpisah , bukan kombo chunks().exact() .)

Tidak, tidak dengan desain saat ini sejauh yang saya tahu sebagai inferensi tipe yang
bekerja sebelumnya sekarang bisa gagal karena ambiguitas tipe. Genetika dengan default
jenis untuk fungsi akan membantu tetapi sangat sulit untuk dilakukan dengan benar.

Pada Jumat, 12 Juni 2020, 21:21 negamartin [email protected] menulis:

Saya baru saja melihat baris kecil dalam komentar oleh tim lib di #RFC 2870
https://github.com/rust-lang/rfcs/pull/2369 :

Kemungkinan mungkin termasuk "membebani berlebihan" metode dengan membuatnya generik,
atau pola pembangun.

Apakah kompatibel ke belakang untuk membuat metode generik jika masih menerima
jenis beton sebelumnya? Jika demikian, saya percaya itu akan menjadi cara terbaik
maju.

(Pola pembangun agak tidak intuitif dengan iterator, karena metode aktif
iterator biasanya adaptor, bukan pengubah perilaku. Selain itu, ada
tidak ada preseden, misalnya chunks dan chunks_exact adalah dua yang terpisah
metode, bukan kombo chunks().exact().)


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/43244#issuecomment-643444213 ,
atau berhenti berlangganan
https://github.com/notifications/unsubscribe-auth/AB2HJELPWXNXJMX2ZDA6F63RWJ53FANCNFSM4DTDLGPA
.

Apakah kompatibel ke belakang untuk membuat metode generik jika masih menerima tipe beton sebelumnya? Jika demikian, saya percaya itu akan menjadi jalan terbaik ke depan.

Tidak, karena ini merusak inferensi tipe dalam beberapa kasus. Misalnya foo.method(bar.into()) akan bekerja dengan argumen tipe konkret, tetapi tidak dengan argumen umum.

Saya pikir drain_filter seperti yang diterapkan sekarang sangat berguna. Bisakah itu distabilkan seperti apa adanya? Jika abstraksi yang lebih baik ditemukan di masa depan, tidak ada yang menghentikan mereka untuk diperkenalkan juga.

Proses apa yang harus saya mulai untuk mencoba menambahkan retain_mut() , terlepas dari apa pun yang terjadi dengan drain_filter() ? Tampaknya bagi saya bahwa persyaratannya telah berbeda, dan retain_mut() itu masih berguna untuk dimiliki terlepas dari apa yang terjadi dengan drain_filter() .

@BartMassey untuk API perpustakaan baru yang tidak stabil, saya pikir membuat PR dengan implementasi seharusnya baik-baik saja. Ada petunjuk di https://rustc-dev-guide.rust-lang.org/implementing_new_features.html untuk mengimplementasikan fitur dan di https://rustc-dev-guide.rust-lang.org/getting-started.html #building -and-testing-stdcorealloctestproc_macroetc untuk menguji perubahan Anda.

Saya telah berjuang dengan perbedaan API antara HashMap dan BTreeMap hari ini dan saya hanya ingin menyampaikan peringatan bahwa menurut saya penting bahwa berbagai koleksi berusaha untuk mempertahankan API yang koheren setiap kali dibuat akal, sesuatu yang pada saat ini tidak selalu terjadi.

Misalnya String, Vec, HashMap, HashSet, BinaryHeap dan VecDeque memiliki metode retain , tetapi LinkedList dan BTreeMap tidak. Saya merasa sangat aneh karena retain sepertinya metode yang lebih alami untuk LinkedList atau Peta daripada untuk vektor di mana penghapusan acak adalah operasi yang sangat mahal.

Dan ketika Anda menggali sedikit lebih dalam, itu bahkan lebih membingungkan: penutupan HashMap::retain memberikan nilai dalam referensi yang bisa berubah, tetapi koleksi lain mendapatkan referensi yang tidak dapat diubah (dan String mendapatkan char ).

Sekarang saya melihat bahwa API baru seperti drain_filter sedang ditambahkan sehingga 1/ tampaknya tumpang tindih dengan retain dan 2/ tidak distabilkan untuk semua koleksi secara bersamaan:

  • HashMap::drain_filter ada di repo hulu tetapi belum dikirimkan dengan std AFAIK Rust (tidak muncul di dokumen)
  • BTreeMap::drain_filter , Vec::drain_filter , LinkedList::drain_filter ada di std Rust, tetapi fitur terjaga keamanannya
  • VecDeque::drain_filter tampaknya tidak ada sama sekali, tidak muncul di dokumen
  • String::drain_filter juga tidak ada

Saya tidak memiliki pendapat yang kuat tentang cara terbaik untuk mengimplementasikan fitur ini, atau jika kami membutuhkan drain_filter , retain atau keduanya, tetapi saya sangat yakin bahwa API ini harus tetap konsisten di seluruh koleksi .

Dan mungkin yang lebih penting, metode serupa dari koleksi yang berbeda harus memiliki semantik yang sama. Sesuatu yang implementasi retain melanggar IMO.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat