Rust: Masalah pelacakan untuk Pin API (RFC 2349)

Dibuat pada 18 Mar 2018  ·  211Komentar  ·  Sumber: rust-lang/rust

Masalah pelacakan untuk rust-lang / rfcs # 2349

Stabilisasi pemblokiran:

  • [x] Implementasi (PR # 49058)
  • [] Dokumentasi

Pertanyaan yang belum terselesaikan:

  • [] Haruskah kami memberikan jaminan yang lebih kuat terkait kebocoran data !Unpin ?

Edit : Ringkasan komentar: https://github.com/rust-lang/rust/issues/49150#issuecomment -409939585 (di bagian tersembunyi-oleh-default)

B-RFC-approved C-tracking-issue T-lang T-libs

Komentar yang paling membantu

@rfcbot peduli api-refactor

Sedikit struktur inspirasi dan tadi malam saya menemukan cara bagaimana kami dapat memfaktor ulang API ini sehingga hanya ada satu tipe Pin , yang membungkus sebuah pointer, daripada harus membuat versi yang disematkan dari setiap pointer tunggal. Ini bukanlah pembentukan ulang API yang mendasar dengan cara apa pun, tetapi rasanya lebih baik untuk menarik komponen "menyematkan memori" ke bagian yang dapat disusun.

Semua 211 komentar

Sekarang saya memperhatikan bahwa stack pinning bukan bagian dari RFC, @withoutboats apakah Anda berencana merilis peti untuk ini, atau haruskah saya menyalin kode contoh ke dalam peti yang membutuhkannya?

@ Nemo157 Anda harus menyalinnya dan melaporkan pengalaman Anda!

Pertanyaan yang belum terselesaikan tentang membocorkan data Unpin berkaitan dengan ini. API itu tidak benar jika kami mengatakan Anda tidak dapat menimpa Unpin data dalam Pin kecuali destruktor berjalan, seperti yang diminta @cramertj . Ada API stack pinning lain yang kurang ergonomis yang berfungsi untuk ini. Tidak jelas pilihan apa yang tepat di sini - apakah API penjepit tumpukan ergonomis lebih berguna atau apakah jaminan tambahan tentang kebocoran lebih berguna?

Satu hal yang akan saya perhatikan adalah bahwa stack pinning tidak cukup untuk hal-hal seperti Future::poll di dalam makro await! , karena itu tidak memungkinkan kita untuk melakukan polling dalam satu putaran. Saya akan tertarik jika Anda mengalami masalah itu, dan bagaimana / jika Anda menyelesaikannya jika Anda melakukannya.

Penggunaan saya saat ini cukup sepele, eksekutor utas tunggal menjalankan satu StableFuture tanpa dukungan pemijahan . Beralih ke API seperti saran @cramertj akan berfungsi dengan baik dengan ini. Saya bertanya-tanya bagaimana cara memperpanjang ini untuk memungkinkan pemijahan beberapa StableFuture s, tetapi setidaknya dengan proyek saya saat ini itu tidak perlu.

Baru saja mencoba bereksperimen dengan API. Sepertinya definisi berikut (disarankan oleh RFC) dari Future tidak lagi aman untuk objek?

trait Future {
    type Item;
    type Error;

    fn poll(self: Pin<Self>, cx: &mut task::Context) -> Poll<Self::Item, Self::Error>;
}

Sudahlah. Menemukan catatan tentang rencana untuk membuat arbitrary_self_types object-safe.

@tokopedia

Satu hal yang akan saya perhatikan adalah bahwa stack pinning tidak cukup untuk hal-hal seperti Future :: poll inside the await! makro, karena tidak memungkinkan kita untuk melakukan polling dalam satu putaran.

Bisakah Anda menjelaskannya?

@RalfJung Anda membutuhkan Pin untuk mendukung reborrows sebagai Pin , yang saat ini tidak.

@cramertj Kedengarannya seperti pembatasan API Pin , bukan API penyematan tumpukan?

@RalfJung Ya, itu benar. Namun, PinBox dapat dipinjam kembali sebagai Pin , sedangkan tipe stack-pinned tidak dapat (satu pinjam sebagai Pin membuat pinjaman untuk seluruh masa pakai tipe tumpukan).

Diberikan Pin , saya dapat meminjamnya sebagai &mut Pin dan kemudian menggunakan Pin::borrow - itu adalah bentuk peminjaman kembali. Kurasa itu bukan jenis pemberitaan yang kau bicarakan?

@RalfJung Tidak-- metode seperti Future::poll direncanakan untuk mengambil self: Pin<Self> , daripada self: &mut Pin<Self> (yang bukan tipe self valid karena bukan ' t Deref<item = Self> - Deref<Item = Pin<Self>> ).

Mungkin kita bisa mendapatkan ini untuk bekerja dengan Pin::borrow sebenarnya. Saya tidak yakin.

@cramertj Saya tidak menyarankan untuk menelepon poll pada x: &mut Pin<Self> ; Saya memikirkan x.borrow().poll() .

@RalfJung Oh, begitu. Ya, menggunakan metode untuk meminjam kembali secara manual bisa berhasil.

Saya akan mencoba dan mengingat untuk memposting contoh beberapa hal yang saya lakukan dengan Pin s minggu depan, sejauh yang saya tahu peminjaman kembali bekerja dengan sempurna. Saya memiliki versi yang disematkan dari sifat futures::io::AsyncRead bersama dengan adaptor yang berfungsi seperti fn read_exact<'a, 'b, R: PinRead + 'a>(read: Pin<'a, R>, buf: &'b [u8]) -> impl StableFuture + 'a + 'b dan saya dapat mengerjakan ini menjadi StableFuture yang relatif kompleks yang hanya tumpukan yang disematkan di bagian atas tingkat.

Inilah contoh lengkap dari apa yang saya gunakan untuk membaca:

pub trait Read {
    type Error;

    fn poll_read(
        self: Pin<Self>,
        cx: &mut task::Context,
        buf: &mut [u8],
    ) -> Poll<usize, Self::Error>;
}

pub fn read_exact<'a, 'b: 'a, R: Read + 'a>(
    mut this: Pin<'a, R>,
    buf: &'b mut [u8],
) -> impl StableFuture<Item = (), Error = Error<R::Error>>
         + Captures<'a>
         + Captures<'b> {
    async_block_pinned! {
        let mut position = 0;
        while position < buf.len() {
            let amount = await!(poll_fn(|cx| {
                Pin::borrow(&mut this).poll_read(cx, &mut buf[position..])
            }))?;
            position += amount;
            if amount == 0 {
                Err(Error::UnexpectedEof)?;
            }
        }
        Ok(())
    }
}

Ini sedikit menjengkelkan karena Anda harus melewati instance di mana-mana sebagai Pin dan menggunakan Pin::borrow setiap kali Anda memanggil fungsi pada mereka.

#[async]
fn foo<'a, R>(source: Pin<'a, R>) -> Result<!, Error> where R: Read + 'a {
    loop {
        let mut buffer = [0; 8];
        await!(read_exact(Pin::borrow(&mut source), &mut buffer[..]));
        // do something with buffer
    }
}

Saya baru saja berpikir bahwa saya bisa impl<'a, R> Read for Pin<'a R> where R: Read + 'a untuk mengatasi masalah harus meneruskan nilai sebagai Pin<'a, R> mana-mana, sebagai gantinya saya bisa menggunakan fn foo<R>(source: R) where R: Read + Unpin . Sayangnya itu gagal karena Pin<'a, R>: !Unpin , menurut saya aman untuk menambahkan unsafe impl<'a, T> Unpin for Pin<'a, T> {} karena pin itu sendiri hanyalah referensi dan data di belakangnya masih disematkan.

Perhatian: Sepertinya kita ingin kebanyakan tipe di libstd mengimplementasikan Unpin tanpa syarat, bahkan jika parameter tipe mereka bukan Pin . Contohnya adalah Vec , VecDeque , Box , Cell , RefCell , Mutex , RwLock , Rc , Arc . Saya berharap sebagian besar peti tidak akan berpikir tentang menyematkan sama sekali, dan karenanya hanya tipenya menjadi Unpin jika semua bidangnya adalah Unpin . Itu pilihan suara, tapi itu mengarah ke antarmuka lemah yang tidak perlu.

Akankah ini menyelesaikan sendiri jika kita memastikan untuk mengimplementasikan Unpin untuk semua jenis penunjuk libstd (bahkan mungkin termasuk penunjuk mentah) dan UnsafeCell ? Apakah itu sesuatu yang ingin kita lakukan?

Akankah ini menyelesaikan sendiri jika kita memastikan untuk mengimplementasikan Lepas pin untuk semua jenis penunjuk libstd (bahkan mungkin termasuk penunjuk mentah) dan UnsafeCell? Apakah itu sesuatu yang ingin kita lakukan?

Ya, sepertinya situasinya sama dengan Send bagi saya.

Sebuah pertanyaan baru muncul di benak saya: Kapan Pin dan PinBox Send ? Sekarang, mekanisme sifat otomatis membuat mereka Send setiap kali T adalah Send . Tidak ada alasan apriori untuk melakukan itu; sama seperti tipe dalam tipe bersama memiliki ciri penanda sendiri untuk kemampuan mengirim (disebut Sync ), kita bisa membuat ciri penanda yang mengatakan ketika Pin<T> adalah Send , misal PinSend . Pada prinsipnya, dimungkinkan untuk menulis tipe yang Send tetapi bukan PinSend dan sebaliknya.

@RalfJung Pin adalah Kirim ketika &mut T adalah Kirim. PinBox adalah Kirim ketika Box<T> adalah Kirim. Saya tidak melihat alasan bagi mereka untuk berbeda.

Nah, seperti beberapa jenis adalah Send tetapi bukan Sync , Anda dapat menggunakan jenis yang mengandalkan "Setelah metode ini dipanggil dengan Pin<Self> , saya tidak akan pernah dipindahkan ke utas lain ". Misalnya, ini dapat menimbulkan masa depan yang dapat dikirim sebelum dimulai untuk pertama kalinya, tetapi kemudian harus tetap dalam satu utas (seperti dapat dipindahkan sebelum dimulai, tetapi kemudian harus tetap disematkan). Saya tidak yakin apakah saya dapat memberikan contoh yang meyakinkan, mungkin sesuatu tentang masa depan yang menggunakan penyimpanan lokal-thread?

Saya baru saja membahas masalah seumur hidup yang disebutkan oleh @Diggsey . Saya percaya Pin<Option<T>> -> Option<Pin<T>> harus menjadi operasi yang aman tetapi tampaknya tidak mungkin untuk diterapkan bahkan menggunakan API yang tidak aman saat ini, apalagi API jenis apa yang diperlukan untuk membuat kode aman ini:

trait OptionAsPin<T> {
    fn as_pin<'a>(self: Pin<'a, Self>) -> Option<Pin<'a, T>>;
}

impl<T> OptionAsPin<T> for Option<T> {
    fn as_pin<'a>(self: Pin<'a, Self>) -> Option<Pin<'a, T>> {
        match *unsafe { Pin::get_mut(&mut self) } {
            Some(ref mut item) => Some(unsafe { Pin::new_unchecked(item) }),
            None => None,
        }
    }
}

(Itu mungkin untuk solusi menggunakan transmute untuk memaksa masa hidup, tapi itu membuatku merasa terlalu menjijikkan).

Saya ingin menambahkan pertanyaan yang belum terjawab: Haruskah kita menambahkan kembali jenis referensi yang disematkan bersama? Menurut saya jawabannya adalah ya. Lihat posting ini

Saya baru saja membaca bahwa futures 0.2 tidak final seperti yang saya kira , jadi mungkin masih memungkinkan untuk mengganti nama Pin kembali ke PinMut dan menambahkan versi bersama.

@RalfJung Saya membaca posting blog Anda lagi dengan lebih teliti untuk memahami perubahan yang Anda usulkan.

Saya pikir Anda telah menemukan kasus penggunaan yang berpotensi menarik karena memiliki varian Pin yang tidak dapat diubah, tetapi saya tidak memahami komentar Anda tentang Deref dan &Pin<T> <=> &&T . Bahkan jika Pin<T> dapat diturunkan menjadi &T dengan aman, itu tidak membuatnya setara, karena &T tidak dapat diubah menjadi Pin<T> . Saya tidak melihat alasan untuk membuat konversi aman menjadi tidak aman (dengan menghilangkan safe Deref impl).

Saat ini metode map pada Pin memiliki tanda tangan

pub unsafe fn map<U, F>(this: &'b mut Pin<'a, T>, f: F) -> Pin<'b, U>

Apa alasannya bukan sebagai berikut?

pub unsafe fn map<U, F>(this: Pin<'a, T>, f: F) -> Pin<'a, U>

Seperti berdiri, saya tidak dapat mengubah Pin dari satu jenis menjadi Pin dari salah satu bidangnya tanpa memperpendek masa pakai yang tidak perlu.

Masalah lain dengan metode map adalah tampaknya tidak mungkin untuk mengubah Pin dari sebuah struct menjadi dua Pin s, masing-masing bidang berbeda dari struct. Apa cara yang benar untuk mencapai itu?

Saya telah menggunakan makro ini untuk itu:

macro_rules! pin_fields {
    ($pin:expr, ($($field:ident),+ $(,)?)) => {
        unsafe {
            let s = Pin::get_mut(&mut $pin);
            ($(Pin::new_unchecked(&mut s.$field),)+)
        }
    };
}

Dalam banyak diskusi tentang Pin tampaknya ada asumsi bahwa "memproyeksikan" pin ke bidang pribadi harus dianggap aman. Saya rasa itu tidak sepenuhnya benar. Misalnya, komentar dokumen pada map saat ini berbunyi:

Anda harus menjamin bahwa data yang Anda kembalikan tidak akan berpindah selama nilai argumen tidak berpindah (misalnya, karena ini adalah salah satu bidang dari nilai tersebut), dan juga bahwa Anda tidak keluar dari argumen yang Anda terima. fungsi interior.

Jaminan bahwa "data yang Anda kembalikan tidak akan bergerak selama nilai argumen tidak bergerak" adalah deskripsi kontrak yang benar yang harus dipegang oleh pemanggil map . Tanda kurung "(misalnya, karena ini adalah salah satu bidang dari nilai tersebut)" tampaknya menyiratkan bahwa selama Anda hanya mengembalikan referensi ke bidang pribadi Anda sendiri, Anda dijamin aman. Tetapi itu tidak benar jika Anda menerapkan Drop . Im Drop akan menghasilkan &mut self , bahkan ketika kode lain telah melihat Pin<Self> . Ia dapat melanjutkan untuk menggunakan mem::replace atau mem::swap untuk keluar dari bidangnya, melanggar janji yang dibuat oleh penggunaan "benar" sebelumnya dari map .

Dengan kata lain: menggunakan panggilan "proyeksi pin yang benar" ke Pin::map (panggilan tersebut terlihat seperti unsafe { Pin::map(&mut self, |x| &mut x.p) } ), tanpa panggilan lain unsafe , seseorang dapat menghasilkan tidak sehat / tidak terdefinisi tingkah laku. Berikut tautan taman bermain yang menunjukkan ini.

Ini tidak menyiratkan bahwa ada yang salah dengan API saat ini. Ini menunjukkan bahwa Pin::map harus ditandai sebagai unsafe , yang sudah ada. Saya juga tidak berpikir ada banyak bahaya orang yang secara tidak sengaja tersandung ini ketika menerapkan masa depan atau sejenisnya - Anda benar-benar harus keluar dari jalan Anda untuk keluar dari bidang Anda sendiri dengan Drop impl, dan Saya ragu ada banyak alasan praktis untuk melakukannya.

Tetapi saya pikir komentar dokumen untuk map mungkin ingin menyebutkan ini, dan saya juga berpikir itu membatalkan beberapa ide untuk ekstensi yang saya lihat di RFC dan diskusi sekitarnya:

  • Tidak boleh ada makro / berasal yang melakukan "pin proyeksi" dan membuatnya tampak aman. Projection benar-benar memaksakan kontrak pada kode lain yang mengelilinginya, yang tidak dapat diterapkan sepenuhnya oleh compiler. Jadi itu harus membutuhkan penggunaan kata kunci unsafe setiap kali selesai.
  • RFC menyebutkan bahwa jika Pin diubah menjadi fitur bahasa &'a pin T , itu akan menjadi "sepele untuk memproyeksikan melalui bidang." Saya yakin saya telah menunjukkan bahwa ini masih memerlukan unsafe , bahkan jika terbatas pada memproyeksikan bidang pribadi.

@tokopedia

Bahkan jika Pindapat dilemparkan ke & T dengan aman, itu tidak membuatnya setara, karena & T tidak dapat dimasukkan ke Pin.

Memang, itu belum cukup. Mereka sama dalam model saya karena di posting saya sebelumnya , kami membuat definisi berikut:

Definisi 5: PinBox<T>.shr . Sebuah pointer ptr dan seumur hidup 'a memenuhi tipe bersama dari PinBox<T> jika ptr adalah pointer hanya-baca ke pointer lain inner_ptr sehingga T.shr('a, inner_ptr)

(Dan, secara implisit, definisi terkait untuk Pin<'a, T>.shr .)
Perhatikan bagaimana PinBox<T>.shr bergantung pada T.shr dan tidak ada yang lain. Ini membuat PinBox<T>.shr persis sama dengan Box<T>.shr , yang berarti &Box<T> = &PinBox<T> . Alasan serupa menunjukkan bahwa &&T = &Pin<T> .

Jadi, ini bukan konsekuensi dari API atau kontrak yang tertulis di RFC. Ini adalah konsekuensi dari model yang hanya memiliki tiga tipe: Dimiliki, dibagikan, disematkan. Jika Anda ingin menentang &&T = &Pin<T> , Anda harus membantah untuk memperkenalkan tipe keempat, "disematkan bersama".

@Bayu_joo

Dalam banyak diskusi seputar Pin, tampaknya ada asumsi bahwa "memproyeksikan" pin ke bidang pribadi harus dianggap aman. Saya rasa itu tidak sepenuhnya benar.

Itu poin yang sangat bagus! Untuk memperjelas, tidak ada masalah dengan membuat bidang p dari Shenanigans publik, bukan? Pada saat itu, setiap klien dapat menulis do_something_that_needs_pinning , dan tujuan RFC adalah membuatnya aman. (Saya tidak tahu mengapa RFC secara khusus menyebutkan bidang privat, interpretasi saya selalu bahwa itu seharusnya bekerja dengan semua bidang.)

Sangat menarik untuk melihat bagaimana ini berinteraksi dengan interpretasi drop dalam model saya . Di sana saya menulis bahwa drop(ptr) memiliki prasyarat T.pin(ptr) . Dengan interpretasi ini, kode sebenarnya yang salah dalam contoh Anda adalah implementasi drop ! (Sekarang saya bertanya-tanya mengapa saya tidak memperhatikan ini saat menulis posting ...) Saya pikir kami ingin mengizinkan proyeksi yang aman ke bidang pada akhirnya, dan kami benar-benar seharusnya tidak memberikan drop dengan &mut jika tipe melakukan pin. Itu jelas palsu.

Adakah cara agar kita dapat (a) membuat impl Drop tidak aman jika tipenya !Unpin , atau (b) mengubah tanda tangannya menjadi drop(self: Pin<Self>) jika T: !Unpin ? Keduanya terdengar sangat tidak masuk akal, tetapi di sisi lain keduanya akan menyelesaikan poin @MicahChalmer , sambil menjaga keamanan proyeksi lapangan. (Jika ini masih pra-1.0 dan kita dapat mengubah Drop::drop menjadi hanya selalu mengambil Pin<Self> ;) Tetapi tentu saja pada saat ini ini bukan lagi solusi khusus perpustakaan. Bagian yang menyedihkan adalah jika kita menstabilkan apa adanya, kita tidak akan pernah memiliki proyeksi lapangan yang aman.

@RalfJung Jadi saya lebih tertarik dengan pertanyaan praktis (seperti yang mungkin Anda harapkan: wink :). Menurut saya ada dua:

  • Haruskah Pin<T: !Unpin> menerapkan Deref<Target =T> ?
  • Haruskah ada Pin<T> dan PinMut<T> (yang sebelumnya adalah pin bersama)?

Saya tidak melihat alasan untuk menjawab pertanyaan pertama secara negatif. Saya pikir Anda telah membuka kembali pertanyaan kedua; Saya cenderung kembali ke dua jenis pin yang berbeda (tetapi tidak sepenuhnya yakin). Jika kami mengupgrade ini ke jenis referensi tingkat bahasa, kami akan memiliki &pin T dan &pin mut T .

Sepertinya apa pun yang kami lakukan, model Anda mungkin membutuhkan status tipe keempat untuk mencerminkan invarian API secara akurat. Saya tidak berpikir mengubah &&T menjadi &Pin<T> seharusnya aman.

Jadi saya lebih tertarik pada pertanyaan praktis (seperti yang mungkin Anda harapkan).

Cukup adil. ;) Namun, menurut saya penting bagi kita untuk memiliki setidaknya pemahaman dasar tentang apa dan tidak dijamin sekitar Pin setelah kode tidak aman memasuki gambar. Basic Rust memiliki beberapa tahun stabilisasi untuk mencari tahu, sekarang kami mengulanginya untuk Pin dalam beberapa bulan. Sementara Pin adalah tambahan perpustakaan-saja sejauh menyangkut sintaks, itu adalah tambahan yang signifikan sejauh menyangkut model. Saya pikir adalah bijaksana bahwa kita mendokumentasikan selengkap mungkin kode yang tidak aman dan tidak diperbolehkan untuk mengasumsikan atau melakukan sekitar Pin .

Sementara kekhawatiran ini bersifat teoritis sekarang, mereka tiba-tiba akan menjadi sangat praktis setelah kami mengalami ketidaknyamanan pertama karena kode yang tidak aman membuat asumsi yang tidak kompatibel.


Mengenai pertanyaan kedua Anda:

Sepertinya apa pun yang kami lakukan, model Anda mungkin membutuhkan status tipe keempat untuk mencerminkan invarian API secara akurat. Saya tidak berpikir untuk mengubah && T menjadi & Pinharus aman.

Itulah yang saya harapkan.

Jika kita ingin menjadi konservatif, kita dapat mengganti nama Pin menjadi PinMut tetapi tidak menambahkan PinShr (kemungkinan akan disebut Pin tetapi saya mencoba untuk menghilangkan keraguan di sini ) untuk saat ini, dan nyatakan bahwa kode tidak aman dapat mengasumsikan bahwa &PinMut<T> sebenarnya mengarah ke sesuatu yang dipasangi pin. Kemudian kita akan memiliki opsi untuk menambahkan Pin sebagai referensi yang disematkan bersama nanti.

Alasan praktis untuk memiliki PinShr telah dikemukakan oleh @comex : seharusnya memungkinkan untuk menyediakan getter yang PinShr<'a, Struct> menjadi PinShr<'a, Field> ; jika kita menggunakan &PinMut untuk pin bersama yang tidak berfungsi. Saya tidak tahu berapa banyak pengambil seperti itu akan dibutuhkan.


Untuk pertanyaan pertama Anda, tampaknya ada beberapa argumen kuat yang mendukung: (a) rencana saat ini untuk tipe mandiri arbitrer yang aman-objek, dan (b) mampu dengan aman menggunakan sejumlah besar API yang ada pada referensi bersama saat memegang a PinShr (atau PinMut ).

Sangat disayangkan bahwa kami tampaknya tidak memiliki cara yang mudah untuk menyediakan operasi serupa yang dapat bekerja pada &mut dan PinMut ; setelah semua banyak kode yang bekerja pada &mut tidak bermaksud menggunakan mem::swap . (Ini, seperti yang saya lihat, akan menjadi keuntungan utama dari solusi berbasis !DynSized atau sesuatu yang sebanding: &mut akan berubah menjadi jenis referensi yang mungkin atau mungkin tidak disematkan. Kami bisa memiliki ini sebagai jenis pustaka lain di Pin API, tapi itu agak tidak berguna mengingat kita sudah memiliki semua metode &mut luar sana.)

Ada satu argumen ringan yang menentang, yaitu tipe yang ingin melakukan hal-hal "menarik" untuk &T dan PinMut<T> . Itu akan sulit dilakukan, tidak semuanya mungkin dan seseorang harus sangat berhati-hati. Tapi saya tidak berpikir itu mengalahkan argumen bagus yang mendukung.

Dalam kasus ini (yaitu, dengan impl Deref ), PinShr<'a, T> harus datang dengan metode aman yang mengubahnya menjadi &'a T (mempertahankan masa pakai).


Dan ada kekhawatiran lain yang menurut saya harus kita selesaikan: Aturan untuk Pin::map dan / atau drop , terkait dengan apa yang @MicahChalmer perhatikan di atas. Kami memiliki dua pilihan:

  • Nyatakan bahwa menggunakan Pin::map dengan proyeksi ke bidang publik (tanpa deref, bahkan tidak implisit) selalu aman. Ini adalah interpretasi saya tentang RFC saat ini. Ini akan cocok dengan &mut dan & . Namun, kemudian kita memiliki masalah sekitar Drop : Sekarang kita dapat menulis kode aman yang menyebabkan UB diberi tipe !Unpin dibentuk dengan baik.
  • Jangan melakukan sesuatu yang lucu sekitar Drop . Kemudian kita harus menambahkan peringatan keras ke Pin::map yang bahkan menggunakannya untuk bidang publik dapat menyebabkan ketidaknyamanan. Bahkan jika kita pernah memiliki &pin T , kita tidak akan dapat menggunakannya untuk mengakses bidang dalam kode aman.

Satu-satunya argumen yang mungkin untuk opsi kedua yang dapat saya lihat adalah bahwa ini mungkin satu-satunya yang benar-benar dapat kita terapkan. ;) Saya pikir itu lebih rendah dalam segala hal yang mungkin - itu membuat &pin agak aneh dan tidak ergonomis bahkan jika itu menjadi built-in suatu hari, itu adalah footgun, itu menghalangi komposisi.

Mungkin ada cara untuk mencapai opsi pertama, tetapi itu tidak sepele dan saya tidak tahu bagaimana membuatnya kompatibel dengan mundur: Kita bisa menambahkan Unpin terikat ke Drop , dan menambahkan DropPinned yang tidak terikat dan dimana drop membutuhkan Pin<Self> . Mungkin Unpin terikat pada Drop dapat diterapkan dengan cara yang aneh di mana Anda dapat menulis impl Drop for S , tetapi ini menambahkan ikatan implisit ke S mengatakan itu itu harus Unpin . Mungkin tidak realistis. : / (Saya rasa ini juga merupakan titik di mana pendekatan berbasis !DynSized bekerja lebih baik - ternyata &mut T menjadi "mungkin atau mungkin tidak disematkan", menjaga drop suara.)

@RalfJung @MicahChalmer Saya pikir lebih baik mendokumentasikan bahwa jika Anda keluar dari bidang di Drop impl, memproyeksikan ke Pin bidang itu di tempat lain tidak sehat.

Memang, sudah terjadi hari ini bahwa (menggunakan kode yang tidak aman) Anda dapat keluar dari bidang jenis !Unpin , dan ini aman dan didefinisikan dengan baik selama Anda tidak pernah memproyeksikan ke pin bidang itu di tempat lain . Satu-satunya perbedaan dengan Drop adalah bagian pemindahan hanya berisi kode yang aman. Tampaknya bagi saya bahwa catatan pada Pin::map perlu diubah untuk dicatat bahwa tidak aman jika Anda keluar dari bidang itu, terlepas dari impl.

Harus aman untuk keluar dari field tipe !Unpin dalam beberapa kasus, karena generator kemungkinan besar akan keluar dari salah satu field ketika mereka kembali.

Saya pikir lebih baik untuk hanya mendokumentasikan bahwa jika Anda keluar dari lapangan di Drop impl, memproyeksikan ke Pin bidang itu di tempat lain tidak sehat.

Ini adalah opsi kedua, yang membuat &pin ke bidang secara permanen merupakan operasi yang tidak aman.
Menurut saya ini bukan perubahan kecil. Ini secara mendasar mengubah arti pub pada bidang. Saat menggunakan tipe perpustakaan, saya tidak tahu apa yang dilakukannya dalam impl drop-nya, jadi saya pada dasarnya tidak memiliki cara untuk mendapatkan referensi yang disematkan ke bidang itu.

Misalnya, saya bahkan tidak akan diizinkan untuk beralih dari Pin<Option<T>> menjadi Option<Pin<T>> kecuali Option secara eksplisit menyatakan bahwa ia tidak akan pernah memiliki Drop melakukan apa pun " lucu". Kompilator tidak dapat memahami pernyataan itu, jadi sementara Option dapat menyediakan metode yang sesuai untuk melakukan ini, melakukan hal yang sama dengan match harus tetap merupakan operasi yang tidak aman.

Satu-satunya perbedaan dengan Drop adalah bahwa bagian pemindahan hanya berisi kode aman.

Tapi itu perbedaan yang sangat besar , bukan? Kita dapat menempatkan aturan sewenang-wenang tentang kode tidak aman yang mungkin atau tidak dapat dilakukan, tetapi tidak untuk kode aman.

Harus aman untuk keluar dari bidang jenis! Lepas pin dalam beberapa kasus, karena generator kemungkinan besar akan keluar dari salah satu bidangnya saat mereka kembali.

Saya kira dalam kasus ini field akan menjadi Unpin ? Jadi kita mungkin bisa memiliki aturan yang mengatakan bahwa Pin::mut untuk bidang publik dari struct asing baik-baik saja jika bidang itu memiliki tipe Unpin . Tidak yakin seberapa berguna ini, tetapi mungkin lebih baik daripada tidak sama sekali.

Saya ingin segera menyatakan kembali kebingungan saya tentang &Pin<T> tidak memberikan jaminan lebih dari &&T . & , &mut , dan &pin masing-masing memberikan "akses bersama", "akses unik", dan "akses unik ke nilai yang tidak akan dipindahkan". Memahami &&pin sebagai "akses bersama ke akses unik ke jenis yang tidak akan dipindahkan" memberi tahu Anda bahwa memori dibagi (jaminan keunikan &pin dibatalkan dengan berbagi & ), tetapi Anda masih mempertahankan properti yang tipenya tidak akan dipindahkan, bukan?

Saya tidak yakin apa yang Anda tanyakan atau katakan. Apakah Anda bingung mengapa menurut saya "shared pinned" adalah mode fundamental / typestate tersendiri?

Intinya adalah, "akses bersama" bukanlah hal yang saya tahu bagaimana mendefinisikannya sendiri. Ada banyak cara berbeda untuk berbagi dan mengkoordinasikan berbagi, seperti yang terlihat dari cara yang sangat berbeda di mana, misalnya, Cell , RefCell , dan Mutex berbagi.

Anda tidak bisa begitu saja mengatakan bahwa Anda membagikan sesuatu ("membatalkan jaminan keunikan" dari sesuatu) yang Anda miliki dan mengharapkan pernyataan itu masuk akal. Anda harus mengatakan bagaimana Anda berbagi, dan bagaimana Anda memastikan bahwa ini tidak dapat menyebabkan malapetaka. Anda dapat "berbagi dengan menjadikannya hanya-baca", atau "berbagi hanya dengan memberikan akses atomik melalui pemuatan / penyimpanan yang disinkronkan", atau "berbagi [hanya dalam satu utas] dengan memiliki tanda pinjam ini mengoordinasikan jenis akses yang akan dibagikan" . Salah satu poin kunci di RustBelt adalah menyadari pentingnya membiarkan setiap jenis mendefinisikan sendiri apa yang terjadi ketika itu dibagikan.

Saya tidak dapat memikirkan cara untuk membuat "penyematan bersama" muncul sebagai komposisi ortogonal dari berbagi dan menyematkan. Mungkin ada cara untuk mendefinisikan gagasan tentang "mekanisme berbagi" yang kemudian dapat diterapkan pada invarian yang dimiliki atau disematkan untuk memunculkan "(normal) bersama" dan "disematkan bersama", tetapi saya sangat meragukannya. Juga, seperti yang telah kita lihat bahwa nilainya tetap untuk RefCell - jika RefCell tidak untuk invarian yang disematkan bersama sesuatu yang mirip dengan apa yang dilakukannya untuk invarian yang baru saja dibagikan, kami tentu tidak dapat membenarkannya dari & &pin RefCell<T> melalui &RefCell<T> (menggunakan Deref ) melalui borrow_mut kita dapat memperoleh referensi &mut yang mengatakan bahwa tidak terjadi penyematan.

@Ralfian

Kita bisa menambahkan Unpin terikat ke Drop, dan menambahkan DropPinned yang tidak memiliki batas dan di mana drop mengambil Pin.

Apakah definisi Drop benar-benar menjadi masalah di sini? Cara lain untuk memikirkannya adalah menyalahkan mem::swap dan mem::replace . Ini adalah operasi yang memungkinkan Anda memindahkan sesuatu yang tidak Anda miliki. Misalkan T: Unpin terikat ditambahkan ke _them_?

Sebagai permulaan, itu akan memperbaiki lubang drop yang saya tunjukkan - Shenanigans akan gagal untuk dikompilasi, dan saya rasa saya tidak dapat membuatnya melanggar janji pin tanpa unsafe . Tapi itu akan memungkinkan lebih dari itu! Jika sudah aman untuk mendapatkan referensi &mut ke nilai yang disematkan sebelumnya di drop , mengapa membatasinya menjadi hanya drop ?

Kecuali saya melewatkan sesuatu, saya pikir ini akan membuatnya aman untuk meminjam referensi &mut dari PinMut<T> kapan saja Anda mau. (Saya menggunakan PinMut untuk merujuk pada apa, di malam hari saat ini, disebut Pin , untuk menghindari kebingungan dengan diskusi tentang pin bersama.) PinMut<T> dapat mengimplementasikan DerefMut tanpa syarat, bukan hanya untuk T: Unpin .

Sayangnya, kami tampaknya tidak memiliki cara yang mudah untuk menyediakan operasi serupa yang dapat bekerja pada & mut dan PinMut; setelah semua banyak kode yang bekerja & mut tidak bermaksud menggunakan mem::swap .

A DerefMut impl pada PinMut akan memperbaikinya, bukan? Kode yang peduli tentang penyematan, dan membutuhkan PinMut , dapat memanggil kode yang berfungsi pada &mut dengan aman dan mudah. Beban akan ditempatkan sebagai gantinya pada fungsi generik yang _do_ ingin menggunakan mem::swap --yang harus memiliki Unpin terikat ditambahkan padanya, atau gunakan unsafe dan hati-hati jangan sampai melanggar kondisi pin.

Menambahkan batas tersebut ke swap dan replace sekarang akan merusak kompatibilitas mundur ke rilis stabil pertama. Saya tidak melihat cara yang realistis untuk sampai ke sana dari sini. Tetapi apakah saya melewatkan beberapa lubang lain dalam berpikir bahwa itu akan menjadi hal yang harus dilakukan, jika saja ini diketahui dalam hari-hari sebelum 1,0?

Karena itu, saya tidak melihat solusi yang lebih baik dari apa yang dikatakan @withoutboats - simpan map tidak aman, dan letakkan pesan di dokumennya untuk memperingatkan orang-orang agar tidak keluar dari bidang apa pun yang sebelumnya disematkan di drop impl.

Kita dapat menempatkan aturan sewenang-wenang tentang kode tidak aman yang mungkin atau tidak dapat dilakukan, tetapi tidak untuk kode aman.

Menggunakan unsafe selalu memberlakukan aturan pada kode aman sekitarnya. Kabar baiknya di sini adalah sejauh yang kami tahu, jika struct pinnable memiliki metode untuk memproyeksikan pin ke bidang pribadi, hanya drop impl miliknya sendiri yang dapat menggunakannya untuk melanggar kontrak dalam kode aman. Jadi, masih mungkin untuk menambahkan proyeksi seperti itu dan menampilkan _users_ dari struct itu dengan API yang sepenuhnya aman.

Apakah definisi Drop benar-benar menjadi masalah di sini?

Menetapkan kesalahan agak sewenang-wenang, ada beberapa hal berbeda yang dapat diubah untuk menyambungkan lubang kesehatan. Tetapi apakah kita sepakat bahwa mengubah Drop seperti yang saya sarankan akan memperbaiki masalah?

Cara lain untuk memikirkannya adalah menyalahkan mem :: swap dan mem :: replace. Ini adalah operasi yang memungkinkan Anda memindahkan sesuatu yang tidak Anda miliki. Misalkan T: Unpin terikat ditambahkan ke mereka?

Nah, itu akan membuat &mut T umumnya aman digunakan untuk tipe !Unpin . Seperti yang Anda amati, kami bahkan tidak membutuhkan PinMut lagi. PinMut<'a, T> dalam proposal Anda dapat ditentukan sebagai &'a mut T , bukan?
Ini pada dasarnya adalah proposal ?Move yang telah dibuang sebelumnya karena kompatibilitas mundur dan masalah kompleksitas bahasa.

Penggunaan unsafe selalu memberlakukan aturan pada kode aman sekitarnya.

Saya tidak yakin apa yang Anda maksud. Di luar batas privasi, seharusnya tidak demikian; kode tidak aman tidak dapat memaksakan apapun pada kliennya.

Kabar baiknya di sini adalah sejauh yang kami ketahui, jika struct pinnable memiliki metode untuk memproyeksikan pin ke bidang privat, hanya implnya sendiri yang dapat menggunakannya untuk melanggar kontrak dalam kode aman. Jadi, masih mungkin untuk menambahkan proyeksi seperti itu dan menghadirkan pengguna dari struct itu dengan API yang sepenuhnya aman.

Ya, tipe dapat memilih untuk menyatakan proyeksi aman. Tetapi misalnya pemeriksa peminjam tidak akan memahami bahwa ini adalah akses bidang, jadi jika diberi PinMut<Struct> Anda tidak dapat menggunakan metode tersebut untuk mendapatkan PinMut ke dua bidang berbeda pada waktu yang bersamaan.

Tetapi apakah kita memiliki kesepakatan bahwa mengubah Drop seperti yang saya sarankan akan memperbaiki masalah?

Saya setuju, itu akan memperbaikinya.

Kami bahkan tidak membutuhkan PinMut lagi. PinMut <'a, T> dalam proposal Anda dapat didefinisikan sebagai &' a mut T, bukan?

Tidak, PinMut<'a, T> masih diperlukan untuk menjanjikan bahwa referen tidak akan pernah pindah lagi. Dengan &'a mut T Anda hanya bisa mempercayainya untuk tidak bergerak seumur hidup 'a . Ini masih akan diizinkan, seperti sekarang ini:

`` `` karat
struct X;
impl! Lepas pin untuk X {}
fn take_a_mut_ref (_: & mut X) {}

fn pinjam_dan_move_and_borrow_again () {
biarkan mut x = X;
take_a_mut_ref (& mut x);
biarkan mut b = Kotak :: baru (x);
take_a_mut_ref (& mut * b);
}
`` ''

Akan aman untuk beralih dari PinMut<'a, T> menjadi &'a mut T tetapi tidak sebaliknya - PinMut::new_unchecked akan tetap ada, dan masih menjadi unsafe .

Ini pada dasarnya adalah proposal ?Move yang telah dibuang sebelumnya karena kompatibilitas mundur dan masalah kompleksitas bahasa.

Seperti yang saya pahami, proposal ?Move mencoba untuk tidak membutuhkan PinMut , dengan mengubah aturan bahasa dasar untuk melarang potongan kode di atas (dengan membuat Unpin be a Move .) Saya tidak mengusulkan hal seperti itu - proposal saya adalah mulai persis seperti di malam hari sekarang, ditambah:

  • Tambahkan Unpin terikat ke fungsi std::mem::swap dan std::mem::replace
  • Hapus Unpin terikat dari DerefMut impl dari Pin ( PinMut jika penggantian nama itu terjadi)

Itu saja - tidak ada bahasa mendasar yang berubah tentang cara kerja gerakan, atau semacamnya. Klaim saya adalah: ya, ini akan menjadi perubahan yang merusak, tetapi itu akan memiliki dampak kerusakan yang lebih kecil daripada mengubah Drop (yang pada gilirannya masih kurang drastis daripada proposal ?Move ), sambil mempertahankan banyak manfaatnya. Secara khusus, ini akan memungkinkan proyeksi pin yang aman (setidaknya untuk bidang pribadi, dan saya pikir bahkan untuk publik? Tidak begitu yakin) dan mencegah situasi di mana kode paralel harus ditulis untuk bekerja dengan PinMut dan &mut .

Tidak, PinMut <'a, T> tetap diperlukan untuk menjanjikan bahwa referen tidak akan pernah berpindah lagi. Dengan & 'a mut T Anda hanya bisa mempercayainya untuk tidak bergerak seumur hidup' a.

Saya melihat. Masuk akal.

Seperti yang saya pahami, proposal? Pindahkan mencoba sepenuhnya untuk tidak membutuhkan PinMut, dengan mengubah aturan bahasa dasar untuk melarang cuplikan kode di atas (dengan membuat Lepas Pin menjadi sifat Pindah.)

Dimengerti.

Klaim saya adalah: ya, ini akan menjadi perubahan yang merusak, tetapi dampak kerusakannya lebih kecil daripada mengubah Drop

Perubahan mana yang harus dihapus yang Anda maksud? Menambahkan Unpin terikat? Anda mungkin benar, tetapi saya tidak tahu seberapa luas penggunaan mem::swap dan mem::replace .

Secara khusus, ini akan memungkinkan proyeksi pin yang aman (setidaknya untuk bidang pribadi, dan saya pikir bahkan untuk publik? Tidak begitu yakin)

Saya tidak melihat bagaimana privat vs publik bahkan dapat membuat perbedaan di sini. Bagaimana bidang publik mengizinkan kurang dari bidang pribadi?

Tapi ya, ini tampaknya bertahan secara keseluruhan. Future masih membutuhkan PinMut karena harus bergantung pada hal-hal yang tidak pernah bergerak, tetapi ia memiliki lebih banyak metode yang tersedia untuk digunakan.

Namun , aspek kompatibilitasnya sangat besar. Saya tidak berpikir ini realistis, itu akan merusak semua kode umum yang menyebut mem::swap / mem::replace . Plus, saat ini kode yang tidak aman bebas untuk menerapkan metode ini sendiri menggunakan ptr::read / ptr::write ; hal ini dapat menyebabkan kerusakan diam -

Sementara kita pada topik memperkenalkan Unpin terikat pada mem::swap dan mem::replace (dan tidak khawatir tentang kerusakan). Jika kita mengasumsikan rute "compiler built in" sudah diambil. Apakah mungkin juga untuk memperkenalkan batasan yang sama pada mem::forget untuk menjamin penghancur dijalankan untuk variabel yang disematkan tumpukan sehingga menghasilkan thread::scoped dan menghindari "buang kotoran di celana Anda" dalam kasus tertentu?

Perhatikan bahwa mem::forget pada PinBox masih diperbolehkan. Jaminan baru yang diusulkan terkait dengan penurunan TIDAK mengatakan "hal-hal tidak bocor". Ia mengatakan "hal-hal tidak dapat dibatalkan alokasinya tanpa drop dipanggil pertama". Itu pernyataan yang sangat berbeda. Jaminan ini tidak membantu thread::scoped .

Untuk menambahkan konteks, memindahkan data dari struct yang mengimplementasikan Future adalah sesuatu yang biasa saya lakukan. Sering kali muncul kebutuhan untuk melakukan pekerjaan pembersihan jika masa depan tidak pernah disurvei sampai selesai (dibatalkan sebelum polling selesai).

Jadi, saya pasti akan mencapai ranjau darat ini ketika memporting kode yang ada ke futures 0.3 bahkan dengan dokumentasi yang ditambahkan ke map .

@carllerche fungsi map mengatakan dengan jelas bahwa Anda tidak boleh menggunakan ini untuk memindahkan apa pun. Kami tidak dapat dan tidak ingin melindungi dari orang-orang yang dengan sengaja keluar dari Pin<T> , tetapi Anda harus menyingkir (menggunakan kode yang tidak aman) untuk melakukan ini. Saya tidak akan menyebutnya ranjau darat.

Jadi, ranjau darat mana yang Anda maksud?

@RalfJung Saya telah mencoba untuk mencari tahu batasan dalam pemetaan referensi yang disematkan sendiri dan saya pikir ini akan menjadi senjata yang besar jika tidak segera diselesaikan. Saya pikir solusi pertama lebih disukai, meskipun kompleksitas; tidak dapat memproyeksikan dengan aman ke bidang yang disematkan membuat hampir tidak mungkin bagi konsumen untuk benar-benar menggunakan API yang mengandalkan pin tanpa menulis kode yang tidak aman.

Jika ini tidak dapat dilakukan, saya pikir dalam praktiknya sebagian besar API yang dapat digunakan yang menggunakan penyematan harus menggunakan PinShare. Ini mungkin bukan cacat besar, saya kira, tapi saya masih belum jelas tentang hubungan dengan Lepas Pin dalam kasus itu. Secara khusus: katakanlah saya mengambil referensi berbagi pin dan mendapatkan referensi ke bidang pada tipe (untuk masa hidup tertentu). Dapatkah saya benar-benar mengandalkannya agar tidak bergerak setelah masa hidup berakhir? Saya mungkin bisa jika field adalah !Unpin jadi mungkin tidak apa-apa, selama Pin tidak dapat memproyeksikan - saya kebanyakan khawatir tentang enum. Kecuali Anda mengatakan bahwa bahkan pemasangan pin bersama tidak dapat aman tanpa memperbaiki penurunan - dalam hal ini, menurut saya memperbaiki penurunan untuk bekerja dengan pemasangan pin pada dasarnya harus terjadi; jika tidak, ini menjadi fitur pustaka khusus yang tidak dapat digunakan dengan aman, dan tidak (IMO) layak mendapat tempat dalam bahasa inti, bahkan jika itu sangat berguna untuk Futures.

Saya juga harus menyebutkan bahwa satu-satunya API praktis yang saya miliki untuk koleksi yang mengganggu sejauh ini (saya masih perlu menyelesaikan masalah) membutuhkan jaminan yang lebih kuat dari itu; perlu untuk dapat menjamin penurunan yang tidak disebut selama ada meminjam ke dalam koleksi. Saya dapat melakukan ini dengan menggunakan teknik gaya GhostCell, tetapi rasanya sangat canggung dan mengharuskan pengguna untuk melakukan manajemen memori manual (karena kami harus membocorkan jika memori pendukung untuk sesuatu dalam koleksi dijatuhkan tanpa diberikan token). Jadi saya agak khawatir bahwa penurunan otomatis itu sendiri tampaknya sulit dilakukan dengan jenis yang menggunakan penyematan dengan cara yang menarik.

Karena penasaran: apa argumen yang melarang penambahan Unpin terikat ke Drop ? Anda mengutip kompatibilitas mundur, dengan alternatif yang Anda perlukan entah bagaimana secara otomatis mengikat hal yang dijatuhkan, tetapi Jatuhkan batasan tingkat sistem tipe yang sudah aneh yang tidak ada untuk sifat lain - mengapa yang satu ini begitu berbeda? Ini tentu tidak seanggun hanya membuat setetes mengambil Pin<T> tetapi kami tidak dapat benar-benar membuat perubahan itu pada saat ini. Apakah masalah yang sebenarnya kita tidak tahu apa yang harus dilakukan jika Anda melakukan panggilan drop pada tipe yang hanya memiliki implementasi Unpin , ketika tipe itu sendiri adalah !Unpin ? Saya kira melempar pengecualian dalam implementasi drop dalam kasus itu mungkin merupakan pendekatan yang benar, karena siapa pun yang mengandalkan drop menjalankan tipe generik sudah perlu menangani kasus panik. Itu berarti akan sangat sulit untuk menggunakan tipe !Unpin dalam praktiknya tanpa sekelompok orang memperbarui kode mereka untuk menggunakan sifat baru Drop (sehingga ekosistem akan dipaksa untuk memindahkan semuanya ke versi baru), tetapi saya pikir saya akan baik-baik saja dengan itu karena masih akan menjaga kesehatan dan tidak merusak kode yang tidak menggunakan !Unpin sama sekali. Plus, "kode Anda panik jika perpustakaan tidak meningkatkan" akan benar-benar mendorong orang untuk pindah!

Faktanya, inilah desain yang saya usulkan:

Perluas sifat Jatuhkan dengan metode kedua yang menggunakan Pin, seperti yang Anda usulkan. Buat implementasi default khusus where T: Unpin call drop (Saya berasumsi ini akan melewati aturan spesialisasi saat ini? Tetapi meskipun tidak, Drop selalu dapat berupa kasing khusus; plus, Drop fungsi biasanya dibuat secara otomatis). Sekarang Anda memiliki perilaku yang persis sama yang saya usulkan di atas, tanpa masalah kompatibilitas mundur dan tidak ada upaya untuk secara otomatis mendapatkan batasan.

Itu memang memiliki masalah bahwa perpustakaan pihak ketiga harus memutakhirkannya agar praktis berguna dengan tipe !Unpin , tetapi seperti yang saya katakan ini bisa dibilang hal yang baik. Bagaimanapun, saya tidak tahu berapa banyak implementasi drop benar-benar mengubah bidang mereka dengan cara yang membutuhkan &mut --sebagian besar yang dapat saya pikirkan untuk melakukan sesuatu yang menarik, baik menggunakan unsafe atau gunakan mutabilitas interior, tapi saya mungkin tidak memikirkan beberapa penggunaan umum.

Hal utama tentang pendekatan ini adalah jika itu akan diambil, itu harus diambil sebelum Pin distabilkan. Ini adalah satu lagi alasan saya benar-benar berharap Pin tidak terburu-buru. Saya rasa kita belum cukup mengeksplorasi konsekuensi desain.

(Saya melihat satu isu yang lebih potensial: ManuallyDrop dan serikat dalam mean umum bahwa seseorang bisa mungkin telah menulis sebuah destructor lebih jenis generik yang tidak menganggap bahwa drop implementasi di dalamnya tidak bisa panik , hanya karena ia tidak pernah dapat dijalankan (ia juga tidak akan diizinkan untuk memanggil fungsi &mut lainnya pada T generik, kecuali untuk fungsi yang diimplementasikan pada sifat tidak aman yang berjanji untuk tidak panik). Sejak Pin sebuah tipe sudah harus menjamin bahwa implementasi dropnya berjalan sebelum memorinya dihancurkan [menurut semantik baru], saya yakin tipe !Unpin dalam ManuallyDrop digunakan untuk tujuan itu dalam kode yang ada tidak dapat dianggap disematkan sejak awal, jadi kode yang ada seharusnya masih benar jika membuat asumsi itu; tentu saja, Anda seharusnya tidak dapat memproyeksikan pin dengan aman ke ManuallyDrop , karena itu hanya bisa aman jika Anda menjamin destruktornya dipanggil sebelum drop. Saya hanya tidak tahu bagaimana orang akan mengkomunikasikan kasus ini ke comp. Dapatkah ini memanfaatkan hal-hal "penutup mata" sama sekali, karena tampaknya dimaksudkan untuk tujuan yang sama?]. Saya tidak begitu yakin itu terbang secara semantik, tapi mungkin bekerja untuk tujuan implementasi Drop untuk kode yang ada.

Mengenai subjek penutup mata, meskipun ... Saya masih tidak yakin dengan definisi formal yang tepat yang dimilikinya, tetapi jika gagasan umumnya adalah bahwa tidak ada fungsi generik yang menarik yang dipanggil di T, mungkin orang dapat memanfaatkannya lebih jauh? Itu berarti bahwa jika implementasi drop_pinned untuk tipe !Unpin diimplementasikan, wadah pelindung penutup mata akan berfungsi dengan benar. Tampaknya mungkin bagi saya bahwa seseorang kemudian dapat mengkompilasi kegagalan waktu untuk jenis !Unpin yang mengimplementasikan Drop , tidak mengimplementasikan drop_pinned , dan tidak memperhatikan parameter generiknya, di seperti yang kami lakukan saat Anda menggunakan wadah non-penutup mata dengan masa pakai referensi sendiri.

Existentials menimbulkan risiko kompatibilitas mundur yang serius dengan strategi waktu kompilasi apa pun; Itulah mengapa saya pikir solusi yang gagal saat runtime lebih realistis. ini tidak akan mengalami kegagalan waktu proses dan tidak akan memiliki kesalahan positif.

Edit: Sebenarnya, menggaruk semua di atas: kita hanya benar-benar perlu khawatir tentang bidang diakses di destructors, bukan &mut akses umumnya, yang benar? Karena kami hanya khawatir tentang proyeksi bidang implisit dari &mut self .

Saya mungkin baru saja menemukan kembali proposal !DynSized , tetapi pada dasarnya: wadah umum harus sudah menjadi !Unpin jika mereka mengekspos metode apa pun yang peduli dengan jenis pin (saya menyadari ini terdengar mencurigakan seperti parametrik yang salah argumen, tapi dengarkan aku!). Eksistensial seperti Box<Trait> dan &mut Trait tidak selalu mencerminkan status Unpin mereka, tetapi (setidaknya dari melihat API saat ini?) Saya tidak berpikir Pin<Box<T>> dan Pin<&mut T> harus dipaksakan menjadi Pin<T> dimana T: !Unpin ; tidak menerapkan itu berarti referensi yang disematkan ke jenis ini tidak akan memberikan akses yang disematkan dengan aman ke interior mereka (perhatikan bahwa ada beberapa preseden untuk ini dengan hubungan & mut dan &: & mut & T tidak dapat dipaksakan ke & mut T, hanya & T, dan Box <& mut T> tidak dapat dipaksa untuk Box<T> , hanya & mut T; secara umum, ketika tipe yang berbeda bertabrakan mereka tidak harus disebarkan secara otomatis). Saya menyadari bahwa biasanya &mut T , Box<T> dan T dianggap benar-benar dapat dipertukarkan dari perspektif typestate, dan argumen ini tidak mencakup hipotesis eksistensi inline, tapi mungkin ini adalah substansi dari proposal DynSize (jangan izinkan pertukaran aman atau mem::replace s untuk nilai objek sifat, kurasa? Sebenarnya, itu sudah dilarang untuk mereka ... tapi saya berasumsi ada beberapa alasan mengapa ini mungkin berubah di masa depan). Itu membuat solusi waktu kompilasi menjadi sangat mudah: untuk struktur apa pun yang (secara transitif) dimiliki secara langsung (no & mut, &, Box , atau petunjuk mentah - tidak ada yang secara aman menyebarkan Pin access , kecuali & untuk pin bersama, tetapi & tidak dapat dipindahkan - atau jika Anda memutuskan untuk menggunakan solusi "tidak dapat memindahkan objek sifat", Anda juga dapat memeriksa bidang secara transitif sepanjang &mut dan Box menurut saya) dan memiliki akses (dalam arti visibilitas) ke jenis !Unpin diketahui (termasuk dirinya sendiri), itu harus menerapkan jenis kedua drop . Menurut saya itu baik-baik saja dan sama sekali bukan footgun kompatibilitas mundur, karena tidak ada tipe stabil yang !Unpin --orang mungkin harus menerapkan ulang destruktor setelah memperbarui perpustakaan, tapi itu saja, bukan? Selain itu, detail implementasi internal akan tetap internal; hanya jika bidang !Unpin terbuka, apakah ada masalah. Akhirnya, semua tipe container generik (tidak hanya Vec dan library standar, tapi pada dasarnya seluruh ekosistem crates.io) akan terus berfungsi dengan baik. Hal bencana apa yang saya lewatkan tentang solusi ini?

(Faktanya, menurut saya, meskipun Anda tidak dapat menerapkan ini pada waktu definisi drop , setidaknya Anda dapat melakukannya pada waktu instantiasi tipe, seperti dropck , karena Anda hanya perlu khawatir tentang jenis yang dipakai sepenuhnya).

Membaca ulang proposal Dynsized : Saya mengamati bahwa argumen yang menentangnya membutuhkan tipe yang tidak dapat dipindahkan untuk selalu menjadi DynSized bahkan sebelum mereka disematkan. Saya berpendapat di atas bahwa kita benar-benar hanya perlu mengkhawatirkan hal ini dalam kasus objek sifat; mungkin saja (meskipun sulit untuk dibenarkan) untuk memaksakan bahwa pemaksaan tipe !Unpin ke suatu sifat yang diperlukan secara eksplisit membatasinya dengan + ?DynSized (atau apapun; itu bisa dilakukan secara otomatis, saya kira). Meskipun mungkin ada banyak kasus di mana ukurannya harus diketahui untuk jenis !Unpin (memang, saya memiliki kasus penggunaan seperti itu!), Atau mereka harus dapat ditukar sebelum disematkan, saya harap ada sangat sedikit kasus seperti itu untuk interior objek sifat yang terbuat dari tipe tak bergerak (satu-satunya kasus yang kita miliki sekarang adalah untuk hal-hal seperti konversi Box -> Rc yang ingin kita larang secara eksplisit, bukan? Sebenarnya bahkan tidak jelas bagi saya bahwa size_of_val benar-benar menjadi masalah di sini atau tidak, karena saya masih tidak tahu apakah Anda diharapkan dapat mengubah Pin<'a, Box<Trait> menjadi Pin<'a, Trait> , tetapi jika Anda tidak bisa, kami hanya dapat mengandalkan Sized tentunya). Seseorang benar-benar ingin dapat mengikat mereka dengan !Unpin dalam hal apa pun tetapi seperti yang saya katakan, saya pikir orang ingin menghindari menambahkan lebih banyak sifat negatif daripada yang sudah kita butuhkan (secara pribadi, saya berharap !Unpin objek sifat akan menjadi cukup langka dan terspesialisasi sehingga objek sifat pembatas dengan ?Unpin daripada Unpin akan benar-benar masuk akal dan tidak menginfeksi sistem tipe terlalu banyak; sebagian besar penggunaan untuk !Unpin Saya pikir tidak akan memiliki implementasi yang berguna untuk kebanyakan ciri yang ada, karena mereka umumnya ingin melakukan tindakan pada Pin<self> . Anda juga biasanya ingin menggunakannya dengan PinBox atau PinTypedArena atau apa pun yang membuat batas ?Unpin terlihat cukup alami).

Saya memiliki desain baru, yang menurut saya tidak seburuk yang lama: https://github.com/pythonesque/pintrusive/blob/master/src/main.rs. Alih-alih mencoba membuat proyeksi pin berfungsi di mana saja, desain ini menanyakan bagaimana kita dapat beroperasi dengan kode Rust "warisan" yang tidak tahu apa-apa tentang pemasangan pin, dari kode Rust "modern" yang selalu ingin mendukung pemasangan pin. Jawaban yang jelas tampaknya menggunakan PinDrop trait @RalfJung yang diusulkan, tetapi hanya untuk tipe yang keduanya memiliki custom drop, dan ingin memproyeksikan field.

Jenis secara eksplisit memilih untuk memproyeksikan bidang (sesuai dengan mendapatkan sifat PinFields ), yang sejalan dengan kode yang ditulis dalam Rust "modern"; namun, itu tidak membuat persyaratan tambahan pada kode di "legacy" Rust, alih-alih memilih untuk hanya mendukung proyeksi ke kedalaman 1 untuk setiap penurunan PinFields . Itu juga tidak mencoba untuk memindahkan Pin melalui referensi, yang saya percaya mungkin ide yang baik untuk tidak melakukannya. Ini mendukung struktur dan enum, termasuk analisis ketidaksesuaian apa pun yang seharusnya dapat disediakan Rust, dengan menghasilkan struktur dengan bidang dan varian yang identik, tetapi dengan tipe yang diganti dengan Pin d referensi ke tipe (itu harus sepele untuk memperpanjang ini menjadi Pin dan PinMut ketika perubahan itu dilakukan). Jelas ini tidak ideal (meskipun mudah-mudahan pengoptimal dapat menghilangkan sebagian besar dari itu) tetapi memiliki keuntungan bahwa ia bekerja dengan pinjamanck dan NLL dan mudah bekerja dengan enum (sebagai lawan dari pengakses yang dihasilkan).

Argumen keselamatan bekerja dengan memastikan bahwa Drop tidak diimplementasikan sama sekali untuk struktur, atau memastikan bahwa jika Drop diimplementasikan untuk itu, itu adalah implementasi sepele yang hanya memanggil PinDrop (versi Drop yang menggunakan Pin). Saya percaya ini mengesampingkan semua masalah kesehatan dengan memproyeksikan bidang yang disematkan, dengan satu tanda tanya: perhatian utama saya adalah menemukan argumen yang baik mengapa bidang terputus pada wadah yang sama persis (yaitu, bidang pada kedalaman 1) yang dapat membatalkan satu sama lain. pin di destruktornya, sudah tidak valid tanpa proyeksi pin. Saya rasa saya dapat membenarkan hal ini jika kami dapat menunjukkan bahwa Anda juga dapat melakukan hal yang sama jika diadakan di PinBox yang terpisah, yang menyiratkan bahwa tempat tinggal mereka adalah bagian dari kontrak keselamatan mereka; itu berarti destruktornya tidak aman dalam isolasi dan membuatnya di luar modul akan membutuhkan kode yang tidak aman. Dengan kata lain, kebenarannya bergantung pada penerapan jenis penampung, yang berarti bahwa tidak masalah untuk meminta lebih banyak destruktornya daripada yang kami lakukan untuk kode aman yang sewenang-wenang.

@pythonesque Saya tidak benar-benar mengikuti apa yang Anda tulis di atas tentang DynSize , tapi menurut saya itu semua sudah ketinggalan zaman sekarang? Jadi, saya hanya akan mengomentari postingan terbaru Anda.

Untuk meringkas, Anda mengatakan bahwa memproyeksikan ke bidang dari struct / enum (termasuk bidang pub ) tidak aman secara umum , tetapi suatu tipe dapat ikut serta ke proyeksi bidang aman dengan tidak menerapkan Drop . Jika tipe menginginkan destruktor, itu harus PinDrop alih-alih Drop :

trait PinDrop {
  fn pin_drop(self: PinMut<Self>);
}

Kita sudah memiliki cek ketik mencari Drop untuk menolak pemindahan dari bidang, jadi tampaknya tidak realistis juga memeriksa Drop untuk menolak memproyeksikan melalui &pin . Tentu saja, pemeriksaan "keluar dari bidang" akan tetap menolak jika terdapat PinDrop , sedangkan proyeksi akan diizinkan dalam kasus tersebut.

Kompilator akan menerapkan batasan yang sama ke PinDrop yang diterapkan pada Drop , ditambah lagi akan memastikan bahwa sebuah tipe tidak mengimplementasikan Drop dan PinDrop . Saat membuat lem jatuh, ia memanggil jenis Drop apa pun yang telah diterapkan oleh jenis tersebut.

Apakah itu meringkas proposal? Bagian yang saya tidak mengerti adalah paragraf terakhir Anda, dapatkah Anda memberikan contoh yang menunjukkan apa yang Anda khawatirkan?


Sekarang, tentu saya bertanya-tanya apa bukti kewajibannya di sini. Saya pikir cara termudah untuk melihat ini adalah dengan mengatakan bahwa PinDrop sebenarnya adalah penghancur utama dan satu-satunya yang secara resmi ada, dan impl Drop for T sebenarnya gula sintaksis untuk impl PinDrop yang mana memanggil metode tidak aman PinMut::get_mut dan kemudian memanggil Drop::drop . Ini adalah auto-kode yang dihasilkan tidak aman, namun, karena menjepit adalah "ekstensi lokal" (yaitu, itu adalah kompatibel dengan yang ada kode yang tidak aman), bahwa kode yang tidak aman selalu aman jika jenis tertentu tidak peduli menjepit.

Secara sedikit lebih formal, ada "default pinning invariant" yang dimiliki tipe jika pembuatnya tidak peduli dengan pin. Jenis yang menggunakan invarian penyematan default tersebut secara otomatis menjadi Unpin . Menulis impl Drop untuk tipe yang memiliki invarian khusus menegaskan bahwa tipe tersebut menggunakan "default pinning invariant". Ini agak mencurigakan karena rasanya agak ada kewajiban pembuktian di sini, tetapi tidak ada unsafe untuk memperingatkan tentang ini - dan memang ini tidak sempurna, tetapi kompatibilitas ke belakang penting jadi apa yang bisa Anda lakukan? melakukan. Ini juga bukan bencana, karena orang dapat memperdebatkan apa artinya ini adalah bahwa hal itu mengubah kewajiban pembuktian yang timbul pada kode tidak aman di tempat lain, untuk memperluas bahwa kode tidak aman ini harus bekerja dengan "default pinning invariant". Jika tidak ada kode yang tidak aman di tempat lain dalam modul ini, semuanya baik-baik saja.

Saya bahkan dapat membayangkan bahwa kita dapat menambahkan lint terhadap impl Drop for T kecuali T: Unpin , mungkin terbatas pada kasus di mana modul yang mendefinisikan tipe memiliki kode yang tidak aman di dalamnya. Itu akan menjadi tempat untuk mendidik orang tentang masalah dan mendorong mereka untuk unsafe impl Unpin (secara resmi menyatakan bahwa mereka menggunakan invarian pin default), atau impl PinDrop .

@Ralfian

Apakah itu meringkas proposal?

Ya, lebih atau kurang (saya pikir proposal Anda sebenarnya jauh lebih ambisius daripada proposal saya karena tampaknya mengusulkan secara otomatis melakukan proyeksi jika Drop tidak diterapkan, yang menurut saya mungkin merupakan masalah kompatibilitas ke depan untuk perpustakaan; tapi mungkin ada jalan lain bahwa).

Bagian yang saya tidak mengerti adalah paragraf terakhir Anda, dapatkah Anda memberikan contoh yang menunjukkan apa yang Anda khawatirkan?

Secara garis besar: Saya khawatir tentang dua bidang yang hidup langsung pada struktur yang sama, salah satunya bermutasi ketika Jatuhkan dipanggil (mungkin mengandalkan hal-hal seperti urutan jatuh untuk keamanan) dengan cara yang melanggar invarian penyematan dari bidang lain, tetapi mempertahankan integritas strukturalnya (sehingga memungkinkan Anda menyaksikan pelanggaran invarian pemasangan pin). Ini jelas tidak dapat menggunakan kode yang sepenuhnya aman (atau akan ditolak oleh dropck, antara lain), jadi perhatian saya hanya tentang beberapa kasus hipotetis di mana penghancur pada jenis bidang aman untuk dijalankan ketika bidang disematkan secara terpisah struktur, tetapi tidak aman jika disematkan di struktur yang sama. Harapan saya adalah bahwa tidak ada kasus seperti itu kecuali ada invarian bersama yang mencakup struktur yang terkandung di dalamnya; jika ada invarian seperti itu, kita tahu bahwa ia harus menyadari bahwa komponennya tidak mematuhi invarian khusus dengan benar dan oleh karena itu kita dapat menyalahkan kode di suatu tempat dalam modul.

Saya pikir cara termudah untuk melihat ini adalah dengan mengatakan bahwa PinDrop sebenarnya adalah jenis destruktor utama dan satu-satunya yang secara resmi ada, dan impl Drop for T sebenarnya adalah gula sintaksis untuk impl PinDrop yang memanggil metode tidak aman PinMut :: get_mut dan kemudian memanggil Jatuhkan :: jatuhkan.

Sepakat. Sayangnya jalan ini tidak terbuka untuk solusi saat ini karena mencoba melakukan berbagai hal di perpustakaan.

Secara sedikit lebih formal, ada "default pinning invariant" yang dimiliki tipe jika pembuatnya tidak peduli dengan pin. Jenis yang menggunakan invarian penyematan default tersebut secara otomatis Lepas pin. Menulis impl Drop untuk tipe yang memiliki invarian khusus menegaskan bahwa tipe tersebut menggunakan "default pinning invariant". Ini agak mencurigakan karena rasanya ada kewajiban pembuktian di sini, tetapi tidak ada yang tidak aman untuk diperingatkan tentang hal ini - dan memang ini tidak sempurna, tetapi kompatibilitas ke belakang penting jadi apa yang dapat Anda lakukan.

Ya, ini kira-kira yang ada dalam pikiran saya ... Saya hanya khawatir bahwa saya tidak memiliki intuisi yang baik tentang apa artinya memformalkan jaminan "kode tidak aman dalam modul" ini. Saya suka ide Anda tentang lint (sebenarnya saya suka apa pun yang membuat lebih banyak orang menggunakan PinDrop!) Tapi menurut saya unsafe impl Unpin mungkin akan terlalu sering salah sehingga menjadi hal yang baik untuk disarankan, di Setidaknya untuk tipe dengan kolom publik generik (tapi sekali lagi, untuk struktur yang tidak memiliki kolom seperti itu, ini sebenarnya sering kali benar ... sebagian besar berlaku untuk library standar, misalnya).

Saya menulis contoh bagaimana #[derive(PinnedFields)] dapat bekerja: https://github.com/withoutboats/derive_pinned

Saya pernah melihat orang-orang menegaskan bahwa turunan seperti ini "tidak terdengar" tetapi afaik itu tidak benar. Anda perlu menggunakan kode yang tidak aman untuk melakukan sesuatu yang akan bertentangan dengan kode yang dihasilkan oleh turunan ini - yaitu, ini membuat kode tidak aman lainnya menjadi tidak benar (dan menurut saya kode itu - memindahkan sekitar ?Unpin data - adalah sesuatu yang Anda dapat / harus selalu menghindari).

EDIT: Oke, sebenarnya baca beberapa posting terakhir tentang destruktor. Akan memproses.

@withoutboats Ya, saya pikir Anda telah melihat ini, tetapi masalahnya adalah bahwa kode yang tidak aman dalam tipe !Unpin dapat dibatalkan oleh destruktor yang aman pada tipe yang menurunkan PinFields (jadi tidak ada kode yang tidak aman dalam modul untuk tipe yang menurunkan PinFields kecuali untuk barang yang dibuat secara otomatis). Itulah bagian yang bermasalah. Lihatlah desain yang saya tautkan (mengabaikan pilihan gaya untuk membuat struktur terpisah daripada mendapatkan aksesor individu - itu hanya saya mencoba membuatnya mendukung sebanyak mungkin kasus penggunaan dengan pemeriksa peminjam). Saya sempat khawatir, tetapi sekarang saya cukup yakin #[derive(PinFields)] masih dapat berfungsi, hanya perlu kehati-hatian untuk memastikan bahwa Drop tidak diterapkan secara langsung.

Saya juga ingin mengemukakan poin lain yang telah saya pikirkan (yang belum saya lihat secara langsung diselesaikan di mana pun?): Saya pikir sesuatu yang akan membuat Pin jauh lebih bermanfaat dan terintegrasi lebih baik ke dalam kode yang ada, akan dengan tegas turun di sisi dasarnya semua jenis pointer menjadi Unpin . Yaitu, menghasilkan &mut T , &T , *const T , *mut T , Box<T> , dan seterusnya semua dianggap Unpin untuk setiap T . Meskipun mungkin tampak mencurigakan untuk membiarkan sesuatu seperti, katakanlah, Box<T> menjadi Unpin , masuk akal jika Anda menganggap bahwa Anda tidak bisa mendapatkan Pin ke bagian dalam Box dari Pin<Box<T>> . Saya pikir ini hanya memungkinkan !Unpin untuk menginfeksi hal-hal yang "sebaris" adalah pendekatan yang sangat masuk akal - Saya tidak memiliki kasus penggunaan tunggal untuk memungkinkan penyematan menjadi viral di seluruh referensi, dan itu membuat semantik setiap akhirnya &pin jenis sangat menyenangkan (Saya membuat tabel tentang bagaimana itu akan berinteraksi dengan jenis penunjuk lain dalam skenario itu, dan pada dasarnya jika Anda mengabaikan gerakan itu membuat &mut pin bertindak sama dengan &mut , &pin bertindak sama dengan & , dan box pin bertindak sama dengan box sehubungan dengan hubungan dengan pointer lain). Ini juga tidak penting secara operasional, sejauh yang saya tahu: secara umum memindahkan nilai tipe A berisi pointer ke nilai tipe B tidak memindahkan nilai menunjuk ke dari ketik B , kecuali nilai tipe B sejajar dengan nilai tipe A --tetapi jika demikian, maka A otomatis menjadi !Unpin karena berisi B tanpa petunjuk arah. Mungkin yang paling penting, ini berarti bahwa sebagian besar jenis yang saat ini memerlukan implementasi manual yang tidak aman dari !Unpin tidak memerlukannya, karena sebagian besar koleksi hanya menyimpan T belakang tipuan penunjuk . Itu akan memungkinkan Drop untuk terus bekerja, dan mengizinkan tipe ini untuk mengimplementasikan PinDrop tanpa mengubah semantiknya (karena jika sebuah tipe adalah Unpin itu dapat memperlakukan Pin argumen sebagai &mut pokoknya).

Saya mungkin melewatkan beberapa alasan mengapa penyematan transitif semacam ini adalah ide yang bagus, tetapi sejauh ini saya belum menemukan apa pun. Satu-satunya hal operasional yang mungkin saya khawatirkan adalah apakah benar-benar mungkin untuk menerapkan perilaku ini dengan ciri-ciri otomatis - saya pikir itu mungkin akan terjadi, tetapi mungkin dalam beberapa kasus di mana orang menggunakan PhantomData<T> tetapi sebenarnya memiliki milik pointer ke T , alangkah baiknya bagi mereka untuk mengubahnya menjadi PhantomData<Box<T>> . Biasanya kami menganggapnya sebagai semantik yang persis sama, tetapi dengan penyematan itu tidak sepenuhnya benar.

@pythonesque Terminologi "bahasa baru" dan semacamnya adalah semacam perpeloncoan bagi saya. Kesan saya tentang apa yang Anda lakukan adalah:

  • Secara default, #[derive(PinFields)] menghasilkan no-op Drop impl. Ini menjamin bahwa Anda tidak pernah mengakses bidang yang disematkan di destruktor.
  • Atribut opsional mengubah Drop impl ini menjadi PinDrop::pin_drop , dan Anda seharusnya mengimplementasikan PinDrop .

Apakah ini benar?


Saya juga percaya bahwa semua ini hanya penting jika kami memberikan jaminan Pin untuk mendukung koleksi yang mengganggu. Apakah ini sesuai dengan pemahaman Anda pada @RalfJung dan @pythonesque?


Semua ini agak membuat frustasi, karena yang tampak jelas adalah bahwa Drop seharusnya mengambil sendiri Pin ! Membuat perubahan yang lebih radikal (mungkin epochal) tampaknya menarik, tetapi saya tidak melihat cara yang tidak terlalu mengganggu.

Berikut adalah contoh jenis ketidakseimbangan akses ke bidang yang disematkan + drop yang dapat menyebabkan: https://play.rust-lang.org/?gist=8e17d664a5285e941fe1565ce0eca1ea&version=nightly&mode=debug

Tipe Foo mengirimkan buffer internal ke semacam API eksternal yang mengharuskan buffer tetap di tempatnya hingga tautannya secara eksplisit diputus. Sejauh yang saya tahu ini terdengar di bawah batasan yang diusulkan @cramertj , setelah Pin<Foo> dibuat, Anda dijamin tidak akan dipindahkan hingga setelah Drop dipanggil (dengan syarat bahwa itu bisa dibocorkan dan Drop tidak pernah dipanggil, tetapi dalam hal ini Anda dijamin tidak akan pernah dipindahkan).

Ketik Bar kemudian pecahkan ini dengan memindahkan Foo selama implementasi Drop .

Saya menggunakan struktur yang sangat mirip dengan Foo untuk mendukung perangkat radio yang berkomunikasi melalui DMA, saya dapat memiliki StableStream dengan buffer internal yang ditulis oleh radio.

@tokopedia

Apakah ini benar?

Ya, kecuali bahwa itu tidak benar-benar menghasilkan no-op Drop impl (karena jenis yang tidak mengimplementasikan Drop di Rust bekerja lebih baik secara umum). Sebaliknya ia mencoba untuk menyatakan bahwa Drop belum diimplementasikan menggunakan beberapa fitur pustaka yang mencurigakan (ini berfungsi pada stabil tetapi rusak di bawah spesialisasi - saya pikir ada varian yang harus bekerja di bawah spesialisasi tetapi tidak sekarang karena masalah dengan konstanta terkait). Jika itu dibuat sebagai fitur bahasa, akan sangat mudah untuk menerapkannya.

Saya juga percaya bahwa semua ini hanya penting jika kami memberikan jaminan Pin untuk mendukung koleksi yang mengganggu. Apakah ini sesuai dengan pemahaman Anda @ralfj dan @pythonesque?

Tidak, sayangnya bukan itu masalahnya. Counterexample yang ditautkan di atas tidak ada hubungannya dengan jaminan ekstra untuk koleksi yang mengganggu. Jenis Pin d sudah harus dapat mengasumsikan bahwa itu tidak akan bergerak sebelum digunakan lagi bahkan tanpa jaminan itu, karena jika sebuah metode dipanggil dua kali pada nilai dari belakang referensi yang disematkan nilainya tidak ada cara untuk mengetahui apakah itu berpindah di antara dua panggilan. Jaminan tambahan yang diperlukan untuk membuatnya berguna untuk koleksi mengganggu menambahkan hal tambahan tentang harus memanggil drop sebelum memori dibebaskan, tetapi bahkan tanpa jaminan bahwa drop masih bisa disebut pada sesuatu yang saat ini disematkan (di belakang PinBox, misalnya). Jika hal yang dijatuhkan kebetulan menyertakan bidang sebaris, dan kami mengizinkan memproyeksikan pin dari benda yang dijatuhkan ke bidang tersebut, maka penghancur tipe luar masih dapat memindahkan bidang dalam, sematkan lagi (dengan meletakkan dipindahkan-keluar nilai bidang dalam PinBox , misalnya), dan kemudian panggil metode di atasnya yang mengharapkan referensi dari saat disematkan sebelumnya agar tetap valid. Saya tidak benar-benar melihat jalan lain; selama Anda bisa menerapkan drop , Anda bisa mendapatkan masalah ini untuk bidang !Unpin sebaris. Itulah mengapa saya pikir solusi paling buruk adalah tidak membiarkan orang menerapkan drop jika mereka ingin memasang pin agar berfungsi dengan benar.

Semua ini agak membuat frustrasi, karena yang tampak jelas adalah bahwa Drop seharusnya mengambil diri sendiri oleh Pin! Membuat perubahan yang lebih radikal (mungkin epochal) tampaknya menarik, tetapi saya tidak melihat cara yang tidak terlalu mengganggu.

Ya, itu benar-benar menjengkelkan ... Aku sangat merajuk selama seminggu. Tapi seperti yang ditunjukkan Ralf, kita harus menunggu tiga tahun lagi untuk 1,0 sebelum ada yang tahu ... dan akan selalu ada yang lebih.

Jika hal yang dijatuhkan kebetulan menyertakan bidang sebaris, dan kami mengizinkan memproyeksikan pin dari benda yang dijatuhkan ke bidang tersebut, maka destruktor tipe luar masih dapat memindahkan bidang dalam dan kemudian memanggil metode di atasnya yang mengharapkan referensi dari saat itu disematkan agar tetap valid.

Porsi yang ditekankan tampak seperti masalah yang sangat besar bagi saya; sebenarnya, tampaknya itu adalah inti dari masalah ini.

Awalnya saya membayangkan kode ini, yang menggunakan satu-satunya metode yang kami miliki yang benar-benar peduli dengan alamat internal:

struct TwoFutures<F>(F, F);

impl Drop for TwoFutures {
     fn drop(&mut self) {
          mem::swap(&mut self.0, &mut self.1);
          unsafe { Pin::new_unchecked(&mut self.0).poll() }
     }
}

Tetapi ini melibatkan kode yang tidak aman untuk kembali ke Pin ! Kode itu seharusnya dianggap tidak masuk akal.

Bisakah kita mengesampingkan kemampuan untuk metode yang membutuhkan &self dan &mut self bergantung pada validitas alamat internal untuk kesehatan? Itu terlihat seperti apa?

@withoutboats Meskipun hanya metode yang mengambil Pin<Self> mengandalkan validitas alamat internal untuk kesehatan, Anda masih dapat mengalami masalah. Penghancur tipe luar dapat mem::replace bidang tipe bagian dalam (menggunakan &mut self referensinya sendiri), lalu PinBox::new nilainya, dan kemudian memanggil metode yang disematkan di atasnya . Tidak perlu tidak aman. Satu-satunya alasan itu bukan masalah tanpa dapat memproyeksikan bidang yang disematkan adalah karena itu dianggap dapat diterima untuk jenis yang benar-benar menerapkan metode aneh !Unpin menggunakan unsafe (atau berbagi invarian dengan jenis yang tidak ) untuk memiliki kewajiban pembuktian atas penerapan drop meskipun hanya menggunakan kode aman di sana. Tetapi Anda tidak dapat menanyakan jenis yang kebetulan berisi jenis yang !Unpin dan tidak memiliki kode yang tidak aman.

@pencinta

Saya pikir proposal Anda sebenarnya jauh lebih ambisius daripada proposal saya karena proposal tersebut tampaknya secara otomatis melakukan proyeksi jika Drop tidak diterapkan, yang menurut saya mungkin merupakan masalah kompatibilitas ke depan untuk perpustakaan; tapi mungkin ada jalan lain

Saya pikir kami ingin melakukannya pada akhirnya. Bagaimana itu bisa menjadi masalah kompatibilitas?

Saya khawatir tentang dua bidang yang hidup langsung pada struktur yang sama, salah satunya bermutasi dengan yang lain saat Jatuhkan dipanggil (mungkin mengandalkan hal-hal seperti perintah jatuh untuk keamanan) dengan cara yang melanggar invarian penyematan bidang lain, tetapi mempertahankan integritas strukturalnya (sehingga memungkinkan Anda menyaksikan pelanggaran invarian pemasangan pin).

Tapi itu saat ini sudah ilegal. Anda tidak boleh melanggar invarian orang lain.

tapi saya pikir impl Unpin yang tidak aman mungkin akan terlalu sering salah sehingga menjadi hal yang baik untuk disarankan, setidaknya untuk tipe dengan bidang publik umum

Saya pikir itu benar-benar akan benar sebagian besar waktu - Saya berharap kebanyakan orang tidak akan memberikan aksesor apa pun untuk Pin<Self> , dan dalam hal ini mereka kemungkinan besar menggunakan invarian penyematan default dan karenanya tidak masalah untuk unsafe impl Unpin untuk mereka.

Saya pikir sesuatu yang akan membuat Pin jauh lebih berguna dan terintegrasi lebih baik ke dalam kode yang ada, akan dengan tegas turun di sisi pada dasarnya semua jenis penunjuk menjadi Lepas Pin. Yaitu, membuat & mut T, & T, * const T, * mut T, Box, dan seterusnya semua dianggap Lepas pin untuk T. Meskipun mungkin tampak mencurigakan untuk mengizinkan sesuatu seperti, katakanlah, Boxuntuk menjadi Lepas Pin, masuk akal jika Anda menganggap bahwa Anda tidak bisa mendapatkan Pin ke bagian dalam Kotak dari Pin>.

Saya setuju bahwa ini kemungkinan besar akan terjadi (lihat juga komentar saya di https://github.com/rust-lang/rust/pull/49621#issuecomment-378286959). Saat ini saya merasa kita harus menunggu sedikit sampai kita merasa lebih percaya diri dalam keseluruhan hal pemasangan pin, tapi memang saya melihat sangat sedikit gunanya menegakkan pin di luar petunjuk arah.
Saya tidak yakin bagaimana perasaan saya tentang melakukan ini pada petunjuk mentah, kami biasanya sangat konservatif tentang ciri-ciri otomatis untuk mereka karena mereka digunakan dalam berbagai cara gila.


@tokopedia

Saya menulis contoh bagaimana # [derive (PinnedFields)] bisa bekerja: https://github.com/withoutboats/derive_pinned

@pythonesque sudah mengatakan ini, tetapi hanya untuk memperjelas: Perpustakaan itu tidak sehat. Menggunakannya dengan masalah drop @MicahChalmer , saya dapat merusak semua jenis pin yang benar-benar bergantung pada penyematan. Ini tidak tergantung pada jaminan tambahan tentang panggilan drop. Beri tahu saya jika contoh masih diperlukan. ;)

@Ralfian

Perpustakaan itu tidak sehat.

Untuk memperjelas, turunannya hanya unsafe , dan tidak dapat digunakan dalam kombinasi dengan manual Drop impl. Dengan menggunakan turunan, Anda berjanji untuk tidak melakukan hal buruk apa pun yang tercantum dalam implementasi Drop .

https://github.com/rust-lang/rust/pull/50497 mengubah API penyematan, yang terpenting mengganti nama Pin menjadi PinMut untuk memberi ruang untuk menambahkan Pin di masa depan.


Untuk memperjelas, turunannya tidak aman, dan tidak dapat digunakan dalam kombinasi dengan impl. Drop manual.

Setuju, membuatnya tidak aman seperti itu akan berhasil. Meskipun pengujian membuatnya terlihat seperti saat ini itu tidak benar-benar ditandai tidak aman?

@RalfJung Benar, saya rasa saat ini belum. Pilihan lain yang baru saja saya pikirkan adalah membuat versi "aman" membuat implementasi Drop untuk tipe tersebut, memblokir petunjuk manual lain dari Drop . Mungkin ada bendera unsafe untuk mematikan ini. WDYT?

@cramertj ya itu juga harus berfungsi. Namun itu akan memiliki efek samping seperti tetesan yang lebih ketat dan tidak bisa keluar dari ladang.

@pythonesque dan saya mendapat telepon pada hari Senin untuk membahas hal ini. Berikut ringkasannya.

Kami menyimpulkan bahwa mungkin perilaku yang "benar" adalah Drop diambil sendiri dengan pin. Namun, beralih ke hal itu menakutkan. Meskipun mungkin dengan perubahan edisi dalam beberapa bentuk yang saya yakini, ini akan sangat mengganggu.

Perubahan yang kompatibel ke belakang adalah membuatnya entah bagaimana tidak koheren untuk jenis yang dapat "diproyeksikan dengan pin" untuk mengimplementasikan Drop . Dalam repositori, @pythonesque telah mengimplementasikan ini dengan menghasilkan impl dari serangkaian sifat yang berbelit-belit.

Orang bisa membayangkan bentuk bawaan yang sedikit lebih sederhana:

  • Sebuah sifat penanda, sebut saja PinProjection , mengontrol apakah suatu tipe dapat diproyeksikan atau tidak. Tidak koheren (melalui sihir bawaan kompiler) untuk mengimplementasikan PinProjection dan Drop .
  • Sifat lain, PinDrop , meluas PinProjection untuk menyediakan penghancur alternatif ke Drop .

Berasal untuk menghasilkan metode proyeksi pin seperti yang @pythonesque dan saya tunjukkan juga akan menghasilkan implikasi PinProjection untuk jenisnya.

@tokopedia

Kami menyimpulkan bahwa mungkin perilaku yang "benar" adalah Drop mengambil risiko sendiri. Namun, beralih ke hal itu menakutkan. Meskipun mungkin dengan perubahan edisi dalam beberapa bentuk yang saya yakini, ini akan sangat mengganggu.

Hanya bertanya-tanya, JIKA kita ingin melakukan hal seperti itu suatu hari nanti, apakah yang Anda usulkan cocok untuk masa depan?

Misalnya, kita memutuskan untuk ...

  • buat Drop secara ajaib menerima baik formulir yang Dipasangi Pin maupun yang Tidak Disematkan, ATAU
  • tulis ulang tanda tangan Drop semua orang untuk mereka sebagai bagian dari peningkatan otomatis ke Rust 2021 :)

(Saya tidak mengusulkan salah satu dari ini, tetapi tampaknya sulit menjawab pertanyaan tanpa contoh konkret.)

@tmandry Pada dasarnya itulah idenya.

Masalah besarnya adalah implementasi Drop generik yang memindahkan parameter type:

struct MyType<T>(Option<T>);

impl<T> Drop for MyType<T> {
    fn drop(&mut self) {
        let moved = self.0.take();
    }
}

Jenis pemutakhiran otomatis yang Anda bicarakan hanya akan merusak impl Drop ini, yang hanya berlaku jika kita menambahkan batasan bahwa T: Unpin .

Saya tidak tahu kasus penggunaan yang valid untuk benar-benar memindahkan data ?Unpin di dalam sebuah destruktor, jadi hanya kasus ini, yang secara teknis dapat tetapi tidak dimaksudkan untuk itu, itu benar-benar masalah.

@tokopedia

Ciri lain, PinDrop, memperluas PinProjection untuk menyediakan destruktor alternatif untuk Drop.

Mengapa PinDrop memperpanjang PinProjection ? Sepertinya itu tidak perlu.

Juga ini mungkin bisa dibuat sedikit lebih pintar dengan mengatakan bahwa PinProjection dan Drop tidak kompatibel kecuali jenisnya adalah Unpin (atau menemukan cara lain untuk menghapus semua perbedaan / batasan baru ini untuk Unpin jenis).

@RalfJung kami ingin PinDrop dan Drop sama-sama eksklusif. Pada akhirnya, ada beberapa cara untuk menerapkan ini dengan trade off yang berbeda; Saya pikir kami membutuhkan RFC untuk itu karena ini bukan perubahan kecil.

@woutboats Setuju. Saya pikir eksklusivitas dapat muncul dari perlakuan khusus PinDrop , tetapi jelas ada beberapa cara untuk melakukannya. Saya rasa akan sangat berguna jika tipe Unpin tidak perlu diperhatikan; bersama-sama dengan membuat semua tipe pointer Unpin tanpa syarat ini mungkin akan membantu mengurangi jumlah kasus di mana orang bahkan harus tahu tentang ini.

@withoutboats Juga perlu diperhatikan bahwa contoh utama saya dapat memikirkan di mana orang ingin, katakanlah, memindahkan data umum di sekitar Vec dalam destruktor (saya memilih Vec karena orang-orang IIRC ingin mengimplementasikan metode pada Vec yang benar-benar peduli tentang Pin , yang berarti tidak dapat menerapkan tanpa syarat Unpin ), sebenarnya &mut Vec<T> atau Vec<&mut T> atau sesuatu. Saya kebanyakan mengungkitnya karena ini adalah kasus yang mungkin akan tersandung oleh lint yang disarankan @RalfJung , dan mudah-mudahan orang-orang akan dapat berpindah ke PinDrop dengan mudah dalam kasus tersebut daripada unsafe impl Unpin (secara teoritis mereka selalu dapat melakukan yang pertama, tentu saja, dengan membatasi T dengan Unpin dalam tipe, tetapi itu akan menjadi perubahan yang merusak bagi klien perpustakaan).

Juga, perlu dicatat bahwa meskipun ini menambahkan beberapa ciri lebih dari yang kita inginkan, ciri-ciri tersebut adalah yang hampir tidak akan pernah muncul dalam tanda tangan tipe siapa pun. Khususnya PinProjection adalah batasan yang sama sekali tidak ada gunanya kecuali Anda menulis makro #[derive(PinFields)] karena kompilator akan selalu (saya percaya) dapat menentukan apakah PinProjection berlaku jika bisa mencari tahu apa bidang pada jenisnya, dan satu-satunya kegunaannya adalah memproyeksikan bidang. Demikian pula, PinDrop pada dasarnya tidak perlu diikat untuk apa pun, karena alasan yang sama bahwa Drop hampir tidak pernah digunakan sebagai penjilid. Bahkan objek ciri seharusnya tidak terpengaruh (kecuali suatu saat kita mendapatkan "bidang terkait", tetapi dengan fitur baru seperti itu kita dapat mengamanatkan bahwa sifat dengan bidang terkait hanya dapat diterapkan pada jenis PinProjection , yang akan menyelesaikannya dengan rapi masalah itu).

@Ralfian

Saya pikir kami ingin melakukannya pada akhirnya. Bagaimana itu bisa menjadi masalah kompatibilitas?

Saya kira itu tidak lebih dari satu daripada menambahkan jenis yang unimplements Send atau Sync , perbedaannya adalah bahwa tidak satupun dari mereka mempengaruhi bahasa inti pada tingkat yang sama seperti ini. Melakukannya secara otomatis tampaknya analog dengan bagaimana Rust dulu memperlakukan Copy (jenis selalu Copy jika mereka hanya berisi jenis Copy dan tidak menerapkan Drop ) yang akhirnya diubah untuk membuatnya eksplisit karena, mungkin, orang tidak menyukai fakta bahwa menambahkan suatu sifat ( Drop ) dapat merusak kode umum tanpa mereka sadari (karena inti dari koherensi adalah tambahan implementasi sifat tidak boleh merusak peti hilir). Ini tampaknya hampir identik, hanya dengan PinProjection bukan Copy . Saya sebenarnya menyukai perilaku lama, saya hanya berpikir akan sulit untuk membenarkan PinProjection bekerja dengan cara ini ketika Copy tidak.

Tapi itu saat ini sudah ilegal. Anda tidak boleh melanggar invarian orang lain.

Ya, semakin saya memikirkan hal ini, semakin tidak masuk akal tampaknya hal itu bisa menjadi masalah.

Saya pikir itu sebenarnya akan benar sebagian besar waktu

Ya, tetapi hanya karena sebagian besar tipe tidak mengimplementasikan metode apa pun yang memerlukan Pin atau mengekspos kolom generiknya sebagai publik. Sementara yang terakhir tidak mungkin berubah, yang pertama tidak - Saya mengharapkan setidaknya beberapa tipe stdlib yang saat ini !Unpin untuk menambahkan metode proyeksi pin, pada titik mana implementasi unsafe tidak akan tidak valid lagi. Jadi sepertinya hal yang sangat rapuh bagi saya. Selain itu, saya khawatir tentang peningkatan jumlah kode tidak aman "boilerplate" yang harus ditulis orang; Saya pikir Send dan Sync batas adalah unsafe impl dengan benar sesering mereka diimplementasikan dengan tidak benar, dan Unpin akan menjadi lebih berbahaya karena biasanya versi yang benar tidak memiliki batasan pada T . Jadi tampaknya jauh lebih baik untuk mengarahkan orang ke PinDrop (Saya mengerti mengapa Anda berhati-hati melakukannya untuk jenis penunjuk mentah. Saya hanya khawatir bahwa- unsafe kode akan menjadi lebih mungkin untuk melakukan unsafe impl tanpa berpikir, tetapi semakin saya memikirkannya, sepertinya default untuk pointer mentah mungkin benar dan menandai dengan lint Anda akan berguna).

Saya rasa akan sangat berguna jika jenis Lepas Pin tidak harus peduli.

Saya agak setuju, tapi karena seperti yang saya katakan orang tidak akan menggunakan PinProjection sebagai ikatan sebenarnya, saya tidak yakin seberapa penting dalam praktiknya. Sudah ada implementasi DerefMut untuk PinMut mana T: Unpin sehingga Anda tidak akan mendapatkan keuntungan darinya hari ini. Aturan yang diimplementasikan dalam compiler (untuk proyeksi) mungkin akan mengubah PinMut::new menjadi semacam &pin reborrow untuk Unpin jenis, tapi itu tidak ada hubungannya dengan proyeksi lapangan. Dan karena mendapatkan PinProjection tidak memerlukan PinProjection untuk bidangnya, Anda tidak akan membutuhkannya hanya untuk memenuhi batas pada derive pada tipe lain. Jadi sungguh, apa untungnya? Satu-satunya hal yang benar-benar memungkinkan Anda lakukan adalah menerapkan Drop dan memperoleh PinProjections pada saat yang sama, tetapi kami akan selalu ingin orang-orang menerapkan PinDrop jika memungkinkan, jadi itu akan menjadi IMO negatif bersih.

Saya memilih Vec karena orang-orang IIRC ingin mengimplementasikan metode pada Vec yang benar-benar peduli dengan Pin, yang berarti tidak dapat mengimplementasikan Unpin tanpa syarat

Hm, kurasa aku tidak suka itu. Jika Box tanpa syarat Unpin , saya merasa Vec harus sama. Kedua jenis ini biasanya cukup setara dalam kepemilikannya.

Juga kita harus berhati-hati dengan jaminan drop ; Vec::drain misalnya dapat menyebabkan hal-hal bocor jika terjadi kepanikan.

Ini tampaknya hampir identik, hanya dengan PinProjection, bukan Copy

Oh, sekarang saya mengerti pertanyaan Anda. Saya sebenarnya tidak berbicara tentang mendapatkan PinProjections karena proposal saya tidak memiliki sifat seperti itu - tetapi salah satu konsekuensi dari proposal saya adalah menambahkan Drop adalah perubahan besar jika Anda memiliki lapangan umum.

Satu-satunya hal yang benar-benar memungkinkan Anda melakukannya adalah mengimplementasikan Drop dan memperoleh PinProjections pada saat yang sama, tetapi kami selalu ingin orang-orang menerapkan PinDrop jika memungkinkan, sehingga itu akan menjadi IMO negatif bersih.

Itu sebenarnya intinya. Semakin sedikit orang yang harus peduli tentang semua hal menjepit ini, semakin baik.

Pertanyaan klarifikasi: Dalam proposal Anda dan @withoutboats , apakah PinDrop item lang dan diperlakukan oleh kompilator sebagai pengganti Drop , atau hanya sifat yang digunakan oleh derive(PinProjections) untuk mengimplementasikan Drop ?

Juga kita harus berhati-hati tentang jaminan drop; Vec :: drain misalnya dapat menyebabkan kebocoran jika terjadi kepanikan.

Yah, itu benar, tetapi itu tidak benar-benar membatalkan alokasi memori apa pun, benar? Untuk lebih jelasnya, saya sebenarnya lebih suka jika Vec benar-benar menerapkan Unpin tanpa syarat karena akan memudahkan orang untuk hanya beralih ke PinDrop , tetapi pasti ada pembicaraan di beberapa diskusi penyematan tentang membiarkan Anda menyematkan ke elemen pendukung. Setelah Anda mulai berbicara tentang bidang yang disematkan, menjadi jelas bahwa Anda tidak selalu dapat mengubah Vec menjadi Box<[T]> dan kemudian menyematkannya, jadi mungkin benar-benar bernilai di titik itu (meskipun jelas Anda juga bisa menambahkan tipe PinVec alih-alih tipe Vec , seperti yang dilakukan untuk Box ).

Itu sebenarnya intinya. Semakin sedikit orang yang harus peduli tentang semua hal menjepit ini, semakin baik.

Hm, dari sudut pandang saya itu akan benar pada awalnya, tetapi dalam jangka panjang kami ingin memigrasi sebanyak mungkin orang ke default PinDrop , terutama jika jenisnya sebenarnya mengganggu #[derive(PinProjections)] (yang, sekali lagi, bahkan tidak diperlukan untuk tujuan apa pun jika jenisnya adalah Unpin --mungkin hanya akan terjadi pada hal-hal seperti kode yang dihasilkan). Kemudian (mungkin setelah &pin telah menggunakan bahasa tersebut untuk sementara waktu) Anda dapat mengalami perubahan zaman yang mengubah Drop menjadi DeprecatedDrop atau sesuatu. Untuk Unpin hanya mengubah jenis tanda tangan dari &mut menjadi &mut pin akan cukup banyak menyelesaikan semuanya, jadi mungkin ini tidak terlalu diperlukan.

Pertanyaan klarifikasi: Dalam proposal Anda dan @withoutboats , apakah PinDrop merupakan item lang dan diperlakukan oleh compiler sebagai pengganti Drop, atau hanya sifat yang digunakan oleh derive (PinProjections) untuk mengimplementasikan Drop?

Mantan.

Seluruh diskusi Drop ini tampaknya seperti pengawasan yang cukup drastis atas nama RFC asli. Apakah akan bermanfaat jika membuka RFC baru untuk membahas hal ini? Agak sulit untuk mengikuti apa masalahnya, kekhawatiran mana yang lebih merusak daripada yang lain, berapa banyak mesin yang perlu kita tambahkan ke bahasa daripada yang kita pikirkan sebelumnya, berapa banyak kerusakan yang harus kita harapkan (dan apakah hal itu dapat dimitigasi oleh edisi) dalam skenario kasus terburuk dan terbaik, bagaimana kami dapat bertransisi ke apa pun yang berhasil Drop, dan apakah kami dapat memanfaatkan semua ini dan masih memenuhi tujuan kami untuk 2018 melalui beberapa cara lain (seperti bagaimana https: //github.com/rust-lang/rfcs/pull/2418 menyarankan untuk menyembunyikan Pin dari semua API publik agar tidak memblokir pada stabilisasi).

@bstrie Saya pikir hanya ada satu perhatian utama, tapi itu cukup penting. RFC disetujui dengan pemahaman bahwa jenis &pin mut pada suatu saat dapat membuat kode semacam ini aman:

struct Foo<T> { foo : T }

fn bar<T>(x: &pin mut Foo<T>) -> &pin mut T {
  &pin mut x.foo
}

Kode jenis ini harus aman karena dalam praktiknya, referensi yang disematkan tidak benar-benar dapat disusun sebaliknya dalam kode aman, jadi misalnya menerapkan salah satu kombinator masa depan akan memerlukan penggunaan unsafe . Pada saat itu, ada anggapan bahwa penggunaan unsafe dapat dicabut dalam banyak kasus dan oleh karena itu tidak akan menjadi masalah besar.

Sayangnya apakah ini aman tergantung pada implementasi Drop untuk Foo . Jadi jika kita ingin mendukung proyeksi safe field (baik itu melalui fitur bahasa, custom #[derive] , atau mekanisme lainnya) kita harus bisa menjamin bahwa implementasi Drop yang begitu buruk bisa ' t terjadi.

Alternatif yang tampaknya bekerja paling baik keduanya memungkinkan kode di atas untuk bekerja tanpa menggunakan unsafe (dengan hanya penambahan #[derive(PinProjections)] di atas struktur) dan tidak merusak kode yang ada . Ia dapat ditambahkan mundur-kompatibel bahkan jika Pin sudah stabil dan bahkan dapat ditambahkan sebagai perpustakaan murni (dengan pukulan ergonomis yang parah). Ini kompatibel dengan makro #[derive] untuk menghasilkan pengakses dan tipe referensi asli &pin . Meskipun memerlukan penambahan setidaknya satu sifat baru (dan mungkin dua, tergantung bagaimana versi yang disematkan dari Drop diimplementasikan), mereka tidak perlu muncul di mana pun di where klausa atau objek sifat, dan versi baru drop hanya perlu diterapkan untuk jenis yang saat ini memiliki implementasi Drop dan ingin memilih proyeksi pin.

Hanya karena sebuah solusi sepertinya tidak memiliki banyak kerugian, bukan berarti itu bukan perubahan yang substansial, jadi saya pikir kita hampir pasti akan membuat RFC untuk proposal ini ( @withoutboats dan saya membahas melakukan ini melalui telepon) . Tidak masalah untuk menendang secara terpisah, karena sepenuhnya kompatibel dengan mundur. Secara pribadi, saya pikir lebih masuk akal untuk mendorong melalui perubahan ini daripada menghilangkan menjepit di tempat lain.

Kekhawatiran kebanyakan orang dengan PinMut di API publik justru akan membutuhkan unsafe atau memasukkan batas Unpin mana-mana, dan proposal ini memecahkan masalah itu. Alternatif yang dibahas dalam rust-lang / rfcs # 2418 tampak jauh lebih kontroversial, baik dalam mekanisme sebenarnya dari cara yang ingin dihindari berurusan dengan pinning (yang melibatkan perkembangan berbagai sifat lain yang akan muncul di API dan dokumentasi publik) dan dalam keseluruhan kompleksitas solusi. Memang, bahkan jika penyematan benar-benar terselesaikan, saya pikir ada sejumlah pertanyaan yang menurut orang tidak diselesaikan secara memadai dengan RFC itu, jadi saya pikir setidaknya ada kemungkinan yang layak bahwa RFC yang menambahkan proyeksi pin aman dapat berakhir. diterima sebelumnya.

Memang benar bahwa pemasangan pin masih dalam tahap awal (dan saya tahu saya mengeluh tentang hal itu sepertinya terlalu cepat distabilkan), tetapi saya yakin kurangnya proyeksi bidang aman adalah hal utama terakhir yang membuat orang enggan menggunakannya sama sekali. . Demi kelengkapan, berikut semua masalah yang pernah saya lihat orang angkat dengan pemasangan pin, solusi yang mereka usulkan, dan apakah solusi tersebut kompatibel dengan tipe Pin (dalam urutan yang sangat kasar dan bias tentang bagaimana kontroversial, saya menganggap masalahnya saat ini):

  • Yang ini (dapatkah kita membuat bidang proyeksi dilakukan dalam kode aman?). Untuk diselesaikan dengan RFC yang akan datang (di mana strategi implementasi yang mungkin akan dijabarkan, serta alternatif lain yang kami pertimbangkan dan mengapa mereka harus dibuang). Semua variasi resolusi yang diusulkan kompatibel ke belakang.
  • Haruskah menyematkan nilai !Unpin tipe dengan destruktor manual menyiratkan jaminan tambahan (bahwa memori pendukung nilai tidak tidak valid hingga destruktor dipanggil), alias "perubahan untuk koleksi yang mengganggu"?

    Masih belum terselesaikan, sebagian besar karena dapat merusak API pemasangan pin tumpukan yang diusulkan; jika API penyematan tumpukan yang mempertahankan jaminan ini dapat dibuat untuk bekerja dengan async / menunggu, sebagian besar pemangku kepentingan tampaknya bersedia menerima ini (IIRC seseorang sudah mencoba menyelesaikannya menggunakan generator tetapi menggunakan ICE karatc).

    Tidak kompatibel dengan garansi lama; mewajibkan kode yang tidak aman untuk menerapkan jaminan untuk saat ini kompatibel dengan kedua kemungkinan hasil, tetapi hanya jika kode tidak aman tidak diizinkan untuk mengandalkan penegakannya demi kebenaran. Jadi, ini tentu membutuhkan resolusi dengan satu atau lain cara sebelum penyematan distabilkan.

    Untungnya, pengguna yang memasang pin untuk Future s dapat mengabaikan ini selain bentuk API penyematan tumpukan. API penyematan berbasis penutupan kompatibel dengan kedua resolusi, jadi Future pengguna yang tidak menggunakan async / await dapat menggunakan API berbasis penutupan hari ini tanpa menunggu ini diputuskan. Keputusan paling memengaruhi orang yang ingin menggunakan pemasangan pin untuk tujuan lain (seperti koleksi yang mengganggu).

  • Haruskah pointer mentah menjadi Unpin tanpa syarat? Masih belum terselesaikan (saya pikir saya satu-satunya yang mengusulkan dan saya masih cukup banyak 50-50 di atasnya). Tidak akan kompatibel mundur; Saya menandai ini sebagai kontroversial terutama karena alasan itu.
  • Haruskah tipe pustaka standar seperti Vec dibuat tanpa syarat Unpin , atau haruskah mereka menambahkan Pin pengakses bidang? Masih belum terselesaikan, dan mungkin perlu resolusi berdasarkan kasus per kasus. Untuk jenis pembungkus aman yang diberikan, baik menambahkan pengakses atau membuat jenis Unpin tanpa syarat kompatibel ke belakang.
  • Haruskah PinMut::deref dihapus? Jawabannya pada dasarnya tampaknya "tidak" karena keuntungan menyimpannya tampaknya jauh lebih besar daripada kerugiannya, dan tampaknya ada solusi untuk kasus-kasus yang awalnya membuat orang menginginkannya (khususnya, Pin<RefCell<T>> ). Mengubahnya tidak akan kompatibel dengan versi sebelumnya.
  • Bagaimana kita harus menyediakan pengakses lapangan dalam jangka pendek (mengabaikan masalah drop)? Masih belum terselesaikan: dua opsi yang disajikan sejauh ini adalah https://github.com/withoutboats/derive_pinned dan https://github.com/pythonesque/pintrusive. Resolusi sepenuhnya kompatibel ke belakang, karena setelah penurunan perubahan ini dapat dilakukan dengan baik dalam makro murni dalam kode perpustakaan.
  • Bagaimana kami harus menyediakan aksesor lapangan dalam jangka panjang (yaitu harus ada jenis karat &mut pin dan &pin ? Bagaimana cara kerja peminjaman ulang?). Masih belum terselesaikan, tetapi kompatibel dengan semua perubahan yang diusulkan lainnya (sejauh pengetahuan saya) dan jelas dapat disepak tanpa batas.
  • Haruskah tipe seperti referensi yang aman harus tanpa syarat Unpin ? Tampaknya sudah terselesaikan (ya, mereka harus). Resolusi sepenuhnya kompatibel ke belakang.
  • Haruskah kita memiliki tipe Pin dibagikan selain Pin satu yang unik, agar pengakses bidang layak (tidak berfungsi dengan &Pin karena itu adalah referensi untuk referensi)? Resolusi mengubah Pin menjadi PinMut dan menambahkan tipe Pin untuk kasus bersama. Ini tidak kompatibel ke belakang, tetapi perubahan yang melanggar ( Pin menjadi PinMut ) sudah dibuat, dan saya cukup yakin ini telah diterima secara efektif.

Saya pikir itu cukup banyak. Seperti yang Anda lihat semuanya (selain petunjuk mentah dan keputusan deref, yang terakhir tampaknya sebagian besar diselesaikan pada titik ini) memiliki beberapa jalur ke depan yang kompatibel ke belakang, bahkan jika penyematan distabilkan hari ini; yang lebih penting, fakta bahwa kami dapat menyelesaikan proyeksi lapangan berarti bahwa menggunakan jenis yang disematkan di API Anda tidak ditakdirkan untuk menjadi keputusan yang kemudian Anda sesali.

Selain pertanyaan di atas, ada proposal lain yang bertujuan untuk memikirkan kembali pemasangan pin dengan cara yang jauh lebih mendasar. Dua yang menurut saya paling saya pahami adalah proposal @comex untuk menghasilkan !Unpin jenis !DynSized , dan proposal steven099 (di internal; maaf, saya tidak tahu nama Github) untuk memiliki jenis pembungkus baru berukuran Pinned yang membuat bagian dalam tidak dapat dipindahkan (semacam pembungkus ZST).

Opsi !DynSized adalah fitur yang cukup konservatif (dalam arti bahwa Rust sudah memiliki sifat serupa yang tersedia) yang memiliki keuntungan yang mungkin sudah diperlukan untuk menangani tipe buram. Dalam hal ini, ini mungkin lebih tidak invasif daripada perubahan yang diusulkan ke Drop . Ini memiliki keuntungan yang tinggi juga: secara otomatis menyelesaikan masalah dengan Drop karena tipe !Unpin akan menjadi !DynSized dan oleh karena itu seseorang tidak akan dapat keluar dari mereka. Ini akan membuat &mut T dan &T secara otomatis berfungsi sebagai PinMut dan Pin jika T adalah !DynSized , jadi Anda tidak akan melakukannya Tidak memerlukan pengembangan versi yang disematkan dari jenis dan metode yang bekerja pada &mut T seringkali dapat dibuat untuk bekerja secara normal (jika mereka diubah agar tidak memerlukan DynSized terikat ketika mereka tidak membutuhkannya ).

Kelemahan utama (selain kekhawatiran biasa sekitar ?Trait ) tampaknya adalah bahwa jenis !Unpin tidak akan &move , tetapi saya tidak yakin apa semantik yang dimaksudkan itu. Saya juga tidak mengerti (dari proposal) bagaimana Anda bisa memiliki proyeksi lapangan yang aman dengannya, karena ini bergantung pada tipe buram; sepertinya Anda harus menggunakan banyak kode tidak aman secara umum untuk menggunakannya.

Jenis Pinned<T> berukuran agak mirip dalam semangat, tetapi ingin mengatasi masalah di atas dengan memungkinkan Anda untuk membungkus jenis dalam ZST yang membuatnya tidak dapat dipindahkan (secara efektif bertindak tidak berukuran). Rust tidak memiliki apa pun yang cukup sebanding saat ini: PhantomData tidak benar-benar menyertakan turunan dari tipe, dan tipe berukuran dinamis lainnya menghasilkan penunjuk lemak dan masih memungkinkan pergerakan (menggunakan API yang mengandalkan size_of_val ; inilah yang dimaksudkan untuk diperbaiki ?DynSized , jadi proposal ini mungkin mendukung sifat itu lagi). Bagi saya, sepertinya proposal ini tidak benar-benar memperbaiki masalah Drop jika Anda mengizinkan proyeksi yang aman, dan tampaknya juga tidak sesuai dengan Deref , jadi bagi saya keuntungan lebih dari Pin tidak begitu jelas.

jika API penyematan tumpukan yang mempertahankan jaminan ini dapat dibuat untuk bekerja dengan async / menunggu sebagian besar pemangku kepentingan tampaknya bersedia menerima ini (IIRC seseorang sudah mencoba menyelesaikan ini menggunakan generator tetapi menggunakan ICE karatc)

Sebagai referensi, ini mungkin merujuk ke https://github.com/rust-lang/rust/issues/49537 yang berasal dari upaya ini pada API pemasangan pin tumpukan berbasis generator bertingkat. Saya tidak yakin apakah masa pakai di sana akan berfungsi bahkan jika ICE diselesaikan.

Masih belum terselesaikan, sebagian besar karena dapat merusak API pemasangan pin tumpukan yang diusulkan; jika API penyematan tumpukan yang mempertahankan jaminan ini dapat dibuat untuk bekerja dengan async / menunggu, sebagian besar pemangku kepentingan tampaknya bersedia menerima ini (IIRC seseorang sudah mencoba menyelesaikannya menggunakan generator tetapi menggunakan ICE karatc).

Saya melihat API penyematan tumpukan berbasis penutupan sebagai solusi untuk ini, dengan jalan masa depan (sekali &pin adalah bahasa primitif) untuk sesuatu yang lebih ergonomis dan diperiksa oleh kompiler. Ada juga solusi berbasis makro yang diusulkan oleh @cramertj.

Haruskah pointer mentah dilepas tanpa syarat? [...] Tidak akan kompatibel dengan versi sebelumnya; Saya menandai ini sebagai kontroversial terutama karena alasan itu.

Menurut Anda mengapa menambahkan impl Unpin for Vec<T> kompatibel mundur tetapi melakukan hal yang sama untuk pointer mentah tidak?

Kelemahan utama (selain kekhawatiran yang biasa terjadi seputar? Sifat) tampaknya jenis a! Lepas pin tidak akan pernah dapat dipindahkan, yang sangat berbeda dari situasi dengan jenis yang disematkan saat ini. Ini berarti bahwa menyusun dua jenis yang disematkan tanpa menggunakan referensi tidak akan benar-benar mungkin (sejauh yang saya tahu, bagaimanapun) dan saya tidak yakin bagaimana atau apakah ada penyelesaian yang diusulkan untuk ini; IIRC ini dimaksudkan untuk bekerja bersama proposal & pindah, tetapi saya tidak yakin apa semantik yang dimaksudkan itu.

Nah, di bawah proposal varian @ steven099 (yang saya lebih suka), sebagian besar pengguna akan (untuk saat ini) menggunakan Pinned<T> , yang berisi T menurut nilai tetapi !Sized ( dan mungkin !DynSized ; desain yang tepat untuk hierarki sifat terbuka untuk bikeshedding). Ini sebenarnya tampak sangat mirip dengan proposal yang ada, kecuali dengan &'a mut Pinned<T> menggantikan Pin<'a, T> . Tapi itu lebih dapat disusun dengan kode saat ini dan masa depan yang generik pada &mut T (untuk T: ?Sized ), dan lebih kompatibel dengan desain masa depan untuk jenis asli asli yang tidak dapat dipindahkan yang tidak perlu gunakan Pinned .

Untuk lebih detailnya, Pinned bisa terlihat seperti ini:

extern { type MakeMeUnsized; }

#[fundamental]
#[repr(C)]
struct Pinned<T> {
    val: T,
    _make_me_unsized: MakeMeUnsized,
}

Biasanya Anda tidak akan membuat Pinned<T> secara langsung. Sebagai gantinya, Anda dapat melakukan cast dari Foo<T> menjadi Foo<Pinned<T>> , di mana Foo adalah penunjuk cerdas yang menjamin tidak akan memindahkan isinya:

// This would actually be a method on Box:
fn pin_box<T>(b: Box<T>) -> Box<Pinned<T>> {
    unsafe { transmute(b) }
}

(Mungkin juga ada stack pinning API, mungkin berdasarkan makro, tapi itu sedikit lebih rumit untuk diterapkan.)

Dalam contoh ini, FakeGenerator adalah singkatan dari tipe generator yang dihasilkan kompiler:

enum FakeGenerator {
    Initial,
    SelfBorrowing { val: i32, reference_to_val: *const i32 },
    Finished,
}

Setelah nilai FakeGenerator disematkan, kode pengguna tidak lagi dapat mengaksesnya secara langsung dengan nilai ( foo: FakeGenerator ) atau bahkan dengan referensi ( foo: &mut FakeGenerator ), karena yang terakhir akan izinkan menggunakan swap atau replace . Sebaliknya, kode pengguna bekerja secara langsung dengan, misalnya, &mut Pinned<FakeGenerator> . Sekali lagi, ini sangat mirip dengan aturan untuk proposal yang ada PinMut<'a, FakeGenerator> . Tetapi sebagai contoh komposabilitas yang lebih baik, impl yang dihasilkan compiler dapat menggunakan sifat Iterator , daripada membutuhkan yang baru yang membutuhkan Pin<Self> :

impl Iterator for Pinned<FakeGenerator> {
    type Item = i32;
    fn next(&mut self) -> Option<Self::Item> {
        /* elided */
    }
}

Di sisi lain, untuk konstruksi awal, kami dapat membagikan nilai FakeGenerator secara langsung dan mengizinkannya untuk dipindahkan, selama kami menjamin bahwa hanya status non-peminjaman sendiri yang dapat diakses sebelum disematkan:

impl FakeGenerator {
    fn new() -> Self { FakeGenerator::Initial }
}

Jadi, jika kita ingin menyusun dua FakeGenerator s berdasarkan nilai, konstruksinya sangat mudah:

struct TwoGenerators {
    a: FakeGenerator,
    b: FakeGenerator,
}

impl TwoGenerators {
    fn new() -> Self {
        TwoGenerators {
            a: FakeGenerator::new(),
            b: FakeGenerator::new(),
        }
    }
}

Kemudian kita dapat menyematkan objek TwoGenerators secara keseluruhan:

let generator = pin_box(Box::new(TwoGenerators::new()));

Tapi, seperti yang Anda sebutkan, kami membutuhkan proyeksi lapangan: cara untuk beralih dari &mut Pinned<TwoGenerators> menjadi &mut Pinned<FakeGenerator> (mengakses a atau b ). Di sini juga, ini akhirnya terlihat sangat mirip dengan desain Pin . Untuk saat ini, kami akan menggunakan makro untuk menghasilkan pengakses:

// Some helper methods:
impl<T> Pinned<T> {
    // Only call this if you can promise not to call swap/replace/etc.:
    unsafe fn unpin_mut(&mut self) -> &mut T {
        &mut self.val
    }
    // Only call this if you promise not to call swap/replace/etc. on the
    // *input*, after the borrow is over.
    unsafe fn with_mut(ptr: &mut T) -> &mut Pinned<T> {
        &mut *(ptr as *mut T as *mut Pinned<T>)
    }
}

// These accessors would be macro-generated:
impl Pinned<TwoGenerators> {
    fn a(&mut self) -> &mut Pinned<FakeGenerator> {
        unsafe { Pinned::with_mut(&mut self.unpin_mut().a) }
    }
    fn b(&mut self) -> &mut Pinned<FakeGenerator> {
        unsafe { Pinned::with_mut(&mut self.unpin_mut().b) }
    }
}

Namun, seperti desain yang ada, makro perlu mencegah pengguna mengimplementasikan Drop untuk TwoGenerators . Di sisi lain, idealnya kompilator akan mengizinkan kita untuk impl Drop for Pinned<TwoGenerators> . Saat ini menolak ini dengan kesalahan bahwa "Implementasi Jatuhkan tidak dapat dikhususkan", tetapi itu dapat diubah. IMO ini akan sedikit lebih baik daripada PinDrop , karena kita tidak membutuhkan sifat baru.

Sekarang, sebagai ekstensi masa depan, pada prinsipnya kompilator dapat mendukung penggunaan sintaks bidang asli mulai dari Pinned<Struct> menjadi Pinned<Field> , mirip dengan saran di bawah desain yang ada bahwa kompilator dapat menambahkan &pin T asli

Namun, "endgame" ideal saya bukanlah itu, tetapi sesuatu yang lebih dramatis (dan lebih kompleks untuk diterapkan) di mana kompiler suatu hari nanti akan mendukung jenis "asli" yang tidak dapat dipindahkan, termasuk masa hidup eksistensial. Sesuatu seperti:

struct SelfReferential {
    foo: i32,
    ref_to_foo: &'foo i32,
}

Tipe-tipe ini akan berperilaku seperti Pinned<T> , menjadi !Sized dll. [1] Namun, tidak seperti Pinned , mereka tidak memerlukan status bergerak awal. Sebaliknya, mereka akan bekerja berdasarkan "jaminan penghapusan salinan", di mana kompilator akan mengizinkan penggunaan tipe yang tidak dapat dipindahkan dalam nilai-nilai pengembalian fungsi, struct literal, dan berbagai tempat lain, tetapi menjamin bahwa di balik tenda, mereka akan dibangun di tempat. Jadi kita tidak hanya bisa menulis:

let sr = SelfReferential { foo: 5, ref_to_foo: &sr.foo };

(yang sudah bisa Anda lakukan ) ... kami juga bisa menulis hal-hal seperti:

fn make_self_referential() -> SelfReferential {
    let sr = SelfReferential { foo: 5, ref_to_foo: &sr.foo };
    sr
}

atau

let sr: Box<SelfReferential> = box SelfReferential { foo: 5, ref_to_foo: &sr.foo };

Tentu saja, di atas hanyalah sketsa yang sangat kasar tentang seperti apa fiturnya; sintaks yang saya gunakan memiliki masalah, dan sintaksis akan menjadi yang terkecil dari banyak kerumitan yang terlibat dalam mendesain fitur. (Meskipun demikian, saya sudah cukup memikirkannya sehingga saya cukup yakin bahwa kerumitannya dapat diselesaikan - bahwa bukan hanya ide yang tidak koheren yang tidak dapat berfungsi.)

Saya hanya menyebutkannya sebagai bagian dari motivasi, kembali ke masa sekarang, untuk menggunakan desain !DynSized -ish alih-alih desain Pin . Saya pikir &'a Pinned<T> sudah lebih baik dari Pin<'a, T> karena menghindari ledakan kombinatorial tipe pointer. Tapi itu mewarisi beberapa masalah yang sama:

  1. Ini pada dasarnya tidak mendukung jenis yang tidak memiliki status bergerak awal, dan
  2. Berisik, membingungkan (perbedaan antara referensi yang disematkan dan tidak disematkan untuk jenis yang sama sulit dijelaskan), dan membuat jenis yang tidak dapat dipindahkan terasa kelas dua. Saya ingin tipe referensial diri yang tidak tergoyahkan merasa kelas satu - keduanya karena mereka berguna secara inheren, dan untuk membuat Rust terlihat lebih baik bagi orang-orang yang berasal dari bahasa lain, di mana membuat nilai referensi diri itu mudah dan umum.

Di masa mendatang, saya membayangkan, tipe asli yang tidak dapat dipindahkan akan menyelesaikan kedua masalah tersebut, dan sebagian besar kode tidak akan pernah perlu menggunakan kata "pin". (Meskipun Pinned mungkin masih memiliki beberapa kasus penggunaan, untuk kasus di mana penghapusan salinan tidak cukup baik dan Anda benar-benar memerlukan keadaan awal yang dapat dipindahkan.) Sebaliknya, Pin membuat konsep pisahkan status "belum disematkan" ke dalam desain setiap sifat yang menggunakannya. Dan built-in &pin akan memasukkannya ke dalam bahasa.

Bagaimanapun, ini adalah tautan taman bermain untuk desain Pinned atas.

[1] ... meskipun kita mungkin menginginkan sifat baru ReallySized (dengan nama yang tidak terlalu konyol), untuk jenis yang berukuran statis tetapi mungkin atau mungkin tidak dapat dipindahkan. Masalahnya, akan ada beberapa churn di sini, karena dengan dukungan untuk nilai r yang tidak berukuran, banyak fungsi yang saat ini menggunakan argumen Sized dapat bekerja sama baiknya dengan yang tidak berukuran tetapi dapat dipindahkan. Kami akan mengubah batasan fungsi yang ada, dan rekomendasi batasan apa yang akan digunakan untuk fungsi selanjutnya. Saya mengklaim itu bahkan mungkin layak memiliki edisi mendatang mengubah default (tersirat) terikat untuk fungsi generik, meskipun ini akan memiliki beberapa kelemahan.

[edit: contoh kode tetap]

@Ralfian

Saya melihat API stack pinning berbasis penutupan sebagai solusi untuk ini, dengan jalan masa depan (sekali & pin adalah bahasa primitif) untuk sesuatu yang lebih ergonomis dan diperiksa oleh kompiler. Ada juga solusi berbasis makro yang diusulkan oleh @cramertj.

Yang berbasis penutupan tidak berfungsi dengan baik dengan async / await, saya yakin, karena Anda tidak dapat menyerah dari dalam penutupan. Yang berbasis makro memang menarik; jika itu benar-benar aman, itu cukup cerdik. Saya tidak berpikir itu bisa berhasil pada awalnya karena kepanikan selama salah satu tetes di scope bisa menyebabkan yang lain bocor, tapi ternyata itu sudah diperbaiki dengan MIR?

Saya juga tidak yakin tentang interaksi antara jaminan "destruktor yang dijalankan sebelum memori dialokasikan" dan kemampuan untuk memproyeksikan bidang yang disematkan; jika penurunan tingkat atas panik, saya mengira bahwa bidang yang diproyeksikan dengan pin dalam nilai tidak akan menjalankan penurunannya. Namun, di taman bermain Rust sebenarnya tampak bahwa bidang jenis tetap berjalan bahkan setelah kepanikan tipe tingkat atas, yang cukup menarik! Apakah jaminan itu benar-benar didokumentasikan di mana saja? Tampaknya perlu jika stack pinning adalah untuk bekerja dengan proyeksi pin (itu, atau sesuatu yang lebih berat, seperti PinDrop selalu membatalkan panik, yang tampaknya tidak diinginkan karena akan memperkenalkan perbedaan fungsionalitas antara Drop dan PinDrop).

Menurut Anda mengapa menambahkan impl Unpin untuk Veckompatibel dengan mundur tetapi melakukan hal yang sama untuk petunjuk mentah tidak?

Saya mengerti maksud Anda: seseorang dapat mengandalkan implementasi otomatis !Unpin dari bidang Vec<T> dimiliki dan mengimplementasikan pengaksesnya sendiri yang mengasumsikan penyematan bersifat transitif untuk beberapa !Unpin T . Meskipun benar bahwa PinMut<Vec<T>> sebenarnya tidak memberi Anda cara yang aman untuk mendapatkan PinMut<T> , kode yang tidak aman masih dapat mengeksploitasi PinMut::deref untuk mendapatkan petunjuk mentah dan membuat asumsi tentang penunjuk menjadi stabil. Jadi saya kira ini adalah situasi lain di mana itu kompatibel ke belakang hanya jika kode yang tidak aman tidak bergantung pada !Unpin menjadi transitif melalui Vec (atau apa pun). Namun, ketergantungan yang begitu besar pada penalaran negatif tampaknya mencurigakan bagi saya; jika Anda ingin memastikan bahwa Anda !Unpin mana T tidak, Anda selalu dapat menambahkan PhantomData<T> (Saya rasa argumen ini juga berlaku untuk petunjuk mentah). Pernyataan selimut seperti "UB menganggap tipe dalam pustaka standar adalah! Lepas pin dalam kode yang tidak aman, terlepas dari parameter tipenya, kecuali tipe menyisih secara eksplisit atau dokumentasinya menyatakan bahwa ini dapat diandalkan" mungkin sudah cukup.

Yang berbasis makro memang menarik; jika itu benar-benar aman, itu cukup cerdik. Saya tidak berpikir itu bisa berhasil pada awalnya karena kepanikan selama salah satu tetes di scope bisa menyebabkan yang lain bocor, tapi ternyata itu sudah diperbaiki dengan MIR?

Ini akan menjadi bug di MIR jika panik-selama-penurunan akan menyebabkan melewatkan penurunan variabel lokal yang tersisa. Itu seharusnya hanya beralih dari "penurunan normal" ke "penurunan lepas". Kepanikan lain kemudian akan membatalkan program.

@Ralfian

Ini akan menjadi bug di MIR jika panik-selama-penurunan akan menyebabkan melewatkan penurunan variabel lokal yang tersisa. Itu seharusnya hanya beralih dari "penurunan normal" ke "penurunan lepas". Kepanikan lain kemudian akan membatalkan program.

Apakah bidang lain dalam struktur yang dihapus dianggap variabel lokal dalam konteks ini? Itu benar-benar tidak jelas bagi saya dari dokumentasi yang dihadapi pengguna (sebenarnya, ini benar dari seluruh jaminan yang Anda bicarakan - saya hanya menemukan itu sebenarnya dianggap sebagai bug yang perlu diperbaiki dari pelacak masalah).

@comex

Hal tentang Pinned (apa yang Anda usulkan) adalah saya pikir jika kita ingin menerapkannya (setelah Rust memiliki semua fitur di tempatnya) kita tidak perlu melakukan lebih dari ini untuk membuatnya kompatibel dengan kode yang ada:

type PinMut<'a, T> = &'a mut Pinned<T>;
type Pin<'a, T> = &'a Pinned<T>;

(Saya pikir memiliki implementasi deref dari Pin menjadi Pinned juga diusulkan). Sangatlah bermanfaat untuk melihat tempat-tempat yang tampaknya tidak akan berhasil. Sebagai contoh:

impl Drop for Pinned<TwoGenerators>

tidak bekerja (setidaknya, tidak langsung) dengan PinMut . Bahkan jika kita berasumsi bahwa ini menggantikan Drop untuk TwoGenerators itu sendiri ketika tipe Pinned dibuat (saya tidak yakin bagaimana ini akan bekerja?), Rust tetap tidak tahu untuk memanggil versi Pinned dari konstruktor untuk setiap bidang yang diproyeksikan, karena bidang hanya akan disimpan oleh nilai. Jika versi destruktor yang disematkan selalu digunakan jika ada, ini secara efektif identik dengan PinDrop , hanya dengan sintaks yang aneh, dan saya tidak melihat bagaimana itu lebih baik.

Namun, seseorang tergoda untuk mempertimbangkan memilih destruktornya dengan menganalisis secara rekursif apakah suatu nilai di-root pada Pinned . Perhatikan bahwa jika kita ingin mengizinkan objek ciri nilai, kita tidak dapat selalu mengandalkan kemampuan untuk memutuskan apakah akan menggunakan penurunan Pinned<T> atau penurunan T pada waktu kompilasi; Saya kira Anda berpikir mungkin ada entri vtable terpisah untuk versi Pinned dalam kasus seperti itu? Ide ini agak menarik bagi saya. Ini pasti membutuhkan dukungan kompiler yang substansial (jauh lebih banyak daripada PinDrop diusulkan), tetapi mungkin secara keseluruhan lebih baik dalam beberapa hal.

Namun, membaca ulang utas, saya ingat ada masalah lain: implementasi deref untuk jenis yang disematkan benar - Pinned<T> (Saya menduga ini karena implementasi deref pada PinMut secara logis salah dalam beberapa hal, itulah sebabnya mengapa hal itu terus menimbulkan masalah, tetapi sangat sulit untuk membenarkan kehilangannya karena kemudahannya - kecuali Anda membuat sejumlah besar tipe tanpa syarat Unpin tetap). Secara khusus, saya menemukan contoh RefCell cukup mengganggu karena dengan adanya Pinned::deref itu berarti bahwa kami bahkan tidak dapat memaksakan pinning secara dinamis dengan tipe yang ada (saya tidak tahu jika spesialisasi sudah cukup). Ini lebih lanjut menyarankan bahwa jika kita mempertahankan implementasi deref , kita akan harus menggandakan permukaan API sebanyak Pinned seperti yang kita lakukan dengan Pin ; dan jika kita tidak menyimpannya, Pinned<T> menjadi sangat sulit digunakan. Ini juga tidak tampak seperti Box<Pinned<T>> akan berfungsi kecuali kita menambahkan ?Move secara terpisah dari ?DynSized (seperti yang ditunjukkan di utas).

Semua ini mungkin ide yang bagus, namun dalam konteks Rust saat ini semuanya tampak agak tidak menarik bagi saya, terutama ketika Anda menganggap bahwa pada dasarnya tidak ada metode saat ini yang akan memiliki ?Move batas (artinya kekurangan dari deref akan benar-benar menyakitkan, kecuali jenisnya adalah Unpin dalam hal ini Pinned tidak menawarkan keuntungan). Saya menduga ini memodelkan apa yang sebenarnya terjadi lebih dekat dengan menyematkan properti milik, yang pada gilirannya membuatnya lebih sulit untuk lolos dengan keputusan ad hoc seperti PinMut::deref dan membuat antarmuka yang jauh lebih menyenangkan (bagaimanapun juga secara subyektif) , tetapi ia menambahkan banyak mesin bahasa untuk melakukannya dan sepertinya menurut Anda tidak ada yang berguna (dibandingkan dengan jenis asli tak bergerak yang Anda usulkan). Saya juga tidak tahu apakah apa yang kita dapatkan yang tidak bisa sepenuhnya kompatibel dengan mundur (kebanyakan, memanggil implementasi drop yang berbeda tergantung pada status pin) sebenarnya berguna, bahkan jika itu bisa dilakukan (mungkin Anda bisa menyimpan cabang di destruktor kadang-kadang jika Anda tahu tipe itu disematkan?). Jadi saya tidak yakin apakah perlu mengubah proposal PinMut pada saat ini. Tapi mungkin saya melewatkan beberapa kasus penggunaan konkret yang sangat menarik.

Hal tentang Pinned (apa yang Anda usulkan) adalah saya pikir jika kita ingin menerapkannya (setelah Rust memiliki semua fitur di tempatnya) kita tidak perlu melakukan lebih dari ini untuk membuatnya kompatibel dengan kode yang ada:

type PinMut<'a, T> = &'a mut Pinned<T>;
type Pin<'a, T> = &'a Pinned<T>;

Pertama-tama, Pinned itu sendiri memerlukan dukungan kompiler minimal-to-no, jika itu yang Anda maksud dengan "fitur". Jika yang Anda maksud adalah desain perpustakaan, seperti DynSized dan ciri terkait, maka itu valid, tapi…

Apa yang Anda sarankan tidak akan benar-benar kompatibel ke belakang, karena misalnya Anda mungkin mencoba menerapkan sifat yang sama untuk Pin<'a, T> dan &'a T , yang tiba-tiba akan menjadi bentrok.

Secara umum, ada perbedaan signifikan dalam desain API. Dengan Pin , ciri-ciri yang diharapkan diimplikasikan oleh tipe tak bergerak harus memiliki metodenya mengambil PinMut<Self> , fungsi umum yang ingin mengacu pada tipe tak bergerak harus terlihat seperti fn foo<T>(p: PinMut<T>) , dll. Di sisi lain, desain Pinned menghindari kebutuhan akan sifat-sifat baru dalam banyak kasus, karena Anda dapat mengimplikasikan ciri-ciri untuk Pinned<MyStruct> . Jadi:

  1. Tipe yang tidak dapat dipindahkan akan tidak sesuai dengan sifat yang ada yang metodenya mengambil &self atau &mut self : misalnya, generator tidak dapat mengimplikasikan Iterator . Jadi kita akan membutuhkan sekumpulan sifat baru yang setara dengan yang sudah ada tetapi mengambil PinMut<Self> sebagai gantinya. Jika kita mengubah arah dan membuat PinMut<T> alias untuk &mut Pinned<T> , kita kemudian dapat kembali dan mencela semua sifat duplikat itu, tapi itu akan sangat konyol. Lebih baik tidak membutuhkan duplikat di tempat pertama.

  2. Di sisi lain, ciri-ciri yang baru dirancang atau khusus generator mungkin akan mengambil PinMut<Self> sebagai satu-satunya pilihan, dengan biaya menambahkan kebisingan untuk jenis yang ingin menerapkan ciri-ciri tersebut tetapi tidak dapat bergerak dan tidak perlu untuk disematkan. (Secara khusus, penelepon harus menelepon PinMut::new untuk beralih dari &mut self menjadi PinMut<Self> , dengan asumsi Self: Unpin .) Bahkan jika Pin<T> menjadi alias untuk &mut Pinned<T> , tidak akan ada cara untuk menghilangkan kebisingan itu. Dan tipe tak bergerak bawaan masa depan yang saya bayangkan akan berada dalam situasi yang sama dengan tipe bergerak, yang tidak perlu dibungkus dalam Pinned ketika mereka selalu dianggap disematkan.

Saya akan menanggapi sisa postingan Anda di postingan kedua.

Mengenai Drop

Saya agak bingung dengan apa yang Anda katakan tentang Drop , tetapi sejauh Anda mencoba membuatnya kompatibel dengan PinMut , saya tidak akan memikirkannya karena menurut saya itu bukan pendekatan yang bagus.

Saya pikir pendekatan terbaik adalah jika Anda memiliki struct seperti TwoGenerators , Anda memiliki dua opsi:

  1. Tidak ada petunjuk Drop impl baik untuk TwoGenerators atau Pinned<TwoGenerators> ;
  2. Anda mengimplikasikan Drop untuk Pinned<TwoGenerators> ; sementara itu, makro yang sama yang memberi Anda pengakses akan menghasilkan Drop impl untuk TwoGenerators itu sendiri yang hanya mengubah dirinya menjadi &mut Pinned<TwoGenerators> dan menjatuhkannya. (Ini aman: hal yang tidak diinginkan untuk mengubah &mut T menjadi &mut Pinned<T> adalah bahwa Anda tidak akan memindahkan T setelah peminjaman berakhir, dan dalam kasus Drop , Anda memiliki pinjaman terakhir yang akan pernah dibuat untuk nilai itu.)

Satu-satunya alasan untuk memiliki dua opsi adalah, seperti yang disebutkan sebelumnya, Anda mungkin tidak ingin struct Anda mengimplikasikan Drop karena struct yang tidak menyiratkannya diperlakukan lebih longgar oleh pemeriksa peminjam.

Saya tidak mengerti mengapa Anda ingin memiliki destruktor terpisah yang sebenarnya untuk status yang disematkan versus tidak disematkan, jadi tidak perlu memainkan trik dengan vtables untuk membedakannya.

Mengenai RefCell

Saya tidak berpikir Pinned::deref harus ada. Pengakses bidang yang dihasilkan makro yang aman harus cukup; Saya tidak melihat bagaimana itu "sangat sulit digunakan". Ini sedikit kurang bagus daripada bisa menggunakan sintaks bidang asli, tapi suatu hari nanti itu akan diperbaiki oleh struct tidak bergerak asli. Bagaimanapun, jika sulit digunakan, masalah yang sama berlaku untuk Pin .

Ini menghindari masalah dengan RefCell .

terutama ketika Anda menganggap bahwa pada dasarnya tidak ada metode di Rust saat ini yang akan memiliki ?Move batas (artinya kurangnya deref akan sangat merugikan [..])

Sebaliknya, semua yang memiliki ?Sized terikat secara implisit ?Move .

Ini masuk akal karena secara umum, tidak mungkin kode dengan ?Sized terikat untuk mengasumsikan kemampuan bergerak. Satu-satunya pengecualian adalah kode tidak aman yang memanggil size_of_val dan kemudian memcpy banyak byte, itulah sebabnya kita memerlukan peretasan di mana size_of_val akan panik untuk jenis yang tidak dapat dipindahkan (dan tidak digunakan lagi untuk mendukung fungsi baru dengan ikatan yang tepat).

Saya agak bingung dengan apa yang Anda katakan tentang Drop, tetapi sejauh Anda mencoba membuatnya kompatibel dengan PinMut, saya tidak akan memikirkannya karena menurut saya itu bukan pendekatan yang baik. .

Saya mengatakan bahwa jika sesuatu tidak kompatibel dengan PinMut , harus ada alasan bagus untuk itu. Namun, hal yang Anda usulkan secara fungsional identik dengan PinDrop di setiap hal tertentu kecuali Anda ingin menerapkannya pada Pinned<T> (yang tidak berfungsi di Rust saat ini). Secara pribadi, saya pikir mengkhususkan Drop menetapkan preseden yang sangat meragukan dan hampir pasti tidak diinginkan karena alasan yang tidak ada hubungannya dengan pin, jadi saya tidak menganggap ini sebagai keuntungan intrinsik apa pun. Bagaimanapun saya pikir PinDrop sebagian besar dapat dipisahkan dari sisa proposal Anda.

Pokoknya, jika sulit digunakan, masalah yang sama berlaku untuk Pin.

Tentu, dan jika kita bersedia membuang PinMut::deref , itu juga akan disusun dengan baik dengan tipe seperti RefCell ; perbedaannya adalah kita masih dapat menggabungkan solusi dengan PinMut sambil mendukung deref , yang tampaknya tidak berfungsi dengan Pinned . Jika kita harus menyingkirkan implementasi deref , saya pikir saya akan lebih setuju bahwa Pinned memberikan keuntungan yang berarti.

Tetapi saya benar-benar tidak yakin Saya setuju bahwa tidak memiliki deref hanyalah masalah kecil dalam praktiknya: misalnya, itu berarti bahwa dalam keadaan saat ini Anda tidak dapat melakukan apa pun dengan &Pinned<Vec<T>> mana T: !Unpin , dan hal yang sama berlaku untuk setiap jenis perpustakaan yang ada. Ini adalah masalah yang berasal dari cara kerja Unpin , bukan jenis referensi tertentu yang Anda miliki. Ekosistem harus secara kolektif memutuskan untuk melakukan hal-hal di sepanjang baris impl Deref for Pinned<Vec<T>> { type Target = Pinned<[T]>; } atau sesuatu, yang menurut saya saya setuju akan lebih baik daripada impl PinDeref<Vec<T>> jika itu dapat dibuat untuk bekerja, tetapi itu masuk dunia tanpa deref . Di dunia dengan deref , hampir semua perpustakaan dapat pergi tanpa aksesor terkait pin sama sekali, dan masih memiliki dukungan setengah layak untuk jenis !Unpin .

Sebaliknya, segala sesuatu yang memiliki "Sized bound" secara implisit "Move".

Ah ya, itu poin yang bagus. Sayangnya, sejumlah besar kode Rust tidak berfungsi dengan tipe dengan !Sized terikat, karena ini bukan default, tetapi setidaknya beberapa di & atau &mut (misalnya untuk irisan atau objek sifat), tidak satu pun dari yang dapat saya lakukan berdasarkan proposal Anda (kecuali untuk tipe Unpin ) karena Anda tidak menginginkan Pinned::deref . Mungkin kasus umum dapat ditangani dengan membuat #[derive] implementasi juga menghasilkan contoh yang berbeda untuk Pinned<T> atau sesuatu?

Drop

Secara pribadi, saya pikir spesialisasi Drop menetapkan preseden yang sangat meragukan dan hampir pasti tidak diinginkan karena alasan yang tidak ada hubungannya dengan pin, jadi saya tidak menganggap ini sebagai keuntungan intrinsik apa pun. Bagaimanapun saya pikir PinDrop sebagian besar dapat dipisahkan dari sisa proposal Anda.

Saya setuju ini dapat dipisahkan, tetapi menurut saya itu tidak meragukan. Setidaknya… apa yang Anda katakan itu benar, itu adalah bentuk spesialisasi. Ini tidak secara harfiah mengkhususkan beberapa implan selimut induk Drop , tetapi lem jatuh yang dihasilkan kompiler melakukan ekuivalen dengan spesialisasi dengan memanggil Drop hanya jika itu diterapkan. Implementasi 'userland' akan terlihat seperti ini (mengabaikan fakta bahwa Anda tidak dapat memanggil drop secara manual):

trait DropIfImplemented {
    fn maybe_drop(&mut self);
}
impl<T: ?Sized> DropIfImplemented for T {
    default fn maybe_drop(&mut self) {}
}
impl<T: ?Sized + Drop> DropIfImplemented for T {
    fn maybe_drop(&mut self) { self.drop() }
}

Jadi, saya membayangkan bahwa alasan Anda saat ini tidak dapat menulis 'terspesialisasi' Drop impls adalah alasan yang sama bahwa spesialisasi itu sendiri saat ini tidak masuk akal: inkoherensi antara trans (yang menghapus parameter seumur hidup) dan typeck (yang tidak). Dengan kata lain, jika kita dapat menulis, katakanlah, impl Drop for Foo<'static> , itu sebenarnya akan dipanggil untuk Foo<'a> , bukan hanya Foo<'static> , karena codegen menganggap kedua tipe itu identik.

Kabar baiknya adalah, seperti yang mungkin Anda ketahui, telah ada upaya untuk menemukan cara membatasi impls khusus sehingga mereka tidak dapat menciptakan jenis inkoherensi tersebut. Dan diharapkan bahwa spesialisasi pada akhirnya akan dikirimkan dengan batasan seperti itu. Setelah itu terjadi, saya tidak melihat alasan kami tidak dapat menerapkan aturan yang sama ke Drop impls - dan untuk membuat bahasa sekonsisten mungkin, kami harus melakukannya.

Sekarang, kami tidak ingin memblokir penyematan pada spesialisasi. Namun, saya mengklaim bahwa mengizinkan impl Drop for Pinned<MyStruct> - atau lebih umum, mengizinkan impl<params> Drop for Pinned<MyStruct<params>> dalam kondisi yang sama dengan yang saat ini diizinkan oleh compiler impl<params> Drop for MyStruct<params> - dijamin menjadi bagian dari spesialisasi apa memungkinkan, jadi jika kita membuat kasus khusus untuk itu hari ini, pada akhirnya akan menghilang ke aturan yang lebih umum.

Tapi sekali lagi, ini bisa dipisahkan; jika orang tidak menyukai ini, kita dapat memiliki sifat yang berbeda.

Unpin

Tetapi saya benar-benar tidak yakin Saya setuju bahwa tidak memiliki deref hanyalah masalah kecil dalam praktiknya: misalnya, itu berarti bahwa dalam keadaan saat ini Anda tidak dapat melakukan apa pun dengan &Pinned<Vec<T>> mana T: !Unpin , dan hal yang sama berlaku untuk setiap jenis perpustakaan yang ada. Ini adalah masalah yang berasal dari cara kerja Unpin , bukan jenis referensi yang Anda miliki.

Er… oke, izinkan saya mengoreksi pernyataan saya. Pinned::deref harus ada, tetapi harus dibatasi pada Unpin - meskipun saya akan menyebutnya Move .

Satu- satunya alasan Deref impl untuk PinMut menyebabkan masalah dengan RefCell adalah bahwa (tidak seperti DerefMut impl) tidak terikat pada Unpin . Dan alasan untuk tidak memiliki batasan adalah keinginan untuk mengizinkan pengguna mendapatkan &MyImmovableType , memungkinkan tipe yang tidak dapat dipindahkan untuk mengimplikasikan ciri dengan metode yang mengambil &self , untuk diteruskan ke fungsi umum yang mengambil &T , dll. Hal ini pada dasarnya tidak mungkin untuk &mut self , tetapi sebagian besar berfungsi dengan &self karena Anda tidak dapat memindahkannya dengan mem::swap atau mem::replace - yaitu, kecuali jika Anda menggunakan RefCell . Namun, menurut penalarannya, kesesuaian dengan referensi yang sudah ada cukup berharga sehingga harus didukung, meskipun batasan referensi yang tidak dapat diubah terasa sewenang-wenang, sekalipun menyebabkan kludges.

Dengan Pinned , kami dapat mendukung referensi yang tidak dapat diubah dan yang dapat diubah: Anda hanya mengimplikasikan ciri-ciri Anda pada Pinned<MyStruct> daripada langsung pada MyStruct . Sisi negatifnya adalah bahwa itu tidak kompatibel dengan ciri atau fungsi yang mengambil &T tetapi secara terpisah memiliki Self: Sized terikat; tetapi itu relatif jarang, dan seringkali tidak disengaja.

Menariknya, Pinned itu sendiri tidak benar-benar membutuhkan Unpin untuk ada sama sekali. Lagi pula, mengapa ada orang yang benar-benar membuat &Pinned<Vec<T>> ? Dengan PinMut , berbagai sifat akan menghasilkan PinMut<Self> , jadi bahkan implik dari sifat-sifat itu untuk tipe bergerak harus menerima PinMut . Dengan Pinned , seperti yang saya katakan, ciri-ciri akan terus mengambil &self atau &mut self , dan Anda akan menyiratkannya untuk Pinned<MyStruct> . Jika Anda ingin menerapkan sifat yang sama untuk Vec<T> , Pinned tidak perlu ditampilkan.

Namun, salah satu sumber potensial adalah makro pengakses bidang. Jika Anda memiliki

struct SomePinnable {
    gen: FakeGenerator,
    also_a_vec: Vec<Foo>,
}

maka desain yang paling sederhana akan selalu menghasilkan pengakses menggunakan Pinned :

impl Pinned<SomePinnable> {
    fn gen(&self) -> &Pinned<FakeGenerator> { … }
    fn gen_mut(&mut self) -> &mut Pinned<FakeGenerator> { … }
    fn also_a_vec(&self) -> &Pinned<Vec<Foo>> { … }
    fn also_a_vec_mut(&mut self) -> &mut Pinned<Vec<Foo>> { … }
}

… Dan terserah Anda untuk menangani Pinned jika Anda tidak menginginkannya. Sebenarnya, saya pikir ini baik-baik saja, karena Unpin / Move seharusnya ada, lihat di bawah. Tetapi jika tidak ada, alternatifnya adalah memiliki cara untuk ikut serta per bidang untuk menerima referensi langsung daripada Pinned satu. Artinya, Anda akan melakukannya

    fn also_a_vec(&self) -> &Vec<Foo> { … }
    fn also_a_vec_mut(&mut self) -> &mut Vec<Foo> { … }

Tidak masuk akal untuk memiliki aksesor Pinned dan non- Pinned , tetapi keduanya saja sudah cukup.

… Tapi ya, kita memang perlu memiliki sifat Move , bukan untuk Pinned . Misalnya, ini akan menjadi bagian dari terikat untuk versi baru size_of_val (koreksi: tidak akan terjadi, tetapi kode yang tidak aman diharapkan memeriksanya sebelum mencoba memcpy jenis arbitrer berdasarkan hasilnya dari size_of_val ); di masa depan dengan rvalues ​​tidak berukuran, itu akan menjadi batasan (santai dari Sized ) untuk ptr::read dan mem::swap dan mem::replace ; jika kita pernah mendapatkan &move , itu akan menjadi batasan untuk membiarkan Anda, yah, keluar dari satu; dan hal serupa berlaku untuk penghapusan salinan yang dijamin.

Jadi, selama kita memiliki sifat tersebut, tidak ada alasan untuk tidak memiliki Pinned::deref (dan deref_mut ) dengan ikatan T: Move .

[edit: seperti yang diingatkan pythonesque, ini memiliki semantik yang berbeda dari Unpin , jadi sudahlah.]

(Dan kemudian jenis seperti Vec dan Box akan ingin secara manual mengimplikasikan Move sehingga itu berlaku terlepas dari apakah jenis elemen adalah Move .)

Er… oke, izinkan saya mengoreksi pernyataan saya. Pinned :: deref harus ada, tetapi harus dibatasi di Unpin - meskipun saya akan menyebutnya Pindah.

Oke, tunggu. Apakah ini ?Move atau Move ? Yang pertama berarti tipe !Unpin bahkan tidak dapat dibangun dalam banyak kasus; yang terakhir membuat saya bertanya-tanya bagaimana tepatnya, kita mengacu pada tipe seperti Pinned<T> (karena ?DynSized sebenarnya bukan batasan yang tepat untuk mereka). Saya tentu berharap mereka tidak satu dan sama - jika tidak, memperlakukan Move sebagai Unpin sekali lagi melakukan hal yang sama persis seperti yang kita coba hindari dan membuat tipe tidak tergoyahkan saat itu dibangun.

Sisi negatifnya adalah tidak kompatibel dengan ciri atau fungsi yang mengambil & T tetapi secara terpisah memiliki Self: Sized bound; tetapi itu relatif jarang, dan seringkali tidak disengaja.

Ada kerugian praktis yang jauh lebih signifikan, yaitu hanya sedikit dari ciri atau fungsi tersebut yang benar-benar berfungsi dengan & Disematkanhari ini. Mereka dapat dibuat untuk bekerja dengannya tetapi akan membutuhkan sejumlah besar implementasi sifat tambahan dan (seperti yang saya katakan) mungkin perbaikan signifikan dari implementasi #[derive] . Ini juga merupakan biaya yang harus dibayar untuk barang baru juga - Anda harus menerapkan semuanya untuk &Pinned<Self> juga jika Anda ingin itu bekerja pada jenis !Unpin . Ini adalah situasi (yang jauh) lebih baik untuk sifat-sifat yang mengambil &mut self daripada dengan PinMut , tetapi lebih buruk untuk &self , yang (saya duga) jauh lebih umum. Oleh karena itu mengapa saya mengatakan saya pikir ini adalah solusi yang lebih tepat (dalam arti bahwa jika kita tidak memiliki banyak pustaka Rust yang ada, versi Pinned akan lebih baik) tetapi mungkin bukan yang lebih bermanfaat.

Dengan Pinned, seperti yang saya katakan, sifat akan terus mengambil & self atau & mut self, dan Anda akan mengimplikasikannya untuk Pinned

Menerapkan ulang setiap sifat di permukaan API Vec , hanya dengan Pinned kali ini, tidak terdengar bagus bagi saya (terutama karena beberapa sifat bahkan tidak bekerja dengannya). Saya cukup yakin untuk menerapkan Deref secara selektif berdasarkan kasus per kasus (misalnya, membiarkan &Pinned<Vec<T>> menjadi &[Pinned<T>] , misalnya), atau membiarkan seluruh Vec menjadi Unpin (dan tidak mengizinkan proyeksi pin), jauh lebih waras. Bagaimanapun, keduanya lebih banyak bekerja daripada tidak melakukan apa-apa, dan itu harus direplikasi di sejumlah besar tipe dan sifat yang ada agar hal-hal yang tidak tergoyahkan dapat bekerja dengan mereka.

Saya dapat diyakinkan sebaliknya - Saya benar-benar menyukai solusi Pinned semakin saya memikirkannya - tetapi saya tidak tahu di mana semua implementasi sifat baru ini pada Pinned<T> sebenarnya akan datang dari; Bagi saya tampaknya lebih mungkin bahwa orang tidak mau repot-repot menerapkannya.

Menariknya, Pinned itu sendiri tidak benar-benar mengharuskan adanya Unpin. Lagi pula, mengapa ada orang yang benar-benar membuat & Disematkan>?

Ada alasan yang cukup bagus untuk melakukan ini (seperti: tipe tersemat Anda memiliki Vec di dalamnya). Koleksi yang mengganggu akan sangat sering mengalami skenario semacam ini. Saya pikir setiap proposal yang didasarkan pada gagasan bahwa orang tidak akan pernah menginginkan referensi Pinned ke penampung yang ada, atau bahwa Anda harus memilih untuk membuat Unpin berfungsi, kemungkinan besar tidak akan berfungsi dengan baik. Tidak dapat pada dasarnya memilih ekosistem reguler Rust dengan menambahkan Unpin terikat akan sangat mengganggu (pada kenyataannya, hampir setiap kasus penggunaan yang saya miliki untuk tipe yang tidak dapat dipindahkan akan menjadi jauh lebih sulit).

Dengan PinMut, berbagai sifat akan menggunakan PinMut, bahkan implik dari ciri-ciri tersebut untuk tipe bergerak harus menerima PinMut.

Tentu! Keuntungan besar dari versi Pinned adalah Anda tidak memerlukan sifat yang berbeda untuk referensi yang disematkan dapat berubah. Namun, ini lebih buruk atau netral daripada PinMut dengan deref untuk hampir setiap skenario lainnya.

Tidak masuk akal jika memiliki aksesor yang Dipasangi Pin dan yang Tidak Disematkan, tetapi keduanya saja sudah cukup.

Aksesor manual yang membutuhkan kode yang tidak aman untuk diterapkan terdengar seperti ide yang buruk bagi saya; Saya tidak melihat bagaimana proposal seperti itu akan memungkinkan pembuatan pengakses menjadi aman (bagaimana Anda menghentikan seseorang dari menyediakan pengakses yang tidak disematkan tanpa membuat mereka menulis unsafe untuk menyatakan bahwa mereka tidak akan melakukannya?). Namun, seperti yang Anda catat, menggunakan Move (dengan asumsi itu sebenarnya berarti Unpin ) akan berfungsi dengan baik.

Jadi, selama kita memiliki sifat tersebut, tidak ada alasan untuk tidak menyematkan :: deref (dan deref_mut) dengan terikat T: Pindah.

Tentu. Saya secara khusus berbicara tentang tipe !Unpin sini. Tipe Unpin juga tidak memiliki masalah komposisi dengan PinMut , jadi mereka tidak begitu relevan dari sudut pandang saya. Namun, batas Unpin (atau Move ) pada obat generik tidak menyenangkan dan idealnya Anda harus dapat menghindarinya sedapat mungkin. Sekali lagi, seperti yang saya katakan sebelumnya: Unpin dan apa pun yang disiratkan oleh !Sized tidaklah sama dan menurut saya Anda tidak dapat memperlakukan mereka sebagai hal yang sama.

… Tapi ya, kita memang perlu memiliki sifat Pindah, hanya saja tidak untuk Disematkan. Misalnya, ini akan menjadi bagian dari batasan untuk versi baru size_of_val; di masa mendatang dengan rvalues ​​tidak berukuran, itu akan menjadi terikat (disederhanakan dari Sized) untuk ptr :: read dan mem :: swap dan mem :: replace; jika kami pernah mendapatkan & pindah, itu akan menjadi batasan untuk membiarkan Anda, yah, keluar dari salah satu; dan hal serupa berlaku untuk penghapusan salinan yang dijamin.

Saya pikir ini sekali lagi menggabungkan !Unpin (yang mengatakan suatu tipe dapat memiliki invarian pin non-default) dan !DynSized -seperti !Move ; mereka tidak bisa benar-benar sama tanpa menyebabkan perilaku pembekuan yang tidak diinginkan.

Ups, Anda sepenuhnya benar. Unpin tidak boleh sama dengan Move .

Jadi saya pikir saya kembali percaya Unpin dan karenanya Pinned::deref seharusnya tidak ada sama sekali, dan sebaliknya kita harus menghindari situasi apa pun (seperti dengan makro penghasil pengakses) di mana Anda akan dapatkan tipe seperti &Pinned<MovableType> . Tapi mungkin ada argumen bahwa itu harus ada sebagai sifat yang terpisah.

Menerapkan ulang setiap sifat di permukaan API Vec , hanya dengan Pinned kali ini, tidak terdengar bagus bagi saya (terutama karena beberapa sifat bahkan tidak bekerja dengannya). Saya cukup yakin apakah akan menerapkan Deref secara selektif berdasarkan kasus per kasus (misalnya, membiarkan &Pinned<Vec<T>> menjadi &[Pinned<T>] , misalnya), atau membiarkan seluruh Vec menjadi Unpin (dan tidak mengizinkan proyeksi pin), jauh lebih waras.

Ya, saya tidak bermaksud untuk mengusulkan penerapan ulang seluruh permukaan API Vec atau semacamnya.

Saya setuju akan lebih baik jika tidak mengizinkan "proyeksi pin" pada Vec , karena &Pinned<Vec<T>> tampak seperti invarian yang asing - Anda harus diizinkan untuk memindahkan Vec tanpa membatalkan pin konten.

Sebagai alternatif, bagaimana dengan mengizinkan transmutasi dari Vec<T> menjadi Vec<Pinned<T>> , yang akan memiliki sebagian besar dari Vec API tetapi menghilangkan metode yang dapat menyebabkan realokasi? Artinya, ubah definisi Vec dari struct Vec<T> menjadi struct Vec<T: ?Sized + ActuallySized> , untuk nama yang tidak terlalu konyol, di mana pada dasarnya Sized menjadi alias untuk ActuallySized + Move ; kemudian tambahkan Sized terikat ke metode yang dapat menyebabkan realokasi, dan metode untuk melakukan transmutasi.

Ini juga akan diperlukan untuk mengubah batasan pada tipe elemen tipe slice bawaan dari Sized menjadi ActuallySized . Memikirkan hal ini memang mengingatkan saya pada kecanggungan mengubah Sized menjadi sesuatu selain Sized , tetapi di sisi lain, masih benar bahwa sebagian besar Sized batas dalam kode yang ada secara inheren membutuhkan Move . Perlu mengetahui size_of::<T>() untuk mengindeks menjadi sepotong adalah pengecualian…

Tentu! Keuntungan besar dari versi Pinned adalah Anda tidak memerlukan sifat yang berbeda untuk referensi yang disematkan dapat berubah. Namun, ini lebih buruk atau netral daripada PinMut dengan deref untuk hampir setiap skenario lainnya.

Ini juga memiliki keuntungan untuk menghindari konflik dengan RefCell , yang diakui dengan mengorbankan hal-hal lain ( Sized bounds).

Aksesor manual yang membutuhkan kode yang tidak aman untuk diterapkan terdengar seperti ide yang buruk bagi saya; Saya tidak melihat bagaimana proposal seperti itu akan memungkinkan pembuatan pengakses menjadi aman (bagaimana Anda menghentikan seseorang dari menyediakan pengakses yang tidak disematkan tanpa membuat mereka menulis tidak aman untuk menyatakan bahwa mereka tidak akan melakukannya?).

Karena pengakses diimplikasikan pada Pinned<MyStruct> , bukan MyStruct secara langsung. Jika Anda memiliki &mut MyStruct , Anda selalu dapat mengakses bidang secara manual untuk mendapatkan &mut MyField , tetapi Anda masih dalam keadaan dapat dipindahkan. Jika Anda memiliki &mut Pinned<MyStruct> , Anda tidak bisa mendapatkan &mut MyStruct (dengan asumsi bahwa MyStruct adalah !Unpin atau Unpin tidak ada), jadi Anda harus menggunakan pengakses untuk pergi ke ladang. Pengakses mengambil &mut Pinned<MyStruct> (yaitu dibutuhkan &mut self dan diterapkan pada Pinned<MyStruct> ) dan memberi Anda &mut Pinned<MyField> atau &mut MyField , tergantung pada opsi mana yang Anda pilih. Tetapi Anda hanya dapat memiliki satu jenis pengakses atau yang lain, karena invarian kritisnya adalah Anda tidak boleh mendapatkan &mut Pinned<MyField> , menulis padanya, melepaskan pinjaman, dan kemudian mendapatkan &mut MyField (dan pindahkan).

Ada alasan yang cukup bagus untuk melakukan ini (seperti: tipe tersemat Anda memiliki Vec di dalamnya). Koleksi yang mengganggu akan sangat sering mengalami skenario semacam ini. Saya pikir proposal apa pun yang didasarkan pada gagasan bahwa orang tidak akan pernah menginginkan referensi Pinned ke penampung yang ada, atau bahwa Anda harus memilih untuk membuat Unpin berfungsi, kemungkinan besar tidak akan berfungsi dengan baik. Tidak dapat pada dasarnya memilih ekosistem reguler Rust dengan menambahkan Unpin terikat akan sangat mengganggu (pada kenyataannya, hampir setiap kasus penggunaan yang saya miliki untuk tipe yang tidak dapat dipindahkan akan menjadi jauh lebih sulit).

Saya tidak sepenuhnya mengerti apa yang Anda maksud di sini, tetapi itu mungkin karena Anda bereaksi terhadap kesalahan saya sendiri, wrt Unpin versus Move :)

Sekarang saya sudah dikoreksi ... Jika Unpin ada, maka Vec harus mengimplikasikannya. Tapi seandainya itu tidak ada, apa sebenarnya skenario yang Anda maksud?

Untuk struct yang memiliki Vec sebagai salah satu bidangnya, saya menjelaskan di atas bagaimana Anda akan mendapatkan referensi yang tidak disematkan ke bidang (dengan biaya tidak bisa mendapatkan referensi yang disematkan ke sana, yang mana baik-baik saja).

Saya kira ini akan menjadi masalah jika Anda menginginkan struct generik dengan bidang yang mungkin berisi Vec , atau mungkin berisi tipe yang tidak dapat dipindahkan, tergantung pada parameter tipe. Namun, mungkin ada cara berbeda untuk menyelesaikan ini tanpa memerlukan sifat Unpin yang semuanya harus dipikirkan apakah akan diterapkan.

@comex

Sebagai alternatif, bagaimana kalau memungkinkan transmisi dari Vecke Vec>, yang mana yang memiliki sebagian besar Vec API tetapi mengabaikan metode yang dapat menyebabkan realokasi?

Karena...

Artinya, ubah definisi Vec dari struct Vec saat iniuntuk menyusun Vec, untuk beberapa nama yang tidak terlalu konyol, di mana pada dasarnya Sized menjadi alias untuk ActuallySized + Move; kemudian tambahkan Sized terikat ke metode yang dapat menyebabkan realokasi, dan metode untuk melakukan transmisi.

... kedengarannya sangat, sangat rumit dan tidak benar-benar melakukan apa yang Anda inginkan (yaitu mendapatkan Vec<T> reguler dari &mut Pinned<Vec<T>> atau apa pun). Ini adalah semacam keren yang memungkinkan Anda pin vektor setelah fakta, sehingga Anda mendapatkan yang bagus analog ke Box<Pinned<T>> , tapi itu sebuah keprihatinan orthogonal; itu hanyalah ilustrasi lain dari fakta bahwa menyematkan menjadi properti dari tipe yang dimiliki mungkin benar. Saya pikir segala sesuatu tentang Unpin hampir sepenuhnya tidak berhubungan dengan pertanyaan tentang bagaimana tipe referensi dibangun.

Sekarang saya dikoreksi ... Jika Unpin ada, maka Vec harus mengimplikasikannya. Tapi seandainya itu tidak ada, apa sebenarnya skenario yang Anda maksud?

Saya hanya akan menanggapi ini karena saya pikir itu akan mengilustrasikan poin saya: Saya bahkan tidak dapat mengubah bidang i32 melalui PinMut tanpa Unpin . Pada titik tertentu jika Anda ingin melakukan sesuatu dengan struktur Anda, Anda sering harus memindahkan sesuatu di dalamnya (kecuali jika benar-benar tidak dapat diubah). Membutuhkan orang untuk secara eksplisit mengimplementasikan pengakses bidang pada Pinned<MyType> tampaknya sangat mengganggu, terutama jika bidang yang dimaksud selalu aman untuk dipindahkan. Pendekatan ini juga sepertinya akan sangat membingungkan untuk digunakan dengan tipe Pinned bawaan karena proyeksi legal entah bagaimana akan bervariasi menurut bidang dengan cara yang tidak bergantung pada jenis bidang, sesuatu yang sudah ditolak di Rust ketika mut field telah dihapus (dan IMO, jika kita akan menambahkan penjelasan seperti itu unsafe adalah pilihan yang lebih baik, karena field yang tidak aman adalah sebuah footgun yang besar dalam prakteknya). Karena tipe tersemat bawaan hanya tentang satu-satunya cara untuk membuat enum tersemat bagus untuk digunakan, saya sangat peduli tentang mereka agar bisa agak konsisten dengan bahasa lainnya.

Tapi yang lebih penting ...

Saya kira ini akan menjadi masalah jika Anda menginginkan struct generik dengan bidang yang mungkin berisi Vec, atau mungkin berisi tipe yang tidak dapat dipindahkan, tergantung pada parameter tipe

Itu cukup banyak kasus penggunaan pembunuh untuk Unpin , dan (bagi saya) fakta bahwa itu benar-benar tampaknya berfungsi cukup fantastis dan memvalidasi seluruh model penyematan (ke titik yang saya pikir bahkan jika kita mulai dari sebelum Rust 1.0 kita mungkin ingin menyimpan Unpin apa adanya). Parameter umum yang hidup sebaris dalam struktur juga merupakan satu-satunya waktu yang Anda perlukan untuk terikat dengan Unpin jika rencana saat ini (untuk membuat hampir semua jenis referensi yang aman menerapkan Unpin tanpa syarat) pergi melalui.

Tetapi yang sebenarnya tidak saya dapatkan adalah: mengapa Anda ingin menghapus Unpin ? Menghilangkannya pada dasarnya tidak memberi Anda apa-apa; semua hal baik yang Anda dapatkan dari Pinned lebih dari PinMut (atau sebaliknya) hampir tidak berhubungan dengan keberadaannya. Menambahkannya membuat Anda mudah kompatibilitas dengan ekosistem Rust lainnya. FWIW, Unpin dan implementasi tanpa syarat dari deref pada Pin cukup banyak tidak berhubungan satu sama lain, kecuali bahwa tanpa Unpin semua tipe merasakan hal yang sama rasa sakit yang dilakukan tipe !Unpin (artinya mungkin akan membuat implementasi deref tanpa syarat lebih berguna). Saya tidak bisa membantu tetapi merasa seperti saya melewatkan sesuatu.

Apakah bidang lain dalam struktur yang dihapus dianggap variabel lokal dalam konteks ini? Itu benar-benar tidak jelas bagi saya dari dokumentasi yang dihadapi pengguna

Cukup adil, saya membuka https://github.com/rust-lang/rust/issues/50765 untuk melacak ini.


@pencinta

Secara khusus, saya menemukan contoh RefCell cukup mengganggu karena dengan adanya Pinned :: deref itu berarti bahwa kita bahkan tidak dapat memaksakan pinning secara dinamis dengan tipe yang ada (saya tidak tahu apakah spesialisasi akan cukup). Hal ini lebih lanjut menyarankan bahwa jika kita tetap menerapkan deref, kita akan harus menggandakan permukaan API hampir sebanyak yang kita lakukan dengan Pin; dan jika kami tidak menyimpannya, Pinmenjadi sangat sulit digunakan.

Solusi untuk RefCell adalah memberikan metode tambahan borrow_pin dan borrow_pin_mut (mengambil Pin<RefCell<T>> ), dan melacak status yang disematkan dari bagian dalam RefCell saat run-time. Ini harus bekerja untuk PinMut dan Pinned . Jadi, apakah argumen Anda di sini bahwa Pinned tidak membantu? Seharusnya tidak memperburuk keadaan untuk RefCell juga.

Tapi kemudian Anda menulis

perbedaannya adalah bahwa kita masih dapat menggabungkan solusi dengan PinMut sambil mendukung deref, yang tampaknya tidak berfungsi dengan Pinned.

dan saya tidak tahu apa yang Anda maksud, mengapa ini tidak berhasil dengan Pinned ?

@comex

Menurut saya Pinned :: deref seharusnya tidak ada. Pengakses bidang yang dihasilkan makro yang aman harus cukup; Saya tidak melihat bagaimana itu "sangat sulit digunakan".

Saya tidak melihat hubungan antara deref dan pengakses bidang. Tetapi saya juga tidak melihat bagaimana deref menjadi lebih bermasalah dengan Pinned<T> , jadi saya kira saya akan menunggu jawaban atas pertanyaan saya di atas terlebih dahulu.

Sama seperti @pythonesque, menurut saya, melacak status tersemat pada tipe di belakang referensi ("pada tipe yang dimiliki") pada dasarnya lebih tepat. Namun, saya ragu ini benar-benar dapat diubah menjadi API keseluruhan yang lebih ergonomis, khususnya mengingat kendala bekerja dengan ekosistem Rust yang ada.

Jika kita akan dengan sengaja menggunakan pendekatan yang menurut kita kurang "benar secara fundamental", setidaknya kita harus menyisihkan banyak waktu untuk bereksperimen sebelum menstabilkannya, sehingga kita bisa seyakin mungkin bahwa kita tidak akan berakhir dengan menyesalinya.

@pythonesque , wow, terima kasih banyak atas ringkasannya yang ekstensif! Senang mendengar bahwa ada RFC yang sedang dikerjakan. :)

Sebaiknya panggil fungsi yang mengambil referensi yang bisa berubah ke nilai yang disematkan selama fungsi itu tidak menggunakan sesuatu seperti mem::swap atau mem::replace . Karena itu terasa lebih alami untuk memiliki fungsi ini menggunakan Unpin terikat daripada membuat setiap deref yang bisa berubah dari Pin menjadi Unpin nilai menjadi tidak aman.

Jika suatu fungsi nantinya akan diperbarui untuk menggunakan swap maka tidak lagi aman untuk memanggilnya pada referensi yang bisa berubah ke nilai yang disematkan. Ketika swap dan replace memiliki ikatan ini, fungsi yang diperbarui juga harus melakukannya dengan membuatnya lebih jelas bahwa ini bukan perubahan yang kompatibel ke belakang.

Jadi beberapa pemikiran yang saya miliki:

  1. Pada dasarnya, Drop memberikan hak yang sama seperti Unpin - Anda bisa mendapatkan &mut untuk sesuatu yang sebelumnya ada di PinMut . Dan Drop aman, yang berarti Unpin seharusnya aman (ini adalah mem::forget dan bocor lagi).
  2. Itu bagus, karena itu berarti bahwa hal-hal seperti API berbasis masa depan saat ini, yang tidak menangani generator pelepas pin, semuanya 100% aman untuk diterapkan bahkan jika mereka mengambil self: PinMut<Self> (tidak ada unsafe impl Unpin ).
  3. Apakah suara API jika Unpin aman? Jawabannya adalah ya: selama generator tidak mengimplementasikan Unpin , dan tidak aman untuk menyematkan proyek ke tipe !Unpin , semuanya aman.
  4. Tetapi ini berarti proyeksi pin tidak aman! Tidak ideal.
  5. Proyeksi pin aman jika Self tidak mengimplementasikan Unpin atau Drop [edit: true or false?] Bisakah kita mengotomatiskan pemeriksaan itu?

Saya punya beberapa ide untuk alternatif yang lebih didukung bahasa untuk API perpustakaan ini, yang melibatkan menjatuhkan Unpin seluruhnya dan sebaliknya membalik polaritas - sifat Pin yang Anda pilih untuk mendapatkan jaminan ini, alih-alih memilih keluar . Tetapi ini akan membutuhkan dukungan bahasa yang signifikan, sedangkan implementasi saat ini sepenuhnya berbasis perpustakaan. Saya akan membuat posting lain setelah saya memikirkannya lebih dalam.


catatan lain karena saya terus lupa:

Keamanan proyeksi pin hanya bergantung pada tipe Self, bukan tipe field, karena tipe field harus menjamin keamanan API publiknya, yang tidak bersyarat. Jadi ini bukan pemeriksaan rekursif - jika Self tidak pernah memindahkan apa pun dari Pin , memproyeksikan pin ke bidang, semua jenis aman.

@withoutboats FWIW ini sama persis dengan kesimpulan yang saya dan @cramertj capai di putaran diskusi sebelumnya. Dan saya yakin kita dapat mengotomatiskan pemeriksaan pengecualian timbal balik, awalnya melalui beberapa atribut yang dibuat khusus yang dipancarkan oleh turunan.

@tokopedia

Pada dasarnya, Drop memberikan hak istimewa yang sama seperti Lepas Pin - Anda bisa mendapatkan & mut ke sesuatu yang sebelumnya ada di PinMut. Dan Jatuhkan aman, yang berarti Lepas pin harus aman (ini adalah mem :: lupa dan kebocoranpocalypse lagi).

Saya tidak melihat koneksi ke kebocoranpocalypse tetapi setuju sebaliknya. Satu-satunya alasan saya (dulu?) Agak ragu-ragu adalah selama itu hanya Drop , ini terasa lebih seperti kasus sudut bagi saya yang tidak perlu dipedulikan banyak orang. Tidak yakin apakah itu keuntungan atau tidak. Dan bagaimanapun, brankas Unpin tidak hanya meningkatkan konsistensi di sini dan "memecahkan" masalah Drop dengan tidak lagi menjadikannya kasus khusus (sebagai gantinya kita dapat menganggap setiap impl Drop sebagai datang dengan implisit impl Unpin ); dari apa yang Anda katakan itu juga membuatnya lebih mudah untuk digunakan pada sisi Future . Jadi, sepertinya ini adalah kemenangan keseluruhan.

@pythonesque kecuali saya melewatkan sesuatu, aman Unpin juga tidak menyebabkan masalah baru untuk koleksi yang mengganggu, bukan? Jika mereka bekerja meskipun aman Drop , mereka masih harus bekerja.


@tokopedia

Proyeksi pin aman jika Self tidak mengimplementasikan Unpin atau Drop [edit: true or false?] Bisakah kita mengotomatiskan pemeriksaan itu?

Anda awalnya juga menyebutkan jenis bidang di sini dan saya akan bertanya mengapa menurut Anda itu relevan. Sekarang saya melihat Anda mengedit posting. :) Saya setuju bahwa ini tentang tipe Self saja. Berikut ini saya cukup mengulangi argumen Anda dalam istilah saya.

Pada dasarnya, pertanyaannya adalah: Bagaimana Self memilih invarian penyematannya? Secara default, kami berasumsi (meskipun ada kode yang tidak aman!) Bahwa invarian penyematan sama persis dengan invarian yang dimiliki, yaitu, invarian tersebut tidak bergantung pada lokasi dan jenis ini tidak melakukan pemasangan pin. Selama saya tidak bisa melakukan apa pun dengan PinMut<T> selain mengubahnya menjadi &mut T , itu asumsi yang aman.

Untuk mengaktifkan proyeksi bidang, invarian penyematan seharusnya menjadi "semua bidang saya disematkan pada invarian jenisnya masing-masing". Invarian itu dengan mudah membenarkan proyeksi pinning, terlepas dari jenis bidangnya (yaitu, mereka dapat memilih invarian pin apa pun yang mereka inginkan). Tentu saja invarian ini tidak cocok dengan mengubah PinMut<T> menjadi &mut T , jadi sebaiknya kita pastikan jenis tersebut bukan Drop atau Unpin .

Saya tidak melihat koneksi ke kebocoranpocalypse tetapi setuju sebaliknya.

hanya sebuah analogi - Lepas pin adalah untuk Jatuhkan sebagai mem :: forget is to Rc cycles. mem :: forget pada awalnya ditandai tidak aman, tetapi tidak ada alasan untuk itu. (Dan argumen yang sama bahwa siklus Rc adalah kasus tepi dibuat melawan penandaan mem :: forget safe.)

Menyalin-menempel (secara spiritual) dari Discord, saya benar-benar ingin melihat bukti bahwa kita tidak hanya membalik masalah: membuat Unpin aman untuk diimplementasikan, dengan membuat pengakses pin struktural tidak aman untuk diterapkan (ini juga akan benar dengan sifat tidak aman yang diperkenalkan, bukan? Anda masih harus menulis kode tidak aman). Ini mengganggu saya karena sebagian besar waktu mereka benar-benar aman - pada dasarnya, selama tidak ada implan Unpin eksplisit untuk tipe tersebut, sama seperti kita selalu aman jika tidak ada impl Drop untuk tipe tersebut. Dengan rencana saat ini, kita membutuhkan sesuatu yang lebih kuat - harus ada implisit eksplisit! Lepaskan pin untuk tipe - yang akan berlaku untuk lebih sedikit tipe (hanya itu yang bisa kita lakukan di perpustakaan).

Sayangnya saya tidak tahu bagaimana atau apakah kompilator dapat memeriksa apakah ada impl manual Unpin untuk tipe tertentu atau tidak, sebagai lawan dari "has any impl", dan saya tidak yakin apakah itu memiliki interaksi yang buruk dengan spesialisasi. Jika kita memiliki beberapa cara pasti untuk melakukan pemeriksaan itu, sehingga pembuat tipe tidak perlu menulis kode yang tidak aman untuk mendapatkan penyematan struktural, saya akan jauh lebih senang dengan impl Unpin aman, saya rasa ... apakah itu sesuatu yang kelihatannya mungkin?

Saya punya pertanyaan sederhana yang, saya coba pahami sekarang. Dalam generik, akankah melepas pin menjadi ukuran seperti ikatan implisit kecuali API Anda untuk semua parameter generik?

itu harus benar agar vecuntuk tetap aman.

akan melepaskan pin seperti ukuran terikat implisit

Tidak.

itu harus benar agar vec terus aman.

Kenapa menurutmu begitu?

Hal menjengkelkan yang dicapai @MajorBreakfast hari ini: PinMut::get_mut memberi Anda &mut dengan masa pakai PinMut , tetapi tidak ada cara yang aman untuk melakukan hal yang sama, karena DerefMut memberi Anda pinjaman dengan masa pakai referensi yang bisa berubah menjadi PinMut . Mungkin kita harus membuat get_mut yang aman, dan menambahkan get_mut_unchecked ?

Maksudmu seperti ini?

fn get_mut(this: PinMut<'a, T>) -> &'a mut T where T : Unpin

Ya, kita harus memilikinya.

@Ralfarum

Metode dari peti berjangka di mana saya ingin menggunakan ini terlihat seperti ini:

fn next(&mut self) -> Option<&'a mut F> {
    self.0.next().map(|f| unsafe { PinMut::get_mut(f) })
}

Saya harus menggunakan blok yang tidak aman meskipun F memiliki Unpin terikat dan itu sepenuhnya aman. Saat ini tidak ada cara aman yang menghasilkan referensi yang mempertahankan masa pakai PinMut .

Ya, itu hanya kelalaian di API.

Apakah Anda ingin menyiapkan PR atau haruskah saya?

Aku bisa melakukan itu. Apakah kita ingin menyebutnya get_mut dan get_mut_unchecked ?

Saya akan menambahkan brankas map dan mengganti nama yang sekarang menjadi map_unchecked juga. Terutama untuk konsistensi. Kemudian semua fungsi yang tidak aman dari PinMut diakhiri dengan _unchecked dan memiliki persamaan yang aman

Apakah kita ingin menyebutnya get_mut dan get_mut_unchecked?

Kedengarannya masuk akal.

Saya akan menambahkan brankas map

Hati-hati disana. Anda ingin terlihat seperti apa? Alasan peta tidak aman adalah karena kita tidak tahu cara membuatnya menjadi aman.

Hati-hati disana. Anda ingin terlihat seperti apa?

Kamu benar. Diperlukan Unpin terikat pada nilai kembali.

Saya baru saja mendapat ide: Bagaimana dengan PinArc ? Kita bisa menggunakan hal seperti itu di BiLock impl (https://github.com/rust-lang-nursery/futures-rs/pull/1044) di peti berjangka.

@MajorSarapan PinArc (dan PinRc , dll) tergantung pada Pin (versi non- Mut ). Saya siap untuk menambahkannya, tetapi saya tidak yakin apakah ada konsensus tentang jaminan apa yang akan ditawarkannya.

Saya tidak yakin lagi apakah menambahkan PinArc menambahkan sesuatu ^^ ' Arc sudah tidak mengizinkan "keluar dari konten pinjaman"

@MajorSarapan Anda dapat keluar dari Arc menggunakan Arc::try_unwrap . Ini akan menjadi perubahan yang tidak kompatibel ke belakang untuk menghapusnya atau memperkenalkan T: Unpin terikat.

Hai!
Saya telah melakukan sedikit pemikiran tentang bagaimana agar pengakses pin bekerja dengan aman. Sejauh yang saya mengerti, masalah dengan pengakses pin terjadi _only_ bila Anda memiliki kombinasi T: !Unpin + Drop . Untuk itu, saya melihat beberapa diskusi mencoba untuk mencegah kemungkinan jenis T ada - misalnya membuat ciri !Unpin dan Drop saling eksklusif dalam berbagai cara, tetapi ada bukanlah cara yang jelas untuk melakukan ini tanpa merusak kompatibilitas ke belakang. Melihat masalah ini lebih dekat, kita bisa mendapatkan pengakses Pin yang aman tanpa mencegah suatu jenis menjadi !Unpin dan Drop , hanya saja jenis seperti itu tidak dapat dimasukkan ke dalam Pin<T> . Mencegah _that_ terjadi adalah sesuatu yang saya yakini jauh lebih bisa dilakukan, dan lebih dalam semangat bagaimana sifat Unpin bekerja.

Saat ini, saya memiliki "solusi" untuk mencegah suatu jenis memasukkan dengan aman Pin<T> yaitu !Unpin dan Drop . Ini semacam membutuhkan fitur yang belum kami miliki di karat, yang merupakan masalah, tapi saya berharap ini dimulai. Pokoknya ini kodenya ...

/// This is an empty trait, it is used solely in the bounds of `Pin`
unsafe trait Pinnable {}

/// Add the extra trait bounds here, and add it to the various impls of Pin as well
struct Pin<T: Pinnable> {
    ...
}

/// Then we impl Pinnable for all the types we want to be able to put into pins
unsafe impl<T: Unpin> Pinnable for T {}
unsafe impl<T: !Unpin + !Drop> Pinnable for T {}

Baik tipe Unpin , dan tipe yang !Unpin tetapi juga !Drop , tidak memiliki masalah (sepengetahuan saya) dengan pengakses pin. Ini tidak merusak kode yang ada yang menggunakan Drop , ini hanya membatasi apa yang dapat dimasukkan ke dalam Pin struct. Dalam kasus yang tidak mungkin * di mana seseorang membutuhkan tipe untuk menjadi !Unpin dan Drop (dan dapat benar-benar ditempatkan di dalam Pin ), mereka akan dapat unsafe impl Pinnable untuk tipenya.

* Saya tidak memiliki pengalaman dengan Pin , tetapi saya berharap bahwa sebagian besar kasus di mana seseorang perlu impl Drop untuk jenis yang !Unpin tidak disebabkan oleh hal yang perlu dihapus adalah inheren !Unpin , tetapi lebih disebabkan oleh sebuah struct yang memiliki bidang Drop , dan bidang !Unpin . Dalam kasus ini, bidang Drop dapat dipisahkan menjadi struct mereka sendiri yang tidak berisi bidang !Unpin . Saya dapat menjelaskan lebih detail tentang itu jika diperlukan, karena saya merasa penjelasan itu tidak tepat, tetapi ini tidak dimaksudkan untuk menjadi bagian utama dari komentar jadi saya akan membiarkannya singkat untuk saat ini.

Sejauh yang saya mengerti, masalah dengan pengakses pin hanya terjadi jika Anda memiliki kombinasi T:! Unpin + Drop.

Ini lebih dari "atau" daripada "dan".

Pengakses pin menyiratkan interpretasi "struktural" dari pemasangan pin: Ketika T disematkan, begitu juga semua bidangnya. impl Unpin dan Drop , di sisi lain, aman karena mereka menganggap tipe tidak peduli tentang penyematan sama sekali - T disematkan atau tidak ada bedanya; khususnya, bidang T tidak disematkan dalam kedua kasus.

Namun, Anda mendapatkannya nanti ketika Anda mengatakan bahwa tipe !Unpin + !Drop adalah jenis yang pengaksesnya aman untuk ditambahkan. (Jenisnya masih harus berhati-hati untuk tidak melakukan kesalahan pada kode yang tidak aman , tetapi Unpin ad Drop adalah dua cara aman untuk memutus aksesor pin.)

Melihat masalah ini lebih dekat, kita bisa mendapatkan pengakses Pin yang aman tanpa mencegah suatu jenis menjadi! Lepas Pin dan Jatuhkan, hanya saja jenis seperti itu tidak dapat dimasukkan ke dalam Pin.

Saya tidak mengikuti. Menurut Anda, mengapa itu cukup? Dan jika ya, mengapa kita bahkan tertarik dengan pengakses yang disematkan jika suatu jenis tidak dapat disematkan ...?

Ini lebih dari "atau" daripada "dan".

Mari kita bicarakan hal ini untuk saat ini, karena seluruh ide saya didasarkan pada kebalikan dari ini jadi jika saya salah paham maka sisa ide tidak membantu.

Menurut pemahaman saya (saat ini), pengakses pin dengan tipe Unpin benar-benar bagus, terlepas dari penurunannya, karena Pin<T> secara efektif &mut T . Juga, aksesor pin pada tipe !Drop benar-benar bagus, bahkan untuk tipe !Unpin , karena drop adalah satu-satunya fungsi yang bisa mendapatkan &mut self pada tipe !Unpin setelah memasukkan Pin , jadi tidak ada yang bisa memindahkan semuanya.

Jika saya telah membuat kesalahan dalam hal itu, beri tahu saya. Tetapi jika tidak, maka tipe Unpin + Drop atau !Unpin + !Drop akan baik-baik saja dengan pengakses pin, dan satu-satunya tipe masalah adalah tipe-tipe yang !Unpin + Drop .

@tokopedia

Selain itu, aksesor pin pada jenis yang! Jatuhkan sepenuhnya terdengar, bahkan untuk jenis! Lepas pin, karena drop adalah satu-satunya fungsi yang dapat & mematikan diri sendiri pada jenis! Lepas pin setelah memasukkan Pin, jadi tidak ada yang dapat melakukannya memindahkan semuanya.

Jika saya menulis struct Foo(InnerNotUnpin); impl Unpin for Foo {} maka tidak masuk akal untuk beralih dari PinMut<Foo> menjadi PinMut<InnerNotUnpin> . Agar proyeksinya bagus, Anda harus (a) melarang drop impls dan (b) memastikan bahwa strukturnya adalah Unpin hanya jika field-nya Unpin .

(a) sebaiknya melarang penurunan implisit pada hal-hal yang !Unpin kan?
(b) harus ditangani oleh fakta bahwa penerapan Unpin tidak aman - yakin itu mungkin untuk menembak diri sendiri di kaki dengan tidak aman mengklaim bahwa tipe dapat melepaskan pin ketika itu benar-benar tidak bisa - tapi itu hanya mungkin untuk label sesuatu sebagai Sync padahal seharusnya tidak dan berakhir dengan perilaku tidak sehat seperti itu

(a) seharusnya hanya melarang penurunan implisit pada hal-hal yang ada! Lepas pin kan?

Ya tapi itu tidak mungkin karena alasan kompatibilitas ke belakang.

Dan itu sampai pada poin saya - melarang penurunan berarti pada hal-hal yang ada! Lepas pin tidak mungkin karena alasan kompatibilitas mundur. Tetapi kita tidak perlu mencegah penurunan implan pada hal-hal yang ada! Lepas pin agar tidak terjadi. Penanda! Lepas pin pada suatu jenis tidak memiliki arti dan tidak membuat janji sampai jenis tersebut berada di dalam Pin, jadi penurunan impl pada jenis! Lepas pin sepenuhnya terdengar _ selama jenis tersebut tidak berada di dalam Pin_. Yang saya usulkan adalah kita mengubah Pin dari Pin<T> menjadi Pin<T: Pinnable> mana sifat Pinnable bertindak sebagai penjaga gerbang untuk mencegah jenis yang Drop + !Unpin (atau lebih umumnya, tipe yang pengakses pinnya bermasalah) ditempatkan di dalam Pin .

Pada saat itu Anda membuat sifat Unpin sama sekali tidak berguna, bukan? Selain itu, Anda masih tidak dapat mengungkapkan batasan yang diperlukan dengan Rust saat ini. Jika kita bisa mengatakan " Unpin dan Drop tidak kompatibel", tidak akan ada masalah. Anda tampaknya membutuhkan sesuatu yang serupa dengan Pinnable . Saya tidak melihat bagaimana ini membantu.

Sifat Lepas Pin - dengan sendirinya - sudah “tidak berguna”. Sifat _only_ berarti sesuatu setelah jenis telah disematkan, jenis dapat dipindahkan sebelum masuk ke dalam Pin.

@RalfJung dan saya baru saja mengadakan pertemuan untuk membicarakan tentang API ini, dan kami sepakat bahwa kami siap untuk menstabilkannya tanpa perubahan API yang besar.

Resolusi untuk pertanyaan yang belum terjawab

Keyakinan saya tentang API pin adalah:

  1. Konsep inti API PinMut dan Unpin membuat solusi terbaik yang tidak melibatkan dukungan bahasa bawaan.
  2. API ini tidak menghalangi dukungan bahasa bawaan di masa mendatang jika kami memutuskan itu diperlukan.

Namun, ada beberapa pertanyaan luar biasa seputar batasan dan trade off mereka, yang ingin saya catat keputusan kami.

Masalah Drop

Satu masalah yang tidak kami temukan sampai setelah kami menggabungkan RFC adalah masalah sifat Drop . Metode Drop::drop memiliki API yang mengambil &mut self . Ini pada dasarnya adalah "melepas" Self , melanggar jaminan yang seharusnya disediakan oleh jenis yang disematkan.

Untungnya, ini tidak mempengaruhi tingkat kesehatan dari jenis inti "generator tidak bergerak" yang kami coba proyeksikan dengan pin: pin tersebut tidak menerapkan destruktor, jadi penerapan !Unpin benar-benar berarti .

Namun, ini berarti bahwa untuk jenis yang Anda tentukan sendiri, sangatlah aman untuk "melepaskan" mereka, yang harus Anda lakukan adalah mengimplementasikan Drop . Artinya adalah bahwa Unpin menjadi unsafe trait tidak memberi kami keamanan tambahan: menerapkan Drop sama baiknya dengan menerapkan Unpin sejauh kesehatannya prihatin.

Untuk alasan ini, kami telah membuat sifat Unpin sifat yang aman untuk diterapkan.

Proyeksi pin

Batasan utama dengan API saat ini adalah bahwa tidak mungkin untuk secara otomatis menentukan bahwa melakukan proyeksi pin aman untuk melakukan proyeksi pin ketika bidang tidak mengimplementasikan Unpin . Proyeksi pin adalah:

Diberikan tipe T dengan bidang a dengan tipe U , saya dapat dengan aman "memproyeksikan" dari PinMut<'a, T> menjadi PinMut<'a, U> dengan mengakses bidang a .

Atau, dalam kode:

struct Foo {
    bar: Bar,
}

impl Foo {
    fn bar(self: PinMut<'_, Foo>) -> PinMut<'_, Bar> {
        unsafe { PinMut::map_unchecked(self, |foo| &mut foo.bar) }
    }
}

Sampai sekarang, kecuali tipe field mengimplementasikan Unpin kita tidak perlu untuk rustc untuk _prove_ bahwa ini aman. Artinya, menerapkan metode proyeksi semacam ini saat ini memerlukan kode yang tidak aman. Untungnya, Anda dapat membuktikan bahwa itu aman. Jika jenis bidang tidak dapat mengimplementasikan Unpin , ini hanya aman jika semua penangguhan ini:

  1. Tipe Self tidak mengimplementasikan Unpin , atau hanya mengimplementasikan Unpin kondisional ketika tipe field melakukannya.
  2. Tipe Self tidak mengimplementasikan Drop , atau destruktor untuk tipe Self tidak pernah memindahkan apapun dari field itu.

Dengan ekstensi bahasa, kita dapat meminta kompilator memeriksa bahwa beberapa kondisi ini berlaku (misalnya Self tidak mengimplementasikan Drop dan hanya mengimplementasikan Unpin bersyarat).

Karena masalah Drop , sifat yang stabil, masalah ini sudah ada sebelum keputusan apa pun yang dapat kami buat tentang masalah ini, dan tidak ada cara tanpa perubahan bahasa untuk membuat proyeksi pin secara otomatis aman. Suatu hari nanti, kami dapat membuat perubahan tersebut, tetapi kami tidak akan memblokir stabilisasi API ini.

Menentukan jaminan "pin"

Di akhir proses RFC, @cramertj menyadari bahwa dengan sedikit ekstensi di luar yang kami butuhkan untuk generator yang tidak dapat dipindahkan, penyematan dapat digunakan untuk merangkum jaminan daftar yang mengganggu dengan aman. Ralf menulis deskripsi ekstensi ini di sini .

Pada dasarnya, jaminan pemasangan pin adalah:

Jika Anda memiliki PinMut<T> , dan T tidak mengimplementasikan Unpin , T tidak akan dipindahkan dan memori yang mendukung T tidak akan tidak valid sampai setelah destruktor berjalan sebesar T .

Ini tidak persis sama dengan "kebebasan bocor" - T masih dapat bocor misalnya dengan terjebak di belakang siklus Rc, tetapi meskipun bocor, itu berarti memori tidak akan pernah tidak valid (hingga program berakhir), karena destruktor tidak akan pernah berjalan.

Jaminan ini menghalangi API tertentu untuk pemasangan pin tumpukan, jadi pada awalnya kami tidak yakin tentang memperpanjang pemasangan pin untuk menyertakannya. Namun, pada akhirnya kami harus menggunakan jaminan ini karena beberapa alasan:

  1. API lain, yang bahkan lebih ergonomis, stack pinning telah ditemukan, seperti makro ini , menghilangkan sisi negatifnya.
  2. Keuntungannya cukup besar! Jenis daftar yang mengganggu ini bisa sangat berdampak pada pustaka seperti josephine yang mengintegrasikan Rust dengan bahasa pengumpulan sampah.
  3. Ralf tidak pernah yakin bahwa beberapa API ini benar-benar bagus untuk memulai (terutama yang didasarkan pada "pinjaman-perma").

Reorganisasi API

Namun, saya ingin membuat satu usulan perubahan terakhir pada API, yaitu memindahkan banyak hal. Meninjau API yang ada, Ralf & saya menyadari bahwa tidak ada tempat yang tepat untuk menjelaskan jaminan tingkat tinggi dan invarian yang terkait dengan Drop dan seterusnya. Oleh karena itu, saya usulkan

  1. Kami membuat modul std::pin baru. Dokumen modul akan memberikan ikhtisar tingkat tinggi tentang pemasangan pin, jaminan yang diberikannya, dan pengguna lain dari bagian yang tidak aman diharapkan untuk ditegakkan.
  2. Kami memindahkan PinMut dan PinBox dari std::mem dan std::boxed ke dalam modul pin . Kami meninggalkan Unpin dalam modul std::marker , dengan ciri-ciri penanda lainnya.

Kita juga perlu benar-benar menulis dokumentasi yang lebih ekstensif itu sebelum kita selesai menstabilkan jenis ini.

Menambahkan Unpin implementasi

Ralf & Saya juga membahas menambahkan lebih banyak implementasi Unpin ke perpustakaan standar. Saat mengimplementasikan Unpin , selalu ada trade off: jika Unpin diimplementasikan tanpa syarat terkait dengan jenis field, Anda tidak dapat menyematkan proyek ke field itu. Oleh karena itu, sejauh ini kami belum terlalu agresif dalam menerapkan Unpin .

Namun, kami percaya bahwa sebagai aturan:

  1. Proyeksi pin melalui pointer tidak boleh dianggap aman (lihat Kotak catatan di bawah).
  2. Proyeksi pin melalui mutabilitas interior tidak boleh dianggap aman.

Secara umum, tipe yang melakukan salah satu dari hal-hal ini melakukan sesuatu (seperti mengalokasikan kembali penyimpanan pendukung seperti dalam Vec ) yang membuat proyeksi pin tidak dapat dipertahankan. Oleh karena itu, kami pikir akan tepat untuk menambahkan implementasi tanpa syarat dari Unpin ke semua tipe di std yang menyimpan parameter umum baik di belakang pointer atau di dalam UnsafeCell; ini termasuk semua std::collections misalnya.

Saya belum menyelidiki terlalu dekat, tetapi saya yakin itu akan cukup untuk sebagian besar jenis ini jika kita menambahkan impls ini:

impl<T: ?Sized> Unpin for *const T { }
impl<T: ?Sized> Unpin for *mut T { }
impl<T: ?Sized> Unpin for UnsafeCell<T> { }

Namun ada satu hal yang mengganggu: secara teknis, kami percaya bahwa pin yang diproyeksikan melalui Box dapat dibuat aman secara teknis: yaitu, beralih dari PinMut<Box<T>> menjadi PinMut<T> dapat dilakukan dengan aman. Ini karena Box adalah penunjuk yang dimiliki sepenuhnya yang tidak pernah mengalokasikan ulang dirinya sendiri.

Mungkin saja kami ingin memberikan lubang sehingga Unpin hanya diimplementasikan secara bersyarat untuk Box<T> ketika T: Unpin . Namun, pendapat pribadi saya adalah bahwa semestinya mungkin untuk menganggap pinning sebagai tentang blok memori yang telah disematkan di tempatnya, sehingga semua proyeksi melalui tipuan penunjuk seharusnya tidak aman.

Kami dapat menunda keputusan ini tanpa memblokir stabilisasi dan menambahkan implik ini seiring waktu.

Kesimpulan

Saya benar-benar ingin mendengar pemikiran orang lain yang telah sangat berinvestasi dalam API pin sejauh ini, dan apakah rencana stabilisasi ini terdengar masuk akal atau tidak. Saya akan membuat ringkasan singkat dengan proposal fcp di posting berikutnya, untuk tim bahasa lainnya.

@rachmandfc_gabung

Saya mengusulkan untuk menstabilkan API pin yang ada setelah beberapa reorganisasi kecil. Anda dapat membaca ringkasan yang lebih panjang di posting saya sebelumnya . TL; DR menurut saya adalah bahwa API saat ini adalah:

  • Suara
  • Sebagus itu tanpa perubahan bahasa
  • Maju kompatibel dengan perubahan bahasa yang akan membuatnya lebih baik

Kami telah menyelesaikan semua pertanyaan utama mengenai jaminan yang tepat dan ketidakberagaman dari pemasangan pin selama beberapa bulan terakhir, keputusan yang kami hadapi saat ini (yang menurut saya adalah keputusan yang harus kami stabilkan) didokumentasikan di pos yang lebih panjang.

Saya mengusulkan beberapa perubahan sebelum kami benar-benar menstabilkan berbagai hal:

  1. Atur ulang PinMut dan PinBox bawah modul baru std::pin , yang akan berisi dokumentasi tingkat tinggi mengenai invarian dan jaminan penyematan secara umum.
  2. Tambahkan implementasi tak bersyarat dari Unpin untuk tipe di std yang berisi parameter umum baik di belakang sebuah petunjuk arah atau di dalam UnsafeCell; alasan mengapa penerapan ini sesuai ada dalam ringkasan yang lebih panjang.

Anggota tim @withoutboats telah mengusulkan untuk menggabungkan ini. Langkah selanjutnya adalah meninjau oleh tim yang diberi tag lainnya:

  • [] @Kimundi
  • [] @SimonSapin
  • [] @alexcrichton
  • [] @dtolnay
  • [] @sfackler
  • [x] @withoutboats

Tidak ada masalah yang saat ini terdaftar.

Setelah mayoritas peninjau menyetujui (dan tidak ada yang menolak), ini akan memasuki periode komentar terakhir. Jika Anda melihat masalah besar yang belum pernah diangkat dalam proses ini, silakan angkat bicara!

Lihat dokumen ini untuk info tentang perintah yang dapat diberikan anggota tim kepada saya.

@rhm_hhhhhh batal

Saya memberi tag lang juga karena keputusan invarian inti yang harus kami buat, jadi saya membatalkan dan memulai kembali FCP

Proposal @woutboats dibatalkan.

@rfcbot fcp merge Lihat pesan gabungan sebelumnya dan ringkasan panjang, maaf!

Anggota tim @withoutboats telah mengusulkan untuk menggabungkan ini. Langkah selanjutnya adalah meninjau oleh tim yang diberi tag lainnya:

  • [x] @kimundi
  • [] @SimonSapin
  • [x] @alexcrichton
  • [] @aturon
  • [x] @cramertj
  • [x] @dtolnay
  • [x] @eddyb
  • [] @joshtriplett
  • [] @nikomatsakis
  • [x] @nrc
  • [] @pnkfelix
  • [x] @scottmcm
  • [] @sfackler
  • [x] @withoutboats

Kekhawatiran:

Setelah mayoritas peninjau menyetujui (dan tidak ada yang menolak), ini akan memasuki periode komentar terakhir. Jika Anda melihat masalah besar yang belum pernah diangkat dalam proses ini, silakan angkat bicara!

Lihat dokumen ini untuk info tentang perintah yang dapat diberikan anggota tim kepada saya.

Saya senang melihat keyakinan bahwa ini bagus. Namun, sulit untuk memahami semua yang berubah sejak RFC asli; dapatkah teks RFC diperbarui untuk mencerminkan desain yang diusulkan untuk stabilisasi? (Secara umum ini adalah masalah yang didapat banyak RFC, di mana mereka berubah begitu banyak selama periode implementasi sehingga mencoba memahaminya dengan membaca RFC adalah sia-sia. Kita harus menemukan cara untuk melakukannya lebih baik di sana.)

RFC bukanlah spesifikasi. Membaca RFC tidak menggantikan dokumentasi yang sebenarnya.

@bstrie perubahan signifikan dari RFC ( Unpin aman) tercakup dalam posting ringkasan saya. Dalam jangka panjang, orang harus mendapatkan pemahaman tentang API pinning dengan membaca dokumen di std::pin (pemblokir stabilisasi), bukan dari menggali RFC asli.

Jika maksudnya adalah dokumentasi memblokir stabilisasi, tetapi dokumentasinya belum ditulis, apakah FCP ini tidak prematur? Mengharapkan pemberi komentar potensial untuk merekonstruksi sendiri dokumentasi tentatif dengan menyatukan sumber-sumber yang berbeda adalah IMO penghalang masuk yang lebih tinggi daripada yang diinginkan.

@bstrie adalah prosedur standar kami untuk mengusulkan FCP sebelum dokumentasi. saya menulis komentar ringkasan yang sangat luas tentang keadaan permainan yang seharusnya memberikan informasi yang cukup bagi siapa saja yang tidak mengikuti masalah pelacakan, serta komentar yang lebih pendek untuk orang-orang yang tidak menginginkan banyak konteks

Untuk menjelaskan lebih lanjut, FCP adalah pertanyaan "apakah kita ingin menstabilkan ini?" Jika jawaban itu ternyata "tidak", maka banyak pekerjaan di dokumen telah terbuang percuma. Penyelesaian FCP tidak berarti bahwa fitur tersebut menjadi stabil secara instan; itu berarti bahwa keputusan telah dibuat untuk menstabilkannya, dan sekaranglah waktunya untuk melakukan pekerjaan yang diperlukan untuk melakukannya; yang mencakup pekerjaan kompilator dan dokumentasi.

Pada 2 Agustus 2018, pukul 12:56, boat [email protected] menulis:

@bstrie adalah prosedur standar kami untuk mengusulkan FCP sebelum dokumentasi. saya menulis komentar ringkasan yang sangat ekstensif tentang status permainan yang seharusnya memberikan informasi yang cukup bagi siapa saja yang tidak mengikuti masalah pelacakan

-
Anda menerima ini karena Anda berkomentar.
Balas email ini secara langsung, lihat di GitHub, atau nonaktifkan utasnya.

@tokopedia

Proyeksi pin melalui pointer tidak boleh dianggap aman (lihat Kotak catatan di bawah).
Proyeksi pin melalui mutabilitas interior tidak boleh dianggap aman.

Bisakah Anda menjelaskan hal ini sedikit? Apakah Anda secara khusus mengacu pada tipe di pustaka standar ? Mirip dengan Box , ada jenis seperti Mutex yang dapat memproyeksikan pin karena mereka memiliki konten yang unik (meskipun mungkin di luar band). Setidaknya di luar pustaka standar, saya berasumsi bahwa kita ingin kemampuan untuk memiliki konkurensi primitif yang memproyeksikan PinRef<InPlaceMux<T>> menjadi PinMut<T> pada panggilan ke .lock() , yang tampaknya seperti kasus "proyeksi melalui mutabilitas interior".

@cramertj Memproyeksikan ke Mutex tampaknya berbahaya; seseorang dapat menggunakan konversi yang aman ke &Mutex dan kemudian Mutex::lock untuk mendapatkan &mut dan kemudian memindahkan semuanya.

EDIT: Oh, maksud Anda Mutex yang selalu disematkan. Seperti, PinMutex . Ya, itu akan berhasil. Perubahan interior baik-baik saja jika Anda tidak pernah membagikan &mut . Tetapi saat ini tampaknya tidak mungkin kita akan memiliki struktur data seperti itu di libstd.

Tapi ya, untuk presisi penuh, pernyataan @withoutboats 'harus diubah untuk mengatakan bahwa "proyeksi pin melalui mutabilitas interior aman hanya jika jenisnya selalu pin, yaitu, tidak pernah membagikan &mut ."

@RalfJung Persis, ya.

Tetapi saat ini tampaknya tidak mungkin kita akan memiliki struktur data seperti itu di libstd.

Benar, saya ingin memastikan bahwa persyaratan yang dinyatakan di sekitar tidak memproyeksikan melalui pointer atau mutabilitas interior secara khusus di sekitar libstd, bukan aturan umum.

@cramertj jelas bukan aturan yang keras dan cepat (bukan UB untuk melanggarnya misalnya), tapi saya akan mengatakan itu pedoman yang baik untuk orang-orang yang tidak yakin

Saya kecewa dengan keputusan untuk menstabilkan ini. Saya tidak terlalu khawatir tentang apa yang terjadi dalam jangka pendek, karena tanpa dukungan compiler, desain potensial apa pun akan terlihat sedikit hackish. Namun, dalam jangka panjang, jika Rust mendapatkan tipe asli yang tidak dapat dipindahkan, mereka tidak akan merasa benar-benar kelas satu kecuali mereka dapat digunakan dengan metode sifat yang menerima &self dan &mut self . Akibatnya, penyematan harus berupa properti jenis referensi, bukan jenis referensi. Itu masih bisa terjadi, tentu saja, dalam desain masa depan untuk tipe asli tak bergerak. Tapi itu akan mengakibatkan Pin<T> menjadi mubazir - tidak hanya, katakanlah, alias yang sudah usang untuk beberapa &pin T , tetapi sama sekali tidak perlu. Pada gilirannya, ciri-ciri seperti Future (dan mungkin banyak lainnya) yang menerima Pin<Self> perlu diubah atau dihentikan, yang akan membutuhkan masa transisi yang berantakan.

Berantakan, bukan tidak mungkin. Tetapi dengan risiko menjadi terlalu pesimis, saya khawatir bahwa keinginan untuk menghindari transisi seperti itu akan membiaskan keputusan di masa depan untuk mengadopsi &pin alih-alih sebagai dasar untuk tipe asli yang tidak dapat dipindahkan - yang, menurut pandangan saya, akan ditinggalkan mereka kelas dua secara permanen. Dan saya masih berpikir bahwa di antara desain jangka pendek, pendekatan &Pinned<T> akan layak, menghindari masalah kompatibilitas ke depan ini dan memiliki manfaat lain juga (misalnya sepenuhnya menghindari masalah dengan mutabilitas interior).

Baiklah. Karena itu, saya masih menantikan untuk menggunakan Pin , tetapi saya lebih suka jika tetap di crates.io daripada memasuki perpustakaan standar.

Saya setuju bahwa masih terlalu dini untuk menstabilkan ini. Saat ini agak canggung dalam beberapa situasi, misalnya ketika mengakses bidang !Unpin ketik di dalam metode yang disebut PinMut<Self> . Saya pikir perubahan @withoutboats akan memperbaiki situasi, tetapi saya tidak yakin. Bukankah lebih baik menunggu sampai perubahan tersebut diterapkan dan diuji setidaknya untuk sementara?

@Thomasdezeeuw Perubahan apa yang menurut Anda akan memperbaiki situasi Perubahan yang saya usulkan tampaknya tidak berhubungan dengan ketidaknyamanan itu.

Tidak ada cara untuk menulis solusi umum - yaitu, menambahkan metode ke std - tetapi variasi makro dari futures ini aman:

macro_rules! get_mut {
    ($f:tt: $t:ty) => (
        fn $f<'a>(
            self: &'a mut std::mem::PinMut<Self>
        ) -> &'a mut $t
            where $t: Unpin
        {
            unsafe {
                 &mut std::mem::PinMut::get_mut_unchecked(self.reborrow()).$f
            }
        }
    )
}

struct Foo {
    bar: Bar,
}

impl Foo {
     get_mut!(bar: Bar);
}

@withoutboats Itu aman karena hanya menyediakan akses ke data Unpin , bukan?

@RalfJung tepatnya! baris dimana klausa membuatnya aman

@withoutboats itu makro yang bagus tetapi hanya berfungsi untuk jenis Unpin .

Tetapi bukankah mungkin untuk menambahkan metode / makro yang akan mengambil PinMut<Self> dan mengembalikan PinMut<Self.field> , lebih disukai dalam kode yang aman. Saya bisa saja salah tetapi pemikiran saya adalah bahwa jika penelepon menjamin bahwa Self dipasangi pin, maka Self.field , bukan? Itu juga harus bekerja untuk jenis yang tidak menerapkan Unpin .

@Thomasdezeeuw Masalah ini dibahas dalam ringkasan saya di bawah bagian "Proyeksi pin"; tidak aman memproyeksikan ke bidang !Unpin kecuali jika Anda juga menjamin bahwa Self tidak melakukan hal-hal tertentu lainnya, yang tidak dapat kami buat ceknya. Kami hanya dapat secara otomatis menjamin kemampuan untuk memproyeksikan bidang yang menerapkan Unpin (karena itu selalu aman, dan kami dapat memeriksanya dengan klausa where ).

@withoutboats Saya sudah membaca ringkasannya, tapi mungkin saya salah paham. Tetapi jika tipe T adalah !Unpin maka PinMut<T> tidak akan mengimplementasikan DerefMut for T , sehingga pemindahan nilai tidak akan mungkin dilakukan tanpa kode yang tidak aman. Anda masih memiliki masalah dengan sifat Drop , tetapi itu lebih besar daripada hanya mendapatkan akses ke bidang tipe. Jadi saya tidak melihat apa yang membuat pemetaan dari PinMut<T> menjadi PinMut<T.field> tidak aman, meskipun T.field adalah !Unpin .

@Thomasdezeeuw Dalam futures-rs kami memiliki situasi di mana kami memiliki tipe di dalam PinMut , tetapi salah satu bidangnya tidak dianggap disematkan

Kelemahan dari makro oleh @withoutboats adalah bahwa ia hanya dapat memberikan akses ke satu bidang pada satu waktu karena ia memegang referensi yang bisa berubah ke self . Akses kolom struktur tidak memiliki batasan ini.

Saya yakin memutuskan untuk menstabilkan API pemasangan pin adalah keputusan yang tepat. Saya akan, bagaimanapun, menyarankan untuk menerapkan modul core::pin diusulkan terlebih dahulu.

Anda masih memiliki masalah dengan sifat Jatuhkan, tetapi itu lebih besar daripada hanya mendapatkan akses ke bidang tipe.

Tidak, tidak. Drop sebenarnya baik-baik saja selama Anda tidak melakukan pemetaan lapangan . Itulah mengapa pemetaan lapangan tidak aman.

Anda masih memiliki masalah dengan sifat Jatuhkan, tetapi itu lebih besar daripada hanya mendapatkan akses ke bidang tipe. Jadi saya tidak melihat apa yang membuat pemetaan dari PinMutke PinMuttidak aman, bahkan jika T.field adalah! Lepas pin.

Untuk lebih jelasnya, kami juga tidak dapat secara otomatis menghasilkan bukti bahwa T: !Unpin . Tetapi bahkan jika tidak, berikut adalah contoh bagaimana Drop impl dapat digunakan:

struct Foo {
    field: UnpinFuture,
}

impl Foo {
     fn field(self: PinMut<Self>) -> PinMut<UnpinFuture> { ... }

     fn poll_field(self: PinMut<Self>, ctx: &mut Context) {
         self.field().poll(ctx);
     }
}

impl Drop {
    fn drop(&mut self) {
        // ...
        let moved_field = mem::replace(&mut self.field, UnpinFuture::new());

        // polling after move! violated the guarantee!
        PinBox::new(moved_field).as_pin().poll();
    }
}

Seperti yang dikatakan @RalfJung , ini persis masalah dengan Drop - jika Anda tidak melakukan proyeksi pin ke bidang !Unpin , semua yang Anda lakukan di Drop tidak masalah.

Kelemahan dari makro oleh @withoutboats adalah bahwa ia hanya dapat memberikan akses ke satu bidang dalam satu waktu karena ia memiliki referensi yang bisa berubah ke diri sendiri. Akses kolom struktur tidak memiliki batasan ini.

Anda dapat menulis metode yang memberikan akses ke beberapa bidang, tetapi Anda harus melakukannya untuk setiap kombinasi yang Anda pedulikan.

Karena penasaran, apakah ada kasus penggunaan konkret lain selain masa depan, atau setidaknya hal lain yang memotivasi keinginan untuk menstabilkannya sekarang (dibandingkan nanti dengan lebih banyak pengalaman)?

Sepertinya tidak ada diskusi lebih lanjut di sini terkait poin yang diangkat oleh @comex. Mereka membuat saya sangat penasaran, karena saya tidak ingat ide untuk membuat pin properti tipe alih-alih referensi. Apakah ini sudah dibahas di tempat lain? Tidak mudah untuk mengikuti semuanya dari "luar", saya melakukan yang terbaik ;-)

Pada gilirannya, ciri-ciri seperti Masa Depan (dan kemungkinan banyak lainnya) yang menerima Pinperlu diubah atau dihentikan, yang akan membutuhkan masa transisi yang berantakan.

Hmmm. Dapatkah beberapa keajaiban yang berhubungan dengan edisi memungkinkan kita untuk membungkus dan membuka bungkus Pin s ini secara diam-diam, dan membuat perubahan sepenuhnya kompatibel dengan versi sebelumnya? Agak menjijikkan, tetapi tidak terlalu, dan itu akan menjaga bahasa dan API tetap bersih selama transisi semacam itu.

Mungkin idenya perlu disempurnakan untuk menjawab pertanyaan ini (saya ingat ini pernah dibahas sebelumnya di utas, tetapi saya lupa di mana.)

@yasammez Saya dapat mengambil diskusi tentang menyematkan berorientasi nilai di sekitar sini

Sekadar konfirmasi, jalan menuju stabilisasi tidak termasuk rencana proyeksi lapangan yang aman? Menurut saya ini akan memperkenalkan banyak kode tidak aman di semua perpustakaan masa depan. Dalam pengalaman saya, manual Future impls tidak jarang, dan saya tahu semua milik saya membutuhkan polling bidang.

Menurut saya tidak masalah bahwa banyak kode yang tidak aman ini akan terbukti aman. Keberadaan kode tidak aman hanya melemahkan kemampuan seseorang untuk mengaudit kode tidak aman yang lebih nyata .

@tikue dalam waktu dekat, Anda dapat:

  1. menggunakan makro seperti makro unsafe_pinned yang dikembangkan perpustakaan berjangka, membatasi diri Anda pada satu hal yang tidak aman untuk setiap jajak pendapat, yang harus Anda verifikasi terhadap batasan yang dijelaskan dalam ringkasan panjang saya (Anda juga tidak dapat menggunakan makro dan cukup tulis pengakses dengan tangan, itu juga hanya satu unsafe ).
  2. mensyaratkan field Anda untuk mengimplementasikan Unpin , membuat proyeksi seperti itu sangat aman dan tidak membutuhkan kode yang tidak aman, dengan biaya mengalokasikan !Unpin futures.

@withoutboats Saya memahami pilihan tetapi merasa sulit untuk langit-langit. Mewajibkan Unpin akan membatasi kompatibilitas dengan banyak masa depan, seperti apa pun yang dipinjam sendiri, misalnya async fn yang menggunakan saluran. Dan polusi yang tidak aman adalah masalah yang nyata.

Secara praktis, saya telah mencoba memigrasi perpustakaan ke futures 0.3 dan merasa kesulitan karena alasan ini.

@tikue Ini kompatibel dengan memberikan cek untuk Anda secara otomatis. Kami belum sepenuhnya merancang atau mengimplementasikannya, tetapi tidak ada yang menghalangi hal itu terjadi.

Namun, saya tidak begitu memahami kekhawatiran ini:

Dan polusi yang tidak aman adalah masalah yang nyata.

Masalah "unsafe" yang terlalu sering ditinggalkan pada level ini saya rasa - hanya larangan menyeluruh atas unsafe .

Untuk sebagian besar berjangka berjangka, akan sangat mudah untuk menentukan apakah Anda dapat menggunakan makro unsafe_pinned! . Daftar periksa biasanya berjalan seperti ini:

  1. Saya tidak mengimplementasikan Unpin secara manual untuk masa depan saya, tetapi saya hanya mengandalkan impl.
  2. Saya tidak menerapkan Drop secara manual untuk masa depan saya, karena saya tidak memiliki destruktor khusus.
  3. Bidang yang ingin saya proyeksikan bersifat pribadi.

Dalam hal ini: Anda baik-baik saja! unsafe_pinned aman digunakan.

Jika Anda benar-benar menerapkan Unpin atau Drop secara manual, Anda harus benar-benar memikirkannya, tetapi bahkan dalam hal ini itu belum tentu menjadi masalah yang sulit:

  1. Jika saya mengimplementasikan Unpin , itu dibatasi di masa depan yang saya abstraksi menjadi Unpin .
  2. Jika saya menerapkan Drop , saya tidak pernah memindahkan bidang masa depan selama destruktor saya.

@tikue untuk referensi, saya telah menulis inti proposal dasar untuk solusi berbasis atribut . Ini membutuhkan dukungan kompilator, tetapi bukan ekstensi yang signifikan untuk bahasa - hanya satu sifat baru dan satu atribut bawaan baru.

Ada juga solusi yang lebih "mengganggu" di mana kita menambahkan tipe referensi &pin yang tepat, yang akan memiliki bentuk yang sama dalam hal batasan, tetapi akan lebih berdampak pada bahasa secara keseluruhan.

Bagaimana dengan PinMut::replace ?

impl<'a, T> PinMut<'a, T> {
  pub fn replace(&mut self, x: T) { unsafe {
    ptr::drop_in_place(self.inner as *mut T);
    ptr::write(self.inner as *mut T, x);
  } }
}

Bahkan jika kita tidak ingin menambahkan metode seperti itu, kita harus memiliki jawaban untuk pertanyaan apakah ini aman - untuk PinMut , dan juga untuk PinBox yang mungkin memiliki sesuatu yang serupa.

Saya pikir sudah pasti aman untuk PinBox , karena saya berhati-hati untuk memanggil destruktor "di tempat" dan kemudian ini hanya menggunakan kembali memori. Namun, saya agak khawatir dengan PinMut tentang nilai yang turun lebih awal, yaitu sebelum masa pakainya berakhir. Tampaknya tidak jelas bagi saya bahwa ini akan selalu baik-baik saja, tetapi saya juga tidak dapat memberikan contoh yang berlawanan. @cramertj @withoutboats ada pemikiran?

(Saya akan menambahkan ini ke @rfcbot jika saya bisa ...)

@rfcbot perhatian ganti

@RalfJung Saya bisa saja salah di sini tapi, tidak bisakah kode itu jatuh dua kali jika T panik?

@RalfJung Kami sudah memiliki PinMut::set .

@rumahsakitjogja . Ya oke saya bisa sedikit lebih teliti dalam memeriksa ini.

Maaf atas kebisingannya, kekhawatiran saya teratasi.

@rfcbot menyelesaikan ganti

Ini adalah kode nyata yang saya tulis sekarang untuk futures 0.1 => 0.3 kompatibilitas dengan tokio_timer::Deadline . Itu perlu membuat PinMut<Future01CompatExt<Delay>> , di mana Delay adalah bidang Deadline .

        let mut compat;
        let mut delay = unsafe {
            let me = PinMut::get_mut_unchecked(self);
            compat = Future01CompatExt::compat(&mut me.delay);
            PinMut::new_unchecked(&mut compat)
        };

Selain penggunaan unsafe yang tidak sepele, saya juga membutuhkan sekitar 5 iterasi pada kode ini untuk menemukan versi yang dikompilasi. Menulis dan memikirkan tentang apa yang terjadi di sini cukup tidak ergonomis.

Saya pikir ada banyak ruang antara larangan selimut tidak aman dan kode ini. Misalnya, map_unchecked terlalu membatasi untuk membantu kode ini karena memerlukan pengembalian referensi, sedangkan kode ini memerlukan pengembalian Future01CompatExt<&mut Delay> .

Sunting: Sumber lain dari unergonomy adalah Anda tidak bisa saling meminjam di penjaga korek api, jadi PinMuttidak dapat bekerja dengan kode seperti ini:

`` karat
cocokkan self.poll_listener (cx)? {
// dulu jika self.open_connections> 0
Jajak Pendapat :: Siap (Tidak Ada) jika self.open_connections ()> 0 => {
^^^^ dipinjam gonta-ganti di pattern guard
kembali Poll :: Tertunda;
}
Jajak Pendapat :: Siap (Tidak Ada) => {
kembali Poll :: Siap (Tidak Ada);
}
}
`` ''

Komentar Anda tentang map_unchecked tepat, mungkin ada tanda tangan yang lebih umum yang juga memungkinkan pengembalian sementara di sekitar pointer.

Apakah ada pedoman tentang kapan aman untuk mendapatkan referensi &mut dari bidang PinMut<Type> ? Saya pikir selama Type tidak pernah mengasumsikan bidang disematkan, oke? Apakah itu harus pribadi?

Ya, baik bidang mengimplementasikan Unpin atau Anda tidak pernah membuat bidang PinMut .

@rhm_hhhhhh

Saya menulis kode berbasis PinMut jumlah yang layak dan satu hal yang sering terjadi adalah saya memiliki operasi yang tidak bergantung pada penyematan, tetapi membutuhkan waktu sendiri sebesar PInMut daripada &mut sebagai cara untuk mengatakan "Aku berjanji tidak akan keluar dari ingatan ini." Namun, ini memaksa semua pengguna untuk memiliki nilai yang disematkan, yang sebenarnya tidak perlu. Saya telah memikirkannya dan tidak dapat menemukan API yang bagus untuk itu, tetapi alangkah baiknya jika ada cara untuk memisahkan "Saya perlu argumen saya untuk disematkan" dari "Saya dapat bekerja dengan pin argumen".

Saya pikir "varians" adalah istilah yang salah; apa yang Anda inginkan adalah tipe konstruktor terkait dengan abstrak atas tipe referensi yang berbeda: D

@RalfJung Ya, itu benar. Namun, saya akan memilih tipe seperti bisa berasal dari PinMut atau dari & mut tetapi hanya dapat digunakan seperti PinMut, meskipun saya tahu ini tidak berskala dengan baik. Saya pikir itu lebih bisa dicapai daripada generik arbitrary_self_types: smile: Mungkin yang terbaik adalah menutup dengan "kami cukup yakin bahwa kami tahan masa depan untuk berfungsi tanda tangan seperti ini":

impl MyType {
    fn foo(self: impl MutableRef<Self>, ...) { ... }
}

@rfcbot peduli api-refactor

Sedikit struktur inspirasi dan tadi malam saya menemukan cara bagaimana kami dapat memfaktor ulang API ini sehingga hanya ada satu tipe Pin , yang membungkus sebuah pointer, daripada harus membuat versi yang disematkan dari setiap pointer tunggal. Ini bukanlah pembentukan ulang API yang mendasar dengan cara apa pun, tetapi rasanya lebih baik untuk menarik komponen "menyematkan memori" ke bagian yang dapat disusun.

Ketika saya mengerjakan tumpukan jaringan untuk nt-rs , saya diberi tahu bahwa referensi yang disematkan akan membantu dengan "tarian kepemilikan" yang saat ini saya selesaikan dengan fold seperti yang terlihat di sini (tx.send memindahkan tx ke masa depan Send<<type of tx>> , sehingga sulit untuk mengulang data yang masuk untuk mengirim paket.). Dengan cara apa menyematkan membantu dengan hal semacam itu?

@Redrield send() mengambil &mut diri sendiri di masa depan 0.3.

@withoutboats Saya sangat ingin mencoba API baru ini di masa depan-rs!

Karena API baru menggunakan pengenal yang berbeda, menurut saya kedua API tersebut harus tersedia secara bersamaan. Saya pikir akan lebih baik untuk memiliki periode transisi singkat di mana kedua API tersedia yang memungkinkan kita bereksperimen, menyiapkan PR dan mem-porting API berjangka di libcore ke gaya baru.

@MajorBreakfast sepertinya kami berpotensi alias type PinMut<T> = Pin<&mut T>; dan menawarkan sejumlah metode yang sama, yang akan mengurangi besaran dan kecepatan kerusakan.

@tokopedia

Jadi dengan API baru:

  1. Pin<&T> dan Pin<&mut T> memiliki invarian "pemasangan standar", di mana nilai di belakangnya:
    1.a. bukan lagi &T / &mut T valid, kecuali Unpin
    1.b. tidak akan digunakan sebagai Pin dari alamat memori lain.
    1.c. akan dijatuhkan sebelum memori tidak valid.
  2. Pin<Smaht<T>> tidak memiliki "khusus" jaminan, kecuali bahwa ia mengembalikan berlaku Pin<&mut T> dan Pin<&T> ketika ditanya (yang hanya jaminan keselamatan standar, mengingat bahwa API aman ).
    2.a. Apakah kita menginginkan jaminan DerefPure , di mana Pin<Smaht<T>> diperlukan untuk mengembalikan nilai yang sama kecuali jika dimutasi? Apakah ada yang menginginkan itu?

Koreksi tentang invarian.

Invarian untuk Pin<Smaht<T>> adalah:

  1. Memanggil Deref::deref(&self.inner) akan memberikan Pin<&T::Target> valid (perhatikan bahwa &self.inner tidak perlu brankas &Smaht<T> ).
  2. Jika Smaht<T>: DerefMut , memanggil DerefMut::deref_mut(&mut self.inner) akan memberikan Pin<&mut T::Target> valid (perhatikan bahwa &mut self.inner tidak perlu brankas &mut Smaht<T> ).
  3. Memanggil destruktor self.inner untuk menghancurkannya tidak apa-apa asalkan aman untuk dimusnahkan.
  4. self.inner tidak harus brankas Smaht<T> - tidak harus mendukung fungsi lain.

Ada beberapa masukan yang diposting di postingan reddit tentang proposal @withoutboats :

  • (multiple) Own adalah nama yang aneh, lihat di bawah untuk kemungkinan cara mengatasi ini.

  • ryani bertanya mengapa implementasi yang membatasi Deref<Target = T> memiliki tambahan generik T ? Tidak bisakah kamu menggunakan P::Target ?

  • jnicklas menanyakan apa tujuan dari sifat Own , bagaimana dengan menambahkan pinned metode yang melekat dengan konvensi? Ini berarti pengguna API tidak perlu mengimpor sifat tersebut namun Anda kehilangan kemampuan untuk menjadi generik di atas konstruktor yang dapat disematkan. Apakah ini masalah besar? Motivasi untuk sifat tersebut tampaknya tidak cukup termotivasi: _ Bagaimanapun juga [mereka] memiliki bentuk yang sama .'_

  • RustMeUp (saya sendiri) bertanya, pada ciri Own , apakah tujuan dari metode own ? Mengapa sifat tersebut tidak bisa membiarkan pinned diterapkan oleh tipe yang ingin ikut serta? Hal ini memungkinkan sifat tersebut aman dan memungkinkan sifat tersebut diberi nama Pinned yang terasa kurang canggung daripada Own . Alasan metode sendiri tampaknya tidak cukup termotivasi.

Saya akan menambahkan pertanyaan lain:

Mengapa Pin<P> itu sendiri maupun Pin<P>::new_unchecked dibatasi untuk P: Deref ?

#[derive(Copy, Clone)]
pub struct Pin<P> {
    pointer: P,
}

impl<P: Deref> Pin<P> { // only change
    pub unsafe fn new_unchecked(pointer: P) -> Pin<P> {
        Pin { pointer }
    }
}

atau

#[derive(Copy, Clone)]
pub struct Pin<P: Deref> { // changed
    pointer: P,
}

impl<P: Deref> Pin<P> { // changed
    pub unsafe fn new_unchecked(pointer: P) -> Pin<P> {
        Pin { pointer }
    }
}

akan mencegah Pin<P> mana P: !Deref instance yang tidak berguna karena tidak ada yang bisa Anda lakukan dengannya kecuali metode tambahan ditambahkan. Kupikir...

Ini adalah inti dari saya dan umpan balik ryani ditujukan.

ryani bertanya mengapa implementasi itu membatasi Derefmemiliki T yang ekstra umum? Tidak bisakah Anda menggunakan P :: Target?

Tidak ada perbedaan sama sekali antara kedua implisit ini selain dari cara penulisannya.

Mengapa tidak ada Pin

itu sendiri atau Pin

:: new_unchecked dibatasi untuk P: Deref?

Saya tidak punya pendapat; secara historis pustaka std tidak terikat pada struct, tetapi saya tidak yakin apakah kebijakan itu termotivasi dengan baik atau kecelakaan historis.


Berencana untuk menerapkan ini setelah # 53227 mendarat, tetapi tidak akan menerapkan sifat Own karena sifatnya kontroversial. Untuk saat ini, hanya pinned konstruktor inheren pada Box , Rc dan Arc .

Menindaklanjuti apa yang ditulis @ arielb1 , saya melihat Pin sini sebagai benar-benar bertindak pada "konstruktor tipe penunjuk" - hal-hal seperti Box atau &'a mut yang memiliki "jenis" * -> * . Tentu saja, kami tidak benar-benar memiliki sintaks untuk itu, tetapi itu adalah cara saya bisa memahaminya dalam istilah invarian. Kami masih memiliki 4 (keamanan (berbeda per jenis seperti dalam posting blog saya : Milik, Dibagikan, Dimiliki Pin, Dibagikan dengan Pin).

Saya pikir formalisasi yang tepat memerlukan tampilan ini, karena idenya adalah bahwa Pin<Ptr><T> mengambil invarian T , mengubahnya (menggunakan invarian yang disematkan di mana-mana), dan kemudian menerapkan Ptr konstruktor untuk jenis yang dihasilkan. (Ini memerlukan perubahan cara kita mendefinisikan Owned , tetapi itu adalah sesuatu yang saya rencanakan untuk jangka panjang.) Dalam hal formalisme, seseorang akan lebih suka menulis Ptr<Pin<T>> , tetapi kami telah mencoba itu dan itu tidak bekerja dengan baik dengan Rust.

Janji, kemudian, yang dibuat oleh konstruktor tipe pointer ketika "memilih" ke Pin adalah bahwa menerapkan Deref / DerefMut akan aman: Jika inputnya benar-benar Ptr diterapkan ke varian jenis Pinned-Owned / Shared, output akan memenuhi varian Pinnd-Owned / Shared dari jenis tersebut.

Ini benar-benar satu-satunya "metode tidak aman" di sini (dalam arti mereka harus berhati-hati dengan invarian):

impl<P, T> Pin<P> where
    P: Deref<Target = T>,
{
    pub fn as_ref(this: &Pin<P>) -> Pin<&T> {
        Pin { pointer: &*this.pointer }
    }
}

impl<P, T> Pin<P> where
    P: DerefMut<Target = T>,
{
    pub fn as_mut(this: &mut Pin<P>) -> Pin<&mut T> {
        Pin { pointer: &mut *this.pointer }
}

Segala sesuatu yang lain yang aman untuk digunakan dapat diimplementasikan di atas ini dengan kode aman, mengeksploitasi bahwa Pin<&T> -> &T adalah konversi yang aman, dan untuk Unpin jenis penangguhan yang sama untuk Pin<&mut T> -> &mut T .

Saya lebih suka jika kita dapat mengubah implementasi Deref dan DerefMut untuk Pin menjadi sesuatu seperti

impl<'a, T: Unpin> Pin<&'a mut T> {
    pub fn unpin_mut(this: Pin<&'a mut T>) -> &'a mut T {
        this.pointer
    }
}

impl<'a, T> Pin<&'a T> {
    // You cannot move out of a shared reference, so "unpinning" this
    // is always safe.
    pub fn unpin_shr(this: Pin<&'a T>) -> &'a T {
        this.pointer
    }
}

// The rest is now safe code that could be written by users as well
impl<P, T> Deref for Pin<P> where
    P: Deref<Target = T>,
{
    type Target = T;
    fn deref(&self) -> &T {
        Pin::unpin_shr(Pin::as_ref(self))
    }
}

impl<P, T> DerefMut for Pin<P> where
    P: DerefMut<Target = T>,
    T: Unpin,
{
    fn deref_mut(&mut self) -> &mut T {
        Pin::unpin_mut(Pin::as_mut(self))
    }
}

Itu akan menunjukkan bahwa keduanya tidak melakukan sesuatu yang baru , mereka hanya menulis as_ref / as_mut dengan konversi yang aman dari Pin<&[mut] T> menjadi &[mut] T - - yang dengan jelas menyisakan as_ref / as_mut sebagai satu-satunya hal yang harus dikhawatirkan oleh konstruktor tipe pointer lainnya.


Saat ini PinMut memiliki metode borrow mengambil &mut self untuk peminjaman kembali yang lebih mudah (karena ini membutuhkan self , kami mendapatkan peminjaman otomatis). Metode ini sama persis dengan Pin::as_mut . Saya kira kita juga menginginkan sesuatu seperti ini di sini?

Saya baru saja memperhatikan bahwa irisan memiliki get_unchecked_mut , tetapi pin memiliki get_mut_unchecked . Sepertinya kita harus tetap berpegang pada sesuatu yang konsisten?

Bisakah seseorang menambahkan ini ke masalah rfcbot?

@rfcbot perhatian get_mut_unchecked_mut_mut

Saya baru menyadari bahwa kami lupa tentang satu situasi di mana rustc akan menyalin barang-barang - itu mungkin bukan masalah besar, untuk saat ini. Saya berbicara tentang struct yang dikemas. Jika struct yang dikemas memiliki bidang yang perlu dihapus, rustc akan mengeluarkan kode untuk menyalin data bidang itu ke suatu tempat yang selaras, dan kemudian memanggil drop untuk itu. Itu untuk memastikan bahwa &mut diteruskan ke drop benar-benar sejajar.

Bagi kami, itu berarti bahwa repr(packed) struct tidak boleh "struktural" atau "rekursif" wrt. menyematkan - bidangnya tidak dapat dianggap dipasang meskipun struct itu sendiri. Secara khusus, tidak aman menggunakan makro pengakses-pin pada struktur seperti itu. Itu harus ditambahkan ke dokumentasinya.

Itu tampak seperti footgun raksasa yang harus ditempelkan di semua dokumen pin.

@alercah Saya tidak berpikir ini lebih merupakan footgun daripada kemampuan yang ada untuk mengimplikasikan Unpin atau Drop - tentu saja keduanya jauh lebih umum terlihat di samping nilai yang disematkan daripada #[repr(packed)] .

Itu adil. Kekhawatiran saya adalah seseorang mungkin berpikir bahwa proyeksi aman untuk jenis yang tidak mereka tulis, dan tidak menyadari bahwa packed membuatnya tidak aman untuk dilakukan, karena jelas tidak jelas. Memang benar bahwa siapa pun yang melakukan proyeksi bertanggung jawab untuk mengetahui hal ini, tetapi saya pikir itu perlu didokumentasikan tambahan di mana pun di mana proyeksi seperti itu mungkin terjadi.

Hm, apakah ini juga berarti bahwa semua struct yang dikemas bisa menjadi Unpin terlepas dari jenis bidang karena penyematan tidak rekursif?

@alercah Saya tidak terlalu paham dengan bagaimana tipe yang dikemas diimplementasikan, tapi saya percaya aman untuk menyematkan ke tipe yang dikemas, hanya saja tidak untuk menyematkan melalui tipe yang dikemas. Jadi satu-satunya kasus di mana Anda tidak mengontrol tipe yang dikemas adalah jika Anda memproyeksikan ke bidang publik dari tipe orang lain, yang bisa juga tidak aman karena Drop atau apa pun. Secara umum, tampaknya tidak disarankan untuk menyematkan proyek ke bidang orang lain kecuali mereka memberikan proyeksi pin yang menunjukkan bahwa itu aman.

Hm, apakah ini juga berarti bahwa semua struct yang dikemas dapat Lepas pin apa pun jenis bidangnya karena pemasangan pin tidak rekursif?

Kasus penggunaan yang bisa saya bayangkan adalah daftar tertaut yang mengganggu, di mana Anda hanya peduli tentang alamat seluruh struct, tetapi ada banyak data yang tidak selaras yang ingin Anda gabungkan tanpa peduli tentang alamat data itu.

Saya harus mempelajari Pin API karena pekerjaan saya dengan Futures, dan saya punya pertanyaan.

  • Box mengimplementasikan Unpin tanpa syarat.

  • Box tanpa syarat mengimplementasikan DerefMut .

  • Metode Pin::get_mut selalu bekerja pada &mut Box<T> (karena Box mengimplementasikan Unpin tanpa syarat).

Secara keseluruhan, ini memungkinkan untuk memindahkan nilai yang disematkan dengan kode yang sepenuhnya aman .

Apakah ini dimaksudkan? Apa alasan mengapa ini aman dilakukan?

Sepertinya Anda memiliki Pin<&mut Box<...>> , yang menyematkan Box , bukan barang di dalam Box . Selalu aman untuk memindahkan Box sekitar, karena Box tidak pernah menyimpan referensi ke lokasinya di tumpukan.

Yang mungkin Anda inginkan adalah Pin<Box<...>> . Tautan taman bermain .

EDIT: tidak lagi akurat

@Pauan Hanya karena Box disematkan tidak berarti hal _inside_ disematkan. Kode apa pun yang bergantung pada ini akan salah.

Hal yang Anda cari mungkin adalah PinBox , yang melarang perilaku yang Anda sebutkan, dan memungkinkan Anda mendapatkan PinMut<Foo> .

@tikue Masih mungkin untuk keluar dari Pin<Box<...>> , yang menurut saya adalah apa yang mereka coba hindari.

@tmandry Koreksi saya jika saya salah, tetapi Pin<Box<...>> menyematkan benda di dalam Box , bukan Kotak itu sendiri. Dalam contoh asli @Pauan , mereka memiliki Pin<&mut Box<...>> , yang hanya menyematkan Box . Lihat tautan taman bermain saya yang menunjukkan bagaimana Pin<Box<...>> mencegah mendapatkan referensi yang bisa berubah ke benda di dalam kotak.

Perhatikan bahwa PinBox baru-baru ini dihapus, dan Pin<Box<T>> sekarang memiliki semantik yang sama dengan PinBox .

@tmandry PinBox<T> telah dihapus dan diganti dengan Pin<Box<T>> pada Nightly (tautan dokumen yang Anda berikan adalah untuk Stabil). Ini adalah tautan Nightly yang benar.

Oh, aturannya pasti sudah berubah sejak terakhir kali aku menggunakan ini. Maaf bila membingungkan.

@tmandry Ya, perubahannya sangat baru. Karena segala sesuatunya masih berubah, sulit untuk mengikuti semua perubahan.

Komentar @tikue benar. Anda perlu ingat bahwa pin hanya menyematkan satu tingkat tipuan ke bawah.

@tikue @tmandry @withoutboats Terima kasih atas jawabannya! Itu sangat membantu.

Jadi apa status dari kedua masalah tersebut sekarang? ( api-refactor & get_mut_unchecked_mut_mut ) Sebagai seseorang yang sangat menantikan fitur async / await series, saya ingin tahu versi Rustc mana yang akan menjadi target API Pin ? Apakah ada perkiraannya?

@ crlf0710 lihat proposal stabilisasi .

@withoutboats Sepertinya sudah selesai? Haruskah kita tutup?

Oke saya minta maaf jika ini bukan tempat untuk memposting ini, tetapi saya telah memikirkan masalah Drop + !Unpin , dan telah keluar dengan ide berikut:

  1. Idealnya, jika Drop::drop adalah fn(self: Pin<&mut Self>) tidak akan ada masalah. Sebut saja Drop PinDrop . Kita tidak bisa begitu saja mengganti Drop dengan PinDrop karena masalah kompatibilitas ulang.
  1. karena satu-satunya masalah dengan Drop::drop(&mut self) adalah untuk kasus Drop + !Unpin , kita dapat memperoleh impl default PinDrop untuk Drop + Unpin (sejak saat itu Pin<&mut T> : DerefMut<Target = T> ) dan buat PinDrop menjadi ciri yang secara otomatis digunakan oleh rustc (terima kasih kepada Pin::new_unchecked(&mut self) , karena drop adalah satu-satunya kasus penjepit tumpukan ketika kita memikirkannya).

Berikut adalah PoC samar dari ide tersebut: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=9aae40afe732babeafef9dab3d7525a8

Bagaimanapun, imho ini harus tetap di beta dan belum stabil, bahkan jika itu harus menjadi pengecualian. Jika ada waktu di mana Drop behavior dapat bergantung pada Unpin tanpa melanggar compat maka saat itu adalah sekarang.

@danielhenrymantilla Saya tidak melihat bagaimana hal itu menyelesaikan masalah kompatibilitas dengan impls penurunan generik yang ada, seperti impl<T> Drop for Vec<T> .

Anda benar, itu membutuhkan hal lain:
implisit T: Unpin terikat pada semua obat generik, dengan penyisihan menggunakan ?Unpin dengan cara yang sama seperti Sized

Itu harus membuatnya menjadi

impl<T> Drop for Vec<T>
where
  T : Unpin, // implicit, which implies that Vec<T> : Unpin which "upgrades" `Drop` into `PinDrop`

implisit T: Unpin terikat pada semua obat generik, dengan penyisihan menggunakan ?Unpin dengan cara yang sama seperti Sized

Ini berdampak besar pada keseluruhan desain API, dan dibahas secara ekstensif sebagai bagian dari proposal ?Move . Misalnya, ini berarti bahwa banyak, banyak pustaka yang ada perlu diperbarui untuk bekerja dengan penyematan. Kesimpulannya adalah bahwa menggunakan solusi khusus perpustakaan seperti yang kita dapatkan sekarang lebih baik karena tidak memerlukan semua ini.

Ya, biaya jangka pendek yang sangat besar karena semua pustaka yang ada perlu memperbarui agar kompatibel dengan !Unpin , tetapi dalam jangka panjang kita akan mendapatkan Drop "lebih aman". Awalnya tidak terlihat buruk, karena setidaknya kami tidak merusak apa pun.

Tapi ini adalah kekhawatiran yang adil (saya tidak tahu bahwa itu telah dibesarkan sebelumnya; terima kasih telah menunjukkannya, @RalfJung ), dan saya rasa kelemahan praktis jangka pendek drop(Pin<&mut Self>) .

Pernahkah ada diskusi tentang Hash implementasi untuk Pin jenis hashing pada alamat?

Pin mungkin harus memiliki implementasi Hash yang hanya mendelegasikan ke penunjuk yang ada, tidak ada prioritas untuk hashing berdasarkan alamat (dan saya tidak melihat alasan mengapa menyematkan nilai harus mengubah cara itu mendapat hash).

Apakah halaman ini membantu?
0 / 5 - 0 peringkat