Rust: Masalah pelacakan untuk mempromosikan `!` ke suatu jenis (RFC 1216)

Dibuat pada 29 Jul 2016  ·  259Komentar  ·  Sumber: rust-lang/rust

Masalah pelacakan untuk rust-lang/rfcs#1216, yang mempromosikan ! ke suatu jenis.

Masalah yang tertunda untuk diselesaikan

Acara dan tautan menarik

A-typesystem B-RFC-approved B-unstable C-tracking-issue F-never_type Libs-Tracked T-lang T-libs finished-final-comment-period

Komentar yang paling membantu

@petrochenkov Lupakan ! dan lihat saja enum.

Jika saya memiliki enum dengan dua varian, saya dapat mencocokkannya dengan dua kasing:

enum Foo {
    Flim,
    Flam,
}

let foo: Foo = ...;
match foo {
    Foo::Flim => ...,
    Foo::Flam => ...,
}

Ini berfungsi untuk n apa pun, bukan hanya dua. Jadi jika saya memiliki enum dengan nol varian, saya dapat mencocokkannya dengan nol kasing.

enum Void {
}

let void: Void = ...;
match void {
}

Sejauh ini baik. Tapi lihat apa yang terjadi, kami mencoba mencocokkan pola bersarang. Berikut ini pencocokan pada enum dua varian di dalam Result .

enum Foo {
    Flim,
    Flam,
}

let result_foo: Result<T, Foo> = ...;
match result_foo {
    Ok(t) => ...,
    Err(Flim) => ...,
    Err(Flam) => ...,
}

Kita dapat memperluas pola dalam di dalam pola luar. Ada dua varian Foo , jadi ada dua kasus untuk Err . Kami tidak memerlukan pernyataan kecocokan terpisah untuk mencocokkan pada Result dan pada Foo . Ini berfungsi untuk enum dengan sejumlah varian... _kecuali nol_.

enum Void {
}

let result_void: Result<T, Void> = ...;
match result_void {
    Ok(t) => ...,
    // ERROR!
}

Mengapa ini tidak bekerja? Saya tidak akan menyebut memperbaiki ini dengan menambahkan "dukungan khusus" untuk tipe yang tidak berpenghuni, saya akan menyebutnya memperbaiki inkonsistensi.

Semua 259 komentar

Sabas!

Ada implementasi WIP ini di sini: https://github.com/canndrew/rust/tree/bang_type_coerced

Statusnya saat ini adalah: dibangun dengan trans lama dan dapat digunakan tetapi memiliki beberapa tes yang gagal. Beberapa tes gagal karena bug yang menyebabkan kode seperti if (return) {} mogok selama trans. Tes lainnya berkaitan dengan optimasi waktu tautan dan selalu bermasalah bagi saya, jadi saya tidak tahu apakah itu ada hubungannya dengan perubahan saya.

Peta jalan saya saat ini adalah:

  • Dapatkan bekerja dengan MIR. Mudah-mudahan ini tidak akan terlalu sulit karena ini adalah bagaimana saya mulai mengimplementasikannya, tetapi saya mengalami masalah ketika MIR membangun segfault selama kompilasi.
  • Bersihkan hal-hal divergensi usang dari kompiler ( FnOutput , FnDiverging dan terkait).
  • Sembunyikan tipe baru di balik gerbang fitur. Ini berarti, ketika fitur dinonaktifkan:

    • ! hanya dapat diuraikan sebagai tipe di posisi pengembalian.

    • Variabel tipe divergen default ke () .

  • Cari tahu bagaimana kami dapat meningkatkan peringatan kompatibilitas ketika () default digunakan untuk menyelesaikan suatu sifat. Salah satu cara untuk melakukannya adalah dengan menambahkan tipe baru ke AST yang disebut DefaultedUnit . Jenis ini berperilaku seperti () dan akan berubah menjadi () dalam beberapa keadaan tetapi menimbulkan peringatan ketika menyelesaikan suatu sifat (sebagai () ). Masalah dengan pendekatan ini adalah saya pikir akan sulit untuk menangkap dan memperbaiki semua bug dengan implementasi - kami akhirnya melanggar kode orang untuk menyelamatkan kode mereka agar tidak rusak.

Apakah ada sesuatu yang perlu ditambahkan ke daftar ini? Apakah hanya saya yang mengerjakan ini? Dan haruskah cabang ini dipindahkan ke repositori utama?

Cari tahu bagaimana kita dapat meningkatkan peringatan kompatibilitas ketika () default digunakan untuk menyelesaikan suatu sifat. Salah satu cara untuk melakukannya adalah dengan menambahkan tipe baru ke AST yang disebut DefaultedUnit . Jenis ini berperilaku seperti () dan akan berubah menjadi () dalam beberapa keadaan tetapi menimbulkan peringatan ketika menyelesaikan suatu sifat (sebagai () ). Masalah dengan pendekatan ini adalah saya pikir akan sulit untuk menangkap dan memperbaiki semua bug dengan implementasi - kami akhirnya melanggar kode orang untuk menyelamatkan kode mereka agar tidak rusak.

@eddyb , @arielb1 , @anyone_else : Pemikiran tentang pendekatan ini? Saya cukup banyak sampai ke tahap ini (tanpa beberapa tes gagal yang saya (sangat lambat) coba perbaiki).

Sifat apa yang harus kita terapkan untuk !? PR awal #35162 termasuk Ord dan beberapa lainnya.

Bukankah ! seharusnya mengimplementasikan _all_ ciri secara otomatis?

Kode semacam ini cukup umum:

trait Baz { ... }

trait Foo {
    type Bar: Baz;

    fn do_something(&self) -> Self::Bar;
}

Saya berharap ! dapat digunakan untuk Foo::Bar untuk menunjukkan bahwa Bar tidak akan pernah benar-benar ada:

impl Foo for MyStruct {
    type Bar = !;
    fn do_something(&self) -> ! { panic!() }
}

Tapi ini hanya mungkin jika ! mengimplementasikan semua sifat.

@tomaka Ada RFC tentang itu: https://github.com/rust-lang/rfcs/pull/1637

Masalahnya adalah jika ! mengimplementasikan Trait itu juga harus mengimplementasikan !Trait ...

Masalahnya adalah jika ! mengimplementasikan Trait itu juga harus mengimplementasikan !Trait...

Kemudian kasus khusus ! sehingga mengabaikan persyaratan sifat apa pun?

@tomaka ! tidak dapat mengimplementasikan _all_ ciri secara otomatis karena sifat dapat memiliki metode statis dan tipe/konstituen terkait. Itu dapat secara otomatis mengimplementasikan sifat-sifat yang hanya memiliki metode non-statis (yaitu metode yang menggunakan Self ).

Adapun !Trait , seseorang menyarankan bahwa ! dapat mengimplementasikan secara otomatis Trait _and_ !Trait . Saya tidak yakin apakah itu terdengar, tetapi saya menduga bahwa sifat-sifat negatif tidak terdengar sama sekali.

Tapi ya, mungkin bagus jika ! dapat mengimplementasikan secara otomatis Baz dalam contoh yang Anda berikan untuk kasus-kasus seperti ini.

Kapan tepatnya kita melakukan default variabel tipe divergen ke () / ! dan kapan kita membuat kesalahan tentang tidak dapat menyimpulkan informasi tipe yang cukup? Apakah ini ditentukan di mana saja? Saya ingin dapat mengkompilasi kode berikut:

let Ok(x) = Ok("hello");

Tapi kesalahan pertama yang saya dapatkan adalah " unable to infer enough type information about _ ". Dalam hal ini saya pikir masuk akal untuk _ default ke ! . Namun ketika saya menulis tes seputar perilaku default, saya merasa sangat sulit untuk membuat variabel tipe default. Itu sebabnya ini tes begitu berbelit-belit.

Saya ingin memiliki gagasan yang jelas tentang mengapa kita memiliki perilaku default ini dan kapan seharusnya dipanggil.

Tetapi kesalahan pertama yang saya dapatkan adalah "tidak dapat menyimpulkan informasi jenis yang cukup tentang _". Dalam hal ini saya pikir masuk akal jika _ menjadi default ke !. Namun ketika saya menulis tes seputar perilaku default, saya merasa sangat sulit untuk membuat variabel tipe default. Itu sebabnya tes ini sangat berbelit-belit.

Itu ide yang sangat bagus menurut saya. Sama untuk None yang akan default ke Option<!> misalnya.

@carllerche

Tes unit_fallback tentu saja merupakan cara yang aneh untuk mendemonstrasikannya. Versi yang kurang makro adalah

trait Balls: Default {}
impl Balls for () {}

struct Flah;

impl Flah {
    fn flah<T: Balls>(&self) -> T {
        Default::default()
    }
}

fn doit(cond: bool) {
    let _ = if cond {
        Flah.flah()
    } else {
        return
    };
}

fn main() {
    let _ = doit(true);
}

Hanya variabel tipe yang dibuat oleh return / break / panic!() default untuk apa pun.

Kapan tepatnya kita melakukan default variabel tipe divergen ke ()/! dan kapan kita membuat kesalahan tentang tidak dapat menyimpulkan informasi jenis yang cukup? Apakah ini ditentukan di mana saja?

Tentukan "ditentukan". :) Jawabannya adalah bahwa operasi tertentu, yang tidak ditulis afaik di mana pun di luar kode, mengharuskan jenisnya diketahui pada saat itu. Kasus yang paling umum adalah akses bidang ( .f ) dan metode pengiriman ( .f() ), tetapi contoh lain adalah deref ( *x ), dan mungkin ada satu atau dua lagi. Sebagian besar alasan yang layak untuk ini diperlukan -- secara umum, ada beberapa cara berbeda untuk melanjutkan, dan kita tidak dapat membuat kemajuan tanpa mengetahui mana yang harus diambil. (Mungkin, berpotensi, untuk memfaktorkan ulang kode sehingga kebutuhan ini dapat didaftarkan sebagai semacam "kewajiban yang tertunda", tetapi rumit untuk melakukannya.)

Jika Anda berhasil sampai ke akhir fn, maka kami menjalankan semua operasi seleksi sifat yang tertunda hingga kondisi mapan tercapai. Ini adalah titik di mana default (misalnya, i32, dll) diterapkan. Bagian terakhir ini dijelaskan dalam RFC yang berbicara tentang parameter tipe default yang ditentukan pengguna (meskipun RFC secara umum perlu bekerja).

@canndrew itu terlihat sedikit mirip dengan https://github.com/rust-lang/rust/issues/12509

Wah, itu bug lama! Tapi ya, saya akan mengatakan bahwa #36038 saya adalah penipuan (saya pikir saya pernah melihatnya di suatu tempat sebelumnya). Saya tidak berpikir ! benar-benar dapat dipertimbangkan untuk prime time sampai itu diperbaiki.

Apakah ! direncanakan untuk memengaruhi kelengkapan pencocokan pola? Contoh perilaku saat ini, yang mungkin salah:

#![feature(never_type)]

fn main() {
    let result: Result<_, !> = Ok(1);
    match result {
//        ^^^^^^ pattern `Err(_)` not covered
        Ok(i) => println!("{}", i),
    }
}

@tikue ya, itu salah satu bug yang tercantum di atas.

@lfairy ups, tidak melihatnya karena tidak terdaftar di kotak centang di atas. Terima kasih!

Apakah ada rencana untuk mengimplementasikan From<!> for T dan Add<T> for ! (dengan tipe keluaran ! )? Saya tahu itu permintaan yang sangat aneh-- Saya mencoba menggunakan keduanya dalam PR ini .

From<!> for T pasti. Add<T> for ! mungkin terserah tim libs untuk memutuskan tetapi saya pribadi berpikir ! harus mengimplementasikan setiap sifat yang memiliki implementasi logis dan kanonik.

@canndrew Terima kasih! Saya terbiasa dengan sifat Nothing scala yang merupakan subtipe dari setiap jenis, dan dengan demikian dapat digunakan hampir di mana saja nilai dapat muncul. Namun, saya benar-benar bersimpati dengan keinginan untuk memahami efek yang impl All for ! atau serupa akan ada pada sistem tipe rust, terutama mengenai batas sifat negatif dan sejenisnya.

Per https://github.com/rust-lang/rfcs/issues/1723#issuecomment -241595070 From<!> for T memiliki masalah koherensi.

Ah benar, ya. Kita perlu melakukan sesuatu tentang itu.

Akan lebih baik jika impls sifat dapat secara eksplisit menyatakan bahwa mereka ditimpa oleh impls sifat lain. Sesuatu seperti:

impl<T> From<T> for T
    overridden_by<T> From<!> for T
{ ... }

impl<T> From<!> for T { ... }

Bukankah ini tercakup dalam spesialisasi? Sunting: Saya percaya ini adalah aturan impl kisi.

Apakah alternatif yang layak untuk menghindari dukungan khusus untuk tipe yang tidak berpenghuni sebanyak mungkin?

Semua match (res: Res<A, !>) { Ok(a) /* No Err */ } , metode khusus untuk Result terlihat sangat dibuat-buat, seperti fitur demi fitur, dan tampaknya tidak sepadan dengan usaha dan kerumitannya.
Saya mengerti ! adalah fitur hewan peliharaan @canndrew dan dia ingin mengembangkannya lebih lanjut, tetapi mungkin itu adalah arah yang salah dari awal dan #12609 bahkan bukan bug?

@ petrochenkov #12609 bukan fitur khusus untuk tipe Never. Ini hanya perbaikan bug untuk mendeteksi beberapa kode yang jelas tidak dapat dijangkau.

@petrochenkov Lupakan ! dan lihat saja enum.

Jika saya memiliki enum dengan dua varian, saya dapat mencocokkannya dengan dua kasing:

enum Foo {
    Flim,
    Flam,
}

let foo: Foo = ...;
match foo {
    Foo::Flim => ...,
    Foo::Flam => ...,
}

Ini berfungsi untuk n apa pun, bukan hanya dua. Jadi jika saya memiliki enum dengan nol varian, saya dapat mencocokkannya dengan nol kasing.

enum Void {
}

let void: Void = ...;
match void {
}

Sejauh ini baik. Tapi lihat apa yang terjadi, kami mencoba mencocokkan pola bersarang. Berikut ini pencocokan pada enum dua varian di dalam Result .

enum Foo {
    Flim,
    Flam,
}

let result_foo: Result<T, Foo> = ...;
match result_foo {
    Ok(t) => ...,
    Err(Flim) => ...,
    Err(Flam) => ...,
}

Kita dapat memperluas pola dalam di dalam pola luar. Ada dua varian Foo , jadi ada dua kasus untuk Err . Kami tidak memerlukan pernyataan kecocokan terpisah untuk mencocokkan pada Result dan pada Foo . Ini berfungsi untuk enum dengan sejumlah varian... _kecuali nol_.

enum Void {
}

let result_void: Result<T, Void> = ...;
match result_void {
    Ok(t) => ...,
    // ERROR!
}

Mengapa ini tidak bekerja? Saya tidak akan menyebut memperbaiki ini dengan menambahkan "dukungan khusus" untuk tipe yang tidak berpenghuni, saya akan menyebutnya memperbaiki inkonsistensi.

@petrochenkov Mungkin saya salah mengerti apa yang Anda katakan. Ada dua pertanyaan yang dibahas di utas #12609:

(0) Haruskah kode ini diizinkan untuk dikompilasi?

let res: Result<u32, !> = ...;
match res {
    Ok(x) => ...,
}

(1) Haruskah kode ini diizinkan untuk dikompilasi?

let res: Result<u32, !> = ...;
match res {
    Ok(x) => ...,
    Err(_) => ...,
}

Seperti yang diterapkan saat ini, jawabannya adalah "tidak" dan "ya". #12609 berbicara tentang (1) khususnya dalam masalah itu sendiri tetapi saya memikirkan (0) ketika saya menjawab. Adapun apa jawaban _seharusnya_, saya pikir (0) pasti harus "ya" tetapi untuk (1) saya juga tidak yakin.

@canndrew
Mungkin masuk akal untuk membuat (1), yaitu pola yang tidak dapat dijangkau, serat dan bukan kesalahan yang sulit terlepas dari jenis yang tidak berpenghuni, RFC 1445 berisi lebih banyak contoh mengapa hal ini dapat berguna.

Mengenai (0) saya kurang lebih yakin dengan penjelasan anda. Saya akan sangat senang jika skema ini terletak secara alami pada implementasi pemeriksaan pola di kompiler dan menghapus lebih banyak kode khusus daripada menambahkan.

Omong-omong, saya telah melakukan PR untuk mencoba dan memperbaiki (0) di sini: https://github.com/rust-lang/rust/pull/36476

Saya akan sangat senang jika skema ini terletak secara alami pada implementasi pemeriksaan pola di kompiler dan menghapus lebih banyak kode khusus daripada menambahkan.

Tidak, anehnya. Tapi itu mungkin lebih merupakan artefak dari cara saya meretasnya ke dalam kode yang ada daripada tidak ada cara yang elegan untuk melakukannya.

Saya pikir untuk makro berguna untuk (1) agar tidak menjadi kesalahan yang sulit.

Saya pikir (1) harus dikompilasi secara default tetapi memberikan peringatan dari serat yang sama seperti di sini:

fn a() -> u32 {
    return 4;
    5
}
warning: unreachable expression, #[warn(unreachable_code)] on by default

Sementara kita melakukannya, apakah masuk akal untuk membuat beberapa pola tak terbantahkan dalam menghadapi ! ?

let res: Result<u32, !> = ...;
let Ok(value) = res;

Saya setuju bahwa membuat kecocokan yang tidak lengkap merupakan kesalahan, tetapi tidak dapat dijangkau yaitu pola yang berlebihan hanya peringatan yang tampaknya masuk akal.

Saya memiliki beberapa PR yang duduk-duduk untuk sementara waktu mengumpulkan kebusukan. Adakah yang bisa saya lakukan untuk membantu peninjauan ini? Mungkin perlu ada diskusi tentang mereka. Saya berbicara tentang #36476, #36449 dan #36489.

Saya menentang gagasan berpikir tipe divergen (atau tipe "bawah" dalam teori tipe) sama dengan tipe enum kosong (atau tipe "nol" dalam teori tipe). Mereka adalah makhluk yang berbeda, meskipun keduanya tidak dapat memiliki nilai atau contoh apa pun.

Menurut pendapat saya, tipe bawah hanya dapat muncul dalam konteks apa pun yang mewakili tipe pengembalian. Sebagai contoh,

fn(A,B)->!
fn(A,fn(B,C)->!)->!

Tapi Anda tidak harus mengatakan

let g:! = panic!("whatever");

atau

fn(x:!) -> !{
     x
}

atau bahkan

type ID=fn(!)->!;

karena tidak ada variabel yang harus bertipe ! , jadi tidak ada variabel input yang harus bertipe ! .

Kosong enum berbeda dalam hal ini, Anda dapat mengatakan

enum Empty {}

impl Empty {
    fn new() -> Empty {
         panic!("empty");
    }
}

kemudian

 match Empty::new() {}

Artinya, ada perbedaan mendasar antara ! dan Empty : Anda tidak dapat mendeklarasikan variabel apa pun untuk memiliki tipe ! tetapi dapat melakukannya untuk Empty .

@earthengine Apa keuntungan memperkenalkan perbedaan ini (di mata saya sepenuhnya buatan) antara dua jenis tipe yang tidak dapat dihuni?

Banyak alasan untuk tidak memiliki perbedaan seperti itu telah dikemukakan - misalnya, dapat menulis Result<!, E> , membuat ini berinteraksi dengan baik dengan fungsi yang berbeda, sementara masih dapat menggunakan operasi monadik pada Result , seperti map dan map_err .

Dalam pemrograman fungsional, tipe kosong ( zero ) sering digunakan untuk mengkodekan fakta bahwa suatu fungsi tidak kembali atau bahwa suatu nilai tidak ada. Ketika Anda mengatakan tipe bottom , tidak jelas bagi saya konsep teori tipe mana yang Anda maksud; biasanya bottom adalah nama untuk tipe yang merupakan subtipe dari semua tipe -- tetapi dalam pengertian itu, baik ! bukan enum kosong adalah bottom di Rust. Tetapi sekali lagi, zero bukan merupakan tipe bottom bukanlah hal yang aneh dalam teori tipe.

Artinya, ada perbedaan mendasar antara ! dan Empty : Anda tidak dapat mendeklarasikan variabel apa pun untuk memiliki tipe ! tetapi dapat melakukannya untuk Empty .

Itulah yang diperbaiki RFC ini. ! sebenarnya bukan "tipe" jika Anda tidak dapat menggunakannya seperti tipe.

@RalfJung
Konsep-konsep itu berasal dari logika linier https://en.wikipedia.org/wiki/Linear_logic

Karena ada beberapa kesalahan dalam teks sebelumnya di posting ini, saya menghapusnya. Setelah saya melakukannya dengan benar, saya akan memperbarui ini.

top adalah nilai yang dapat digunakan dengan cara apa pun yang memungkinkan

Apa artinya ini dalam subtipe? Bahwa itu bisa menjadi tipe apa saja? Karena itu bottom begitu.

@eddyb Saya membuat beberapa kesalahan, harap tunggu pembaruan baru saya.

PR ini - yang digabungkan dalam sehari - sangat bertentangan dengan PR pencocokan pola saya yang telah ditinjau selama lebih dari sebulan. Maaf, PR pencocokan pola kedua saya sejak yang pertama menjadi tidak dapat digabungkan dan harus ditulis ulang setelah ditinjau selama dua bulan.

ff teman-teman

@canndrew argh, saya minta maaf tentang itu. =(Aku juga berencana mengerjakan PRmu hari ini...

Maaf, saya tidak ingin merengek, tetapi ini telah berlarut-larut. Saya menyerah untuk mencoba mempertahankan beberapa PR sekaligus dan sekarang saya telah mencoba untuk mendapatkan perubahan yang satu ini sejak September. Saya pikir bagian dari masalahnya adalah perbedaan zona waktu - Anda semua online saat saya di tempat tidur sehingga sulit untuk mengobrol tentang hal ini.

Mengingat bahwa pengecualian adalah lubang logis dan perlu dalam sistem tipe, saya bertanya-tanya apakah ada yang memberikan pemikiran serius untuk bergerak ke arah yang lebih formal menanganinya dengan !.

Menggunakan https://is.gd/4EC1Dk sebagai contoh dan titik referensi, bagaimana jika kita melewatinya, dan.

1) Perlakukan fungsi apa pun yang dapat membuat panik tetapi tidak mengembalikan kesalahan atau hasilnya memiliki tanda tangan jenisnya secara implisit berubah dari -> Foo menjadi -> Result<Foo,!> 2) any Hasiltypes would have their Error types implicitly be converted to enum AnonMyErrWrapper { Mati(!),Error}```
3) Sejak ! adalah iklan berukuran nol tidak dapat dihuni, Tidak akan ada biaya untuk mengonversi antar jenis, dan konversi implisit dapat ditambahkan untuk membuatnya kompatibel.

Manfaatnya, tentu saja, adalah bahwa pengecualian secara efektif diangkat ke dalam sistem tipe, dan dimungkinkan untuk mempertimbangkannya, melakukan analisis statis pada mereka, dll.

Saya menyadari bahwa ini akan ...non-sepele dari sudut pandang komunitas, jika bukan dari yang teknis. :)

Juga, ini mungkin tumpang tindih dengan kemungkinan sistem efek masa depan.

@tupshin itu perubahan besar, setidaknya tanpa banyak senam. Saya sarankan untuk menonaktifkan pelepasan dan menggunakan "Hasil" secara manual jika Anda menginginkan kejelasan itu. [Dan btw, masalah ini bukanlah tempat yang tepat untuk memunculkan hal seperti itu---desain ! bukanlah sesuatu yang Anda pertanyakan jadi ini murni pekerjaan masa depan.]

Saya menyadari betapa melanggarnya, setidaknya tanpa senam yang signifikan, dan poin yang adil tentang itu menjadi pekerjaan yang benar-benar di masa depan. Dan ya, saya cukup senang dengan apa yang direncanakan! sejauh ini, sejauh itu pergi. :)

@nikomatsakis Mengenai masalah yang tertunda, keduanya dapat dicentang

  • Pembersihan kode dari #35162, atur ulang typeck sedikit ke jenis utas melalui posisi pengembalian alih-alih memanggil expr_ty
  • Selesaikan perawatan tipe yang tidak berpenghuni dalam korek api

Saya akan memulai retakan lain pada yang ini:

  • Bagaimana cara menerapkan peringatan untuk orang yang mengandalkan (): Trait fallback di mana perilaku itu mungkin berubah di masa depan?

Saya berencana menambahkan tanda ke TyTuple untuk menunjukkan bahwa itu dibuat dengan default variabel tipe divergen, kemudian memeriksa tanda itu dalam pemilihan sifat.

@canndrew

Saya berencana menambahkan tanda ke TyTuple untuk menunjukkan bahwa itu dibuat dengan default variabel tipe divergen, kemudian memeriksa tanda itu dalam pemilihan sifat.

OK bagus!

Yah, mungkin hebat. =) Kedengarannya agak rumit untuk memiliki dua tipe yang setara secara semantik ( () vs () ) yang berbeda dengan cara itu, tetapi saya tidak dapat memikirkan cara yang lebih baik. =( Dan banyak kode yang harus disiapkan untuk itu bagaimanapun juga berkat daerah.

Maksud saya adalah saya menambahkan bool ke TyTuple

TyTuple(&'tcx Slice<Ty<'tcx>>, bool),

yang menunjukkan bahwa kita harus meningkatkan peringatan pada tupel (unit) ini jika kita mencoba untuk memilih ciri-ciri tertentu di atasnya. Ini lebih aman daripada pendekatan asli saya untuk menambahkan TyDefaultedUnit .

Mudah-mudahan kita hanya perlu menjaga dorongan itu untuk satu siklus peringatan.

Mengenai masalah uninitialized / transmute / MaybeUninitialized Saya pikir tindakan yang masuk akal adalah:

  • Tambahkan MaybeUninitialized ke pustaka standar, di bawah mem
  • Tambahkan tanda centang pada uninitialized bahwa tipenya diketahui berpenghuni. Naikkan peringatan sebaliknya dengan catatan yang mengatakan bahwa ini akan menjadi kesalahan yang sulit di masa depan. Sarankan menggunakan MaybeUninitialized sebagai gantinya.
  • Tambahkan tanda centang pada transmute bahwa tipe "ke" hanya tidak berpenghuni jika tipe "dari" juga. Demikian pula memunculkan peringatan yang akan menjadi kesalahan keras.

Pikiran?

Tampaknya ada beberapa ketidaksepakatan tentang semantik &! . Setelah #39151, dengan mengaktifkan gerbang fitur never_type , ia diperlakukan sebagai tidak berpenghuni dalam pertandingan.

Jika sifat otomatis impls untuk ! tidak akan ada, setidaknya menerapkan sifat yang sesuai dari std akan sangat membantu. Satu batasan besar dari void::Void adalah ia hidup di luar std dan itu berarti blanket impl<T> From<Void> for T tidak mungkin dan itu membatasi penanganan kesalahan.

Saya pikir setidaknya From<!> harus diterapkan untuk semua jenis dan Error , Display dan Debug harus diimplementasikan untuk ! .

Saya pikir setidaknya From<!> harus diterapkan untuk semua jenis

Sayangnya ini bertentangan dengan From<T> for T impl.

Sayangnya ini bertentangan dengan impl From<T> for T .

Setidaknya sampai spesialisasi impl kisi diimplementasikan.

Poin bagus. Saya berharap untuk melihatnya dilakukan suatu hari nanti.

Haruskah [T; 0] subtipe [!; 0] dan &[T] subtipe &[!] ? Tampaknya intuitif bagi saya bahwa jawabannya seharusnya "ya", tetapi implementasi saat ini berpikir sebaliknya.

[!; 0] dihuni dan begitu juga &[!] jadi saya akan mengatakan tidak. Selain itu, kami tidak menggunakan subtipe kali ini.

Baik [!; 0] dan &[!] tidak boleh tidak berpenghuni, kedua tipe dapat menerima nilai [] (atau &[] ).

Tidak ada yang mengatakan mereka atau seharusnya tidak berpenghuni.

let _: u32 = unsafe { mem::transmute(return) };
Pada prinsipnya ini bisa baik-baik saja, tetapi kompiler mengeluh "transmutasi antara 0 bit dan 32 bit".

Transmutasi ! -> () diperbolehkan. Saya tidak punya alasan khusus untuk menunjukkan hal ini; itu inkonsistensi, tapi saya tidak bisa memikirkan masalah praktis atau teoretis dengannya.

Saya tidak akan mengharapkan subtipe dan akan menentangnya dengan alasan menghindari kerumitan segala macam logika dalam kompiler. Saya tidak ingin subtipe yang tidak terkait dengan wilayah atau pengikatan wilayah, pada dasarnya.

Apakah mungkin untuk mengimplementasikan Fn , FnMut dan FnOnce untuk ! dengan cara yang generik untuk semua jenis input dan output?

Dalam kasus saya, saya memiliki pembuat generik yang dapat mengambil fungsi, yang akan dipanggil saat membangun:

struct Builder<F: FnOnce(Input) -> Output> {
    func: Option<F>,
}

Karena func adalah Option , konstruktor yang menggunakan None tidak dapat menyimpulkan tipe F . Oleh karena itu implementasi fn new harus menggunakan Bang:

impl Builder<!> {
    pub fn new() -> Builder<!> {
        Builder {
            func: None,
        }
    }
}

Fungsi func builder akan terlihat seperti ini untuk dipanggil baik pada Builder<!> dan Builder<F> :

impl<F: FnOnce(Input) -> Output> Builder<F> {
    pub fn func<F2: FnOnce(Input) -> Output>(self, func: F) -> Builder<F2> {
        Builder {
            func: func,
        }
    }
}

Sekarang Masalahnya: Saat ini sifat Fn* tidak diterapkan untuk ! . Selain itu, Anda tidak dapat memiliki tipe terkait generik untuk sifat (misalnya Output dalam Fn ). Jadi apakah mungkin untuk mengimplementasikan keluarga sifat Fn* untuk ! dengan cara yang generik untuk semua jenis input dan output?

Itu terdengar kontradiktif. Bukankah <! as Fn>::Output perlu diselesaikan ke satu jenis?

<! as Fn>::Output adalah ! , bukan?

Secara teori <! as Fn>::Output seharusnya ! , tetapi pemeriksa tipe saat ini ingin tipe terkait sama persis: https://is.gd/4Mkxfm

@SimonSapin Memang perlu diselesaikan ke satu jenis, itulah sebabnya saya mengajukan pertanyaan saya. Dari sudut pandang bahasa, itu adalah perilaku yang sepenuhnya benar. Tetapi dari sudut pandang perpustakaan, penggunaan ! dalam konteks fungsi generik akan sangat terbatas jika perilaku saat ini dipertahankan.

Haruskah kode berikut:

#![feature(never_type)]

fn with_print<T>(i:i32, r:T) -> T {
    println!("{}", i);
    r
}


fn main() {
    #[allow(unreachable_code)]
    *with_print(10,&return)
}

mencetak 10 ? Saat ini tidak mencetak apa pun. Atau solusi lain yang akan menunda return hingga dicetak?

Maksud saya, seperti yang saya katakan sebelumnya, sebenarnya ada dua jenis tipe ! : satu malas dan satu lagi bersemangat. Notasi yang biasa menunjukkan yang bersemangat, tetapi kadang-kadang kita mungkin membutuhkan yang malas.


Karena @RalfJung tertarik dengan versi sebelumnya yang menggunakan &return , saya menyesuaikan kode lagi. Dan lagi, maksud saya adalah kita harus bisa menunda return nanti. Kita bisa menggunakan penutupan di sini tapi saya hanya bisa menggunakan return , bukan break atau continue dll.

Misalnya, akan lebih baik jika kita bisa menulis

#![feature(never_type)]

fn with_print<T>(i:i32, r:T) -> T {
    println!("{}", i);
    r
}


fn main() {
    for i in 1..10 {
        if i==5 { with_print(i, /* delay */break) }
        with_print(i, i);
    }
}

dengan jeda tertunda hingga 5 dicetak.

@earthengine Tidak-- yang seharusnya tidak mencetak apa pun. ! tidak berpenghuni, yang berarti tidak ada nilai tipe ! dapat dibuat. Jadi jika Anda memiliki referensi ke nilai tipe ! , Anda berada di dalam blok kode mati.

Alasan return memiliki tipe ! adalah karena divergen. Kode yang menggunakan hasil ekspresi return tidak akan pernah bisa dieksekusi karena return tidak pernah "selesai".

oO Saya tidak tahu Anda bisa menulis &return . Itu tidak masuk akal, mengapa Anda harus diizinkan untuk mengambil alamat return ?^^

Tapi, bagaimanapun, @earthengine dalam kode Anda, argumen dievaluasi sebelum quit_with_print dipanggil. Mengevaluasi argumen ke-2 menjalankan return , yang menghentikan program. Ini seperti menulis sesuatu seperti foo({ return; 2 }) -- foo tidak pernah dieksekusi.

@RalfJung return adalah ekspresi dari tipe ! , seperti break atau continue . Memiliki tipe, tetapi tipenya tidak berpenghuni.

! telah diimplementasikan dan bekerja di malam hari selama satu tahun sekarang. Saya ingin tahu apa yang perlu dilakukan untuk memindahkannya ke arah stabilisasi.

Satu hal yang belum diselesaikan adalah apa yang harus dilakukan tentang intrinsik yang dapat mengembalikan ! (yaitu uninitialized , ptr::read dan transmute ). Untuk uninitialized , terakhir saya mendengar ada konsensus bahwa itu harus ditinggalkan demi tipe MaybeUninit dan kemungkinan masa depan &in , &out , &uninit referensi. Tidak ada RFC untuk ini, meskipun ada RFC sebelumnya di mana saya mengusulkan untuk menghentikan uninitialized hanya untuk tipe yang tidak menerapkan sifat Inhabited . Mungkin RFC itu harus dihapus dan diganti dengan "deprecate uninitialized " dan "add MaybeUninit " RFC?

Untuk ptr::read , saya pikir tidak apa-apa untuk meninggalkannya sebagai UB. Ketika seseorang menelepon ptr::read mereka menyatakan bahwa data yang mereka baca valid, dan dalam kasus ! itu pasti tidak. Mungkin seseorang memiliki pendapat yang lebih bernuansa tentang ini?

Memperbaiki transmute itu mudah - buat saja kesalahan untuk mentransmisikan ke tipe yang tidak berpenghuni (bukan ICE seperti saat ini). Ada PR untuk memperbaikinya tetapi ditutup dengan alasan bahwa kami masih membutuhkan ide yang lebih baik tentang bagaimana memperlakukan data yang tidak diinisialisasi.

Jadi di mana kita dengan ini saat ini? (/cc @nikomatsakis)? Apakah kita siap untuk melanjutkan dengan menghentikan uninitialized dan menambahkan tipe MaybeUninit ? Jika kita membuat intrinsik ini panik ketika dipanggil dengan ! , apakah itu akan menjadi ukuran stop-gap yang cocok yang akan memungkinkan kita untuk menstabilkan ! ?

Ada juga masalah tertunda yang terdaftar:

Sifat apa yang harus kita terapkan untuk ! ? PR awal #35162 termasuk Ord dan beberapa lainnya. Ini mungkin lebih merupakan masalah T-libs, jadi saya menambahkan tag itu ke masalah ini.

Saat ini ada pilihan yang cukup mendasar: PartialEq , Eq , PartialOrd , Ord , Debug , Display , Error . Selain Clone , yang pasti harus ditambahkan ke daftar itu, saya tidak dapat melihat yang lain yang sangat penting. Apakah kita perlu memblokir stabilisasi ini? Kita bisa menambahkan lebih banyak impls nanti jika kita mau.

Bagaimana cara menerapkan peringatan untuk orang yang mengandalkan fallback (): Trait mana perilaku itu mungkin berubah di masa mendatang?

Peringatan resolve_trait_on_defaulted_unit diimplementasikan dan sudah stabil.

Semantik yang diinginkan untuk ! dalam paksaan (#40800)
Variabel tipe mana yang harus mundur ke ! (#40801)

Ini tampak seperti pemblokir nyata. @nikomatsakis : Apakah Anda punya ide konkret tentang apa yang harus dilakukan tentang ini yang hanya menunggu untuk diimplementasikan?

Apakah masuk akal jika ! juga mengimplementasikan Sync dan Send ? Ada beberapa kasus di mana jenis kesalahan memiliki batas Sync dan/atau Send , di mana akan berguna untuk menggunakan ! sebagai "tidak dapat gagal".

@canndrew Saya tidak setuju dengan melarangnya dalam kode yang tidak aman. Peti unreachable menggunakan transmute untuk membuat tipe yang tidak berpenghuni dan dengan demikian mengisyaratkan pengoptimal bahwa cabang kode tertentu tidak akan pernah muncul. Ini berguna untuk optimasi. Saya berencana untuk membuat peti saya sendiri menggunakan teknik tersebut dengan cara yang menarik.

@Kixunil bukankah lebih eksplisit menggunakan https://doc.rust-lang.org/beta/std/intrinsics/fn.unreachable.html di sini?

@RalfJung akan, tapi itu tidak stabil. Mengingatkan saya bahwa melarang transmutasi menjadi tipe yang tidak berpenghuni akan menjadi perubahan besar.

@jsgf Maaf, ya, ! juga menyiratkan semua sifat penanda ( Send , Sync , Copy , Sized ).

@Kixunil Hmm, sayang sekali. Akan jauh lebih baik untuk menstabilkan intrinsik unreachable .

Bukan pemblokir stabilisasi (karena kinerja, bukan semantik), tetapi tampaknya Result<T, !> tidak menggunakan ! tidak berpenghuni dalam tata letak enum atau cocokkan codegen: https://github.com /rust-lang/rust/issues/43278

Ini adalah ide yang kabur sehingga saya hampir merasa bersalah karena menjatuhkannya di sini, tetapi:

Mungkinkah kapur dapat membantu menjawab beberapa pertanyaan yang lebih sulit sehubungan dengan implementasi !: Trait ?

@ExpHP Saya rasa tidak. Implementasi otomatis !: Trait untuk sifat yang tidak memiliki tipe/konstituen terkait dan hanya metode non-statis yang diketahui baik. Ini hanya masalah apakah kita ingin melakukannya atau tidak. Implementasi otomatis untuk sifat-sifat lain tidak terlalu masuk akal karena kompiler perlu memasukkan nilai arbitrer untuk tipe/konstituen terkait dan menciptakan impls metode statis arbitrernya sendiri.

Sudah ada resolve-trait-on-defaulted-unit lint di rustc. Haruskah item yang sesuai dicentang?

@canndrew Apakah tidak masuk ! ?

@taralx itu tidak buruk, tetapi itu akan mencegah kode yang valid berfungsi. Perhatikan bahwa kode berikut dikompilasi hari ini:

trait Foo {
    type Bar;
    fn make_bar() -> Self::Bar;
}

impl Foo for ! {
    type Bar = (i32, bool);
    fn make_bar() -> Self::Bar { (42, true) }
}

@lfairy Itu tergantung pada pendapat Anda tentang apa itu kode "valid". Bagi saya, saya akan dengan senang hati menerima bahwa kode yang Anda berikan "tidak valid", karena Anda seharusnya tidak bisa mendapatkan apa pun dari ! .

@earthengine Saya tidak mengerti maksud yang Anda coba ! tidak ada hubungannya dengan itu.

Tidak ada alasan mengapa Anda tidak bisa mendapatkan apa pun dari ! type . Anda tidak bisa mendapatkan apa pun dari ! meskipun, yang mengapa metode non-statis pada ! bisa diturunkan tanpa memaksa keputusan pada programmer.

Saya pikir aturan yang paling mudah diingat adalah mengimplementasikan sifat apa pun yang memiliki tepat 1 implementasi yang valid (tidak termasuk yang menambahkan lebih banyak perbedaan daripada ! s dalam lingkup yang sudah menyebabkan).

Apa pun yang kita lakukan harus sama atau lebih konservatif dengan itu (yang tidak sesuai dengan implikasi otomatis).

@ Ericson2314 Saya tidak mengerti apa artinya, bisakah Anda memberikan beberapa contoh?

@SimonSapin Aturan "tidak ada lagi divergensi" berarti tidak ada panic!() atau loop { } , tetapi v: ! yang sudah ada dalam cakupan tidak masalah. Dengan ekspresi seperti yang dikesampingkan, banyak sifat hanya memiliki satu kemungkinan impl untuk ! jenis-cek. (Orang lain mungkin memiliki kontrak informal yang mengesampingkan semua kecuali 1 impl, tetapi kami tidak dapat menanganinya secara otomatis.)

// two implementations: constant functions returning true and false.
// And infinitely more with side effects taken into account.
trait Foo { fn() -> bool }
// Exactly one implementation because the body is unreachable no matter what.
trait Bar { fn(Self) -> Self }

Secara matematis, ! adalah "elemen awal", yang berarti bahwa untuk tanda tangan tipe yang memiliki ! dalam argumennya, hanya ada satu implementasi, yaitu, semua implementasi sama -- ketika diamati dari luar. Ini karena mereka semua tidak bisa dipanggil.

Apa yang sebenarnya menghalangi ini dari stabilisasi? Apakah hanya situasi mem::uninitialized atau sesuatu yang lain?

@arielb1 : Saya pikir itu #40800 dan #40801 juga. Apakah Anda tahu apa statusnya pada ini?

Dokumen apa yang telah ditulis tentang ini, atau apakah orang berencana untuk menulis untuk ini? Dengan penambahan terbaru ke halaman tipe primitif di std docs (#43529, #43560), seharusnya ! ketik dapatkan halaman di sana? Referensi mungkin juga harus diperbarui, jika belum.

Apakah ada rencana untuk memungkinkan menentukan bahwa asm divergen sehingga kami dapat menulis hal-hal seperti?

#[naked]
unsafe fn error() -> !{
  asm!("hlt");
}

Tanpa harus menggunakan loop{} untuk menenangkan pemeriksa tipe?

Sunting: Meskipun saya kira menggunakan core::intrinsics::unreachable dapat diterima dalam kode tingkat yang sangat rendah.

@Eroc33 ketika ! adalah tipe, Anda dapat melakukan ini:

#[naked]
unsafe fn error() -> ! {
  asm!("hlt");
  std::mem::uninitialized()
}

Sampai saat itu Anda dapat melakukan ini:

#[naked]
unsafe fn error() -> ! {
  asm!("hlt");

  enum Never {}
  let never: Never = std::mem::uninitialized();
  match never {}
}

@Kixunil ! adalah tipe pada malam hari yang perlu Anda gunakan asm! . Ini biasanya dilakukan sebagai:

#[naked]
unsafe fn error() -> ! {
    asm!("hlt");
    std::intrinsics::unreachable();
}

@Kixunil Itu dapat dilakukan dengan lebih mudah menggunakan std::intrinics::unreachable() yang secara tepat menentukan bahwa kode sebelumnya divergen.

@Kixunil Dari diskusi di atas std::mem::uninitialized dapat kembali ! tampaknya dapat berubah meskipun? Kecuali saya salah paham?

Dari diskusi di atas std::mem::uninitialized dapat mengembalikan ! tampaknya dapat berubah? Kecuali saya salah paham?

Saya pikir uninitialized pada akhirnya akan ditinggalkan sepenuhnya dan, sebagai stop gap, akan dibuat panik saat runtime ketika digunakan dengan ! (meskipun itu tidak akan mempengaruhi kasus ini).

Saya juga berpikir kita perlu menstabilkan std::intrinsics::unreachable di suatu tempat di libcore dan beri nama unchecked_unreachable atau sesuatu untuk membedakannya dari makro unreachable! . Aku sudah lama ingin menulis RFC untuk itu.

@eddyb Ah, ya, lupa bahwa asm!() tidak stabil, jadi std::intrinsics::unreachable() mungkin bisa digunakan juga.

@tbu- Tentu, saya lebih suka yang itu daripada membuat tipe yang tidak berpenghuni. Masalahnya adalah, itu tidak stabil, jadi jika entah bagaimana tidak mungkin untuk secara tidak aman menandai cabang kode sebagai tidak dapat dijangkau, itu tidak hanya akan merusak kode yang ada, tetapi juga membuatnya tidak dapat diperbaiki. Saya pikir hal seperti itu akan sangat merusak reputasi Rust (terutama mengingat pengembang utama dengan bangga mengklaimnya stabil). Sama seperti saya menyukai Rust, hal seperti itu akan membuat saya mempertimbangkannya kembali sebagai bahasa yang berguna.

@ Eroc33 pemahaman saya adalah bahwa beberapa orang menyarankan untuk melakukannya, tetapi itu hanya saran, bukan keputusan yang pasti. Dan saya menentang saran seperti itu karena itu akan merusak kompatibilitas, memaksa Rust menjadi 2.0. Saya tidak berpikir ada salahnya mendukung itu. Jika orang khawatir tentang bug, lint akan lebih tepat.

@canndrew Mengapa menurut Anda uninitialized akan ditinggalkan? Aku tidak percaya hal seperti itu. Saya merasa sangat berguna, jadi kecuali ada pengganti yang sangat bagus, saya tidak melihat alasan untuk melakukannya.

Terakhir, saya ingin menegaskan kembali bahwa saya setuju bahwa intrinsics::unreachable() lebih baik daripada peretasan saat ini. Karena itu, saya menentang pelarangan atau bahkan mencela peretasan itu sampai ada pengganti yang memadai.

Pengganti untuk yang tidak diinisialisasi adalah union { !, T }. Anda bisa menjadi tidak terjangkau oleh
ptr::read::<*const !>()ing dan banyak cara serupa lainnya.

Pada 8 Agustus 2017 15:58, "Martin Habovštiak" [email protected]
menulis:

@eddyb https://github.com/eddyb Ah, ya, lupa asm itu!() adalah
tidak stabil, jadi std::intrinsics::unreachable() dapat digunakan juga.

@tbu- https://github.com/tbu- Tentu, saya lebih suka yang itu daripada
menciptakan tipe yang tidak berpenghuni. Masalahnya adalah, itu tidak stabil, jadi jika itu
entah bagaimana tidak mungkin untuk secara tidak aman menandai cabang kode sebagai tidak dapat dijangkau, itu tidak
hanya memecahkan kode yang ada, tetapi juga membuatnya tidak dapat diperbaiki. Saya pikir hal seperti itu
akan sangat merusak reputasi Rust (terutama mengingat pengembang utama
dengan bangga mengklaimnya stabil). Sebanyak aku mencintai Rust, hal seperti itu akan
membuat saya mempertimbangkan kembali itu menjadi bahasa yang berguna.

@Eroc33 https://github.com/eroc33 pemahaman saya adalah bahwa beberapa orang
menyarankan melakukannya, tapi itu hanya saran, bukan keputusan yang pasti. Dan saya
menentang saran tersebut karena akan merusak kompatibilitas,
memaksa Rust menjadi 2.0. Saya tidak berpikir ada salahnya mendukung itu.
Jika orang khawatir tentang bug, lint akan lebih tepat.

@canndrew https://github.com/canndrew Mengapa menurut Anda tidak diinisialisasi
akan ditinggalkan? Aku tidak percaya hal seperti itu. Saya merasa sangat berguna,
jadi kecuali ada pengganti yang sangat bagus, saya tidak melihat alasan untuk
melakukannya.

Akhirnya, saya ingin menegaskan kembali bahwa saya setuju bahwa intrinsik::unreachable()
lebih baik daripada peretasan saat ini. Karena itu, saya menentang larangan atau bahkan
menghentikan peretasan itu sampai ada pengganti yang memadai.


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

@nagisa Terima kasih! Sepertinya itu akan menyelesaikan masalah di tingkat teknis.

Apakah mungkin untuk mengukir subset dari ini sehingga ! dapat digunakan sebagai parameter tipe dalam tipe pengembalian? Sekarang jika Anda memiliki fungsi yang stabil

fn foo() -> ! { ··· }

Dan ingin menggunakan ? , Anda tidak dapat melakukan transformasi biasa ke

fn foo() -> io::Result<!> { ··· }

Apakah pertanyaan standar parameter tipe dan paksaan berduri memengaruhi kasus itu?

40801 Dapat dicentang.

Kita harus mencoba memperbaiki #43061 sebelum menstabilkan ! .

Saya tidak melihat masalah I-unsound terbuka yang menyebutkan never_type jadi saya mengajukan yang baru untuk ini: #47563. Hal-hal tampaknya mengasumsikan bahwa [!; 0] tidak berpenghuni tetapi saya dapat membuatnya dengan [] .

@dtolnay ditambahkan ke tajuk masalah

@canndrew bagaimana ! distabilkan (tidak termasuk aturan seputar apa yang lengkap). Saya agak kehilangan benang di mana kita berada, dan saya pikir itu membutuhkan seorang juara. Apakah Anda punya waktu untuk menjadi orang itu?

@nikomatsakis Saya yakin saya dapat menemukan waktu untuk mengerjakan hal ini. Apa yang perlu dilakukan dengan tepat? Apakah hanya bug yang terkait dengan masalah ini?

Sepertinya @varkor sudah membuka PR untuk memperbaiki masalah yang tersisa (luar biasa!). Sejauh yang saya bisa lihat, satu-satunya hal yang tersisa untuk dilakukan adalah memutuskan apakah kita senang dengan ciri-ciri yang saat ini diterapkan dan untuk memindahkan/mengganti nama gerbang fitur sehingga hanya mencakup perubahan pencocokan pola yang lengkap.

Meskipun hal lain: Apakah kita ingin membuat mem::uninitialized::<!>() panik saat run-time (saat ini menyebabkan UB)? Atau haruskah kita meninggalkan perubahan semacam itu untuk saat ini? Saya tidak up-to-speed dengan apa yang terjadi dengan hal-hal pedoman kode yang tidak aman.

Saya pikir rencananya masih https://github.com/rust-lang/rfcs/pull/1892 , yang baru saja saya perhatikan Anda tulis. :)

@RalfJung Apakah ada sesuatu yang khusus memblokir itu? Saya bisa menulis PR hari ini yang menambahkan MaybeUninit dan tidak menggunakan uninitialized .

@canndrew Karena banyak proyek ingin mendukung ketiga saluran rilis dan uninitialized tersedia di stable, lebih baik untuk hanya mulai memancarkan peringatan penghentian di Nightly setelah penggantian tersedia di saluran stabil. Penghentian lunak melalui komentar dokumen tidak masalah.

Saya telah membuat PR untuk menstabilkan ! : https://github.com/rust-lang/rust/pull/47630. Saya tidak tahu apakah kita sudah siap untuk menggabungkannya. Setidaknya kita harus menunggu untuk melihat apakah PR @varkor memperbaiki masalah terbuka yang tersisa.

Saya juga berpikir kita harus mengunjungi kembali https://github.com/rust-lang/rfcs/pull/1699 sehingga orang dapat mulai menulis impls sifat elegan untuk ! .

@canndrew : Meskipun RFC itu tidak diterima, kelihatannya sangat mirip dengan proposal di https://github.com/rust-lang/rust/issues/20021.

https://github.com/rust-lang/rust/issues/36479 mungkin harus ditambahkan ke tajuk masalah juga.

@varkor sangat mirip. Dengan pekerjaan kode Anda yang tidak dapat dijangkau, apakah Anda melihat ada masalah dengan kode seperti ini yang sekarang ditandai sebagai tidak dapat dijangkau?:

impl fmt::Debug for ! {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        *self    // unreachable!
    }
}

Karena itu akan membutuhkan penerimaan semacam proposal seperti itu.

@canndrew sesuatu yang saya setelah RFC, mencoba meringkas dalam bentuk daftar-peluru fitur menonjol dari apa yang sedang distabilkan tetapi juga apa yang tidak .

Bagian dari ini akan menjadi petunjuk untuk menguji kasus yang menunjukkan perilaku yang dimaksud.

Apakah Anda pikir Anda bisa mencoba menggambar hal seperti itu? Kita bisa mengobrol sedikit bersama jika Anda suka tentang semacam "garis besar" -- atau saya mungkin bisa mencoba membuat sketsanya.

Pertanyaan terkait: haruskah kita mencoba menyelesaikan https://github.com/rust-lang/rust/issues/46325 sebelum kita stabil? Mungkin itu tidak masalah.

@nikomatsakis Saya memilih untuk tidak menunggu peringatan apa pun, hanya masalah yang harus diselesaikan. Itu tidak berbahaya. Jika tidak ada kekhawatiran lebih lanjut yang muncul, saya pikir kita harus melanjutkan dan menstabilkannya.

@canndrew : Saya tidak berpikir saya benar-benar melihat kasus seperti itu, meskipun saya benar-benar berpikir dapat menghilangkan implementasi yang tidak mungkin akan sangat dibutuhkan ketika ! distabilkan.

@nikomatsakis

Apakah Anda pikir Anda bisa mencoba menggambar hal seperti itu? Kita bisa mengobrol sedikit bersama jika Anda suka tentang semacam "garis besar" -- atau saya mungkin bisa mencoba membuat sketsanya.

Saya bisa menulis draf setidaknya dan Anda bisa memberi tahu saya jika itu yang Anda pikirkan. Saya akan mencoba menyelesaikannya dalam beberapa hari ke depan.

@nikomatsakis Sesuatu seperti ini?

Masalah ringkasan - stabilisasi !

Apa yang sedang distabilkan

  • ! sekarang merupakan tipe yang lengkap dan sekarang dapat digunakan dalam posisi tipe apa pun (mis. RFC 1216 ). Jenis ! dapat memaksa ke jenis lain, lihat https://github.com/rust-lang/rust/tree/master/src/test/run-fail/adjust_never.rs untuk contoh.
  • Inferensi tipe sekarang akan membuat variabel tipe tidak dibatasi default ke ! alih-alih () . Serat resolve_trait_on_defaulted_unit telah dihentikan. Contoh di mana ini muncul adalah jika Anda memiliki sesuatu seperti:

    // We didn't specify the type of `x`. Under some circumstances, type inference
    // will pick a type for us rather than erroring
    let x = Deserialize::deserialize(data);
    

    Di bawah aturan lama ini akan deserialize a () , sedangkan di bawah aturan baru itu akan deserialize a ! .

  • Gerbang fitur never_type stabil, meskipun beberapa perilaku yang digunakan untuk gerbang sekarang tinggal di belakang gerbang fitur exhaustive_patterns (lihat di bawah).

Apa yang tidak distabilkan

haruskah kita mencoba menyelesaikan #46325 sebelum kita stabil?

Sebagus apa pun membersihkan setiap ujung yang longgar seperti ini, itu tidak benar-benar tampak seperti penghalang.

@canndrew

Sesuatu seperti ini?

Ya, terima kasih! Itu hebat.

Hal utama yang hilang adalah petunjuk untuk menguji kasus yang menunjukkan bagaimana ! berperilaku. Penontonnya harus tim lang atau orang lain yang mengikuti dengan cermat, menurut saya, itu tidak benar-benar menargetkan orang-orang "bertujuan umum". Jadi misalnya saya ingin beberapa contoh tempat di mana paksaan adalah legal, atau di mana ! dapat digunakan. Saya juga ingin melihat testis yang menunjukkan kepada kita bahwa pencocokan pola yang lengkap (tanpa mengaktifkan gerbang fitur) masih berfungsi. Ini harus menjadi petunjuk ke dalam repositori.

@canndrew

Sebagus apa pun membersihkan setiap ujung yang longgar seperti ini, itu tidak benar-benar tampak seperti penghalang.

Nah, itu berarti kita akan mengaktifkan kode baru yang akan segera mengubah perilaku (mis., let x: ! = ... menurut saya akan berperilaku berbeda untuk beberapa ekspresi). Tampaknya terbaik untuk menyelesaikan jika kita bisa. Mungkin Anda bisa membuatnya menjadi hard error pada PR yang telah Anda buka dan kita bisa menyamakannya dengan kawah yang ada pada PR?

@nikomatsakis Saya telah memperbarui masalah ringkasan itu lagi dengan beberapa tautan dan contoh. Juga, maaf saya butuh beberapa saat untuk sampai ke sini, saya sangat sibuk minggu lalu.

Mungkin Anda bisa membuatnya menjadi hard error pada PR yang telah Anda buka dan kita bisa menyamakannya dengan kawah yang ada pada PR?

Selesai.

@rfcbot penggabungan

Saya mengusulkan agar kita menstabilkan tipe ! -- atau setidaknya sebagian darinya, seperti yang dijelaskan di sini . PR-nya di sini .

Satu bit data yang saya inginkan adalah kawah run. Saya sedang dalam proses rebasing https://github.com/rust-lang/rust/pull/47630 (karena @canndrew tidak menanggapi ping sekarang) sehingga kami bisa mendapatkan data itu.

Anggota tim @nikomatsakis telah mengusulkan untuk menggabungkan ini. Langkah selanjutnya adalah peninjauan oleh sisa tim yang ditandai:

  • [x] @Kimundi
  • [x] @alexcrichton
  • [ ] @aturon
  • [x] @cramertj
  • [x] @dtolnay
  • [x] @eddyb
  • [x] @nikomatsakis
  • [x] @nrc
  • [ ] @pnkfelix
  • [x] @sfackler
  • [x] @withoutboats

Tidak ada kekhawatiran saat ini terdaftar.

Setelah mayoritas pengulas menyetujui (dan tidak ada yang keberatan), ini akan memasuki periode komentar terakhirnya. Jika Anda menemukan masalah besar yang belum diangkat pada titik mana pun dalam proses ini, silakan angkat bicara!

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

Oh, beberapa hal yang baru saya ingat:

  • Kita harus mempertimbangkan gagasan untuk menstabilkan ini hanya di zaman baru . Secara khusus, perubahan aturan fallback tidak sesuai -- kawah akan memberi kita semacam batas bawah pada fallout, tapi itu hanya batas bawah. Tapi mungkin kita bisa menjaga aturan fallback lama kecuali Anda berada di zaman baru.
  • Kedua, saya percaya bahwa bagian dari rencana di sini juga memiliki beberapa pedoman tentang kapan waktu yang tepat untuk menerapkan suatu sifat untuk ! . TL;DR adalah tidak apa-apa jika metode dalam sifat tidak dapat digunakan tanpa terlebih dahulu memberikan nilai ! -- jadi menerapkan Clone untuk ! tidak apa-apa, saya pikir , tetapi menerapkan Default tidak. Dengan kata lain, jika menerapkan impl akan mengharuskan Anda untuk panic! tanpa pandang bulu, itu pertanda buruk. =)

@nikomatsakis Bisakah kita mengubah aturan fallback di zaman baru tetapi masih menjadikan ! sebagai jenis yang tersedia di zaman 2015?

@nikomatsakis

Kita harus mempertimbangkan gagasan untuk menstabilkan ini hanya di zaman baru .

Terakhir kali kami melakukan kawah (yang sudah lama sekali) dampak dari mengubah aturan mundur cukup minimal. Kami juga telah linting terhadap kode yang dapat dipengaruhi oleh perubahan untuk sementara waktu.

Kedua, saya percaya bahwa bagian dari rencana di sini juga memiliki beberapa pedoman tentang kapan waktu yang tepat untuk menerapkan suatu sifat untuk ! .

Ini disebutkan dalam dokumen untuk !

@SimonSapin

Bisakah kita mengubah aturan mundur di zaman baru tetapi tetap membuat ! sebagai tipe yang tersedia di era 2015?

Ya

@canndrew

Terakhir kali kami melakukan kawah (yang sudah lama sekali) dampak dari mengubah aturan mundur cukup minimal. Kami juga telah linting terhadap kode yang dapat dipengaruhi oleh perubahan untuk sementara waktu.

Ya. Mari kita lihat apa yang dikatakan kawah. Tapi, seperti yang saya katakan, kawah hanya memberi kita batas bawah -- dan ini semacam "perubahan elektif". Tetap saja, saya curiga Anda benar dan kami dapat "pergi" dengan mengubah ini tanpa banyak efek pada kode di alam liar.

TL;DR adalah bahwa tidak apa-apa jika metode dalam sifat tidak dapat digunakan tanpa terlebih dahulu memberikan nilai !

Itu hanya aturan normal - Anda menambahkan impl ketika Anda dapat menerapkannya dengan cara yang waras, di mana "menerapkannya dengan cara yang waras" tidak termasuk panik, tetapi menyertakan "ex falso" UB dengan adanya data yang tidak valid.

@arielb1 Ya, tetapi untuk beberapa alasan orang cenderung bingung tentang hal-hal seperti ini dengan adanya ! , jadi sepertinya perlu dipanggil secara eksplisit.

Mungkin akan membantu untuk memiliki metode aman fn absurd(x: !) -> ! yang didokumentasikan sebagai cara aman untuk mengekspresikan kode yang tidak dapat dijangkau atau lebih di suatu tempat di libstd? Saya pikir ada RFC untuk itu ... atau setidaknya masalah RFC: https://github.com/rust-lang/rfcs/issues/1385

@RalfJung Bukankah absurd hanya identity ? Mengapa berguna?

Ini tidak sama dengan intrinsik unsafe fn unreachable() -> ! yang tidak mengambil argumen apa pun dan perilakunya sangat tidak terdefinisi.

Yah, ya, kebanyakan begitu. Saya menggunakan tipe pengembalian ! dalam arti "tidak berakhir". (Jenis absurd adalah fn absurd<T>(x: !) -> T , tetapi itu juga tampaknya tidak berguna di Rust.)

Saya berpikir mungkin ini akan membantu dengan "orang cenderung bingung tentang hal-hal seperti ini di hadapan ! " -- memiliki beberapa dokumentasi di suatu tempat yang alasan "ex faalso" adalah suatu hal.

Sepertinya saya juga mendapat masalah yang salah ... Saya pikir di suatu tempat ada diskusi tentang fungsi "ex faalso" semacam itu. Sepertinya aku salah.

fn absurd<T>(x: !) -> T juga dapat ditulis match x {} , tetapi sulit untuk menemukan jika Anda belum pernah melihatnya. Setidaknya perlu ditunjukkan di rustdoc dan di buku.

tidak masuk akal(x: !) -> T juga bisa ditulis dengan x {}, tetapi sulit untuk menemukan jika Anda belum pernah melihatnya.

Benar, itu sebabnya saya menyarankan metode di suatu tempat di libstd. Tidak yakin di mana tempat terbaik adalah meskipun.

Hal yang menyenangkan tentang absurd adalah Anda dapat meneruskannya ke fungsi tingkat tinggi. Saya tidak yakin ini pernah diperlukan mengingat caranya! berperilaku wrt. penyatuan, meskipun.

fn absurd<T>(x: !) -> T juga dapat ditulis match x {}

Itu juga dapat ditulis hanya x (AFAIK) - Anda hanya perlu match jika Anda memiliki enum kosong.

Fungsi absurd(x: !) -> T akan berguna untuk meneruskan ke fungsi tingkat tinggi. Saya membutuhkan ini (misalnya) ketika merantai futures, salah satunya memiliki tipe kesalahan ! dan jenis kesalahan lainnya T . Meskipun ekspresi tipe ! dapat memaksa menjadi T itu tidak berarti bahwa ! dan T akan bersatu, jadi terkadang Anda masih perlu melakukannya secara manual mengkonversi jenis.

Demikian pula, saya pikir Result -like types harus memiliki metode .infallible() yang mengubah Result<T, !> menjadi Result<T, E> .

Tipe seperti hasil harus memiliki metode .infallible() yang mengonversi Hasiluntuk Hasil.

Saya mengharapkan metode seperti itu memiliki tipe Result<T, !> -> T .

Saya akan mengharapkan metode seperti itu untuk memiliki tipe Hasil-> T

Bukankah ini sama dengan unwrap ? Kepanikan akan dioptimalkan karena kasus Err tidak dapat dijangkau.

@Amanieu Intinya adalah tidak ada kepanikan untuk memulai. Saya ingin memiliki serat panik, dan itu akan tersandung jika kita mengandalkan pengoptimalan. Bahkan tanpa lint, menggunakan unwrap menambahkan footgun selama refactor, mengurangi panik menjadi kode langsung sekali lagi.

unwrap dibaca seperti assert -- "run-time check dulu" (IIRC bahkan ada proposal untuk mengganti namanya tetapi mereka datang terlambat... sayangnya). Jika Anda tidak ingin dan memerlukan pemeriksaan run-time, infallible akan membuatnya eksplisit.

:bell: Ini sekarang memasuki periode komentar terakhir , sesuai ulasan di atas . :lonceng:

@rfcbot fcp batal

@aturon dan @pnkfelix belum mencentang kotak mereka.

@cramertj proposal dibatalkan.

Anggota tim @cramertj telah mengusulkan untuk menggabungkan ini. Langkah selanjutnya adalah peninjauan oleh sisa tim yang ditandai:

  • [x] @alexcrichton
  • [x] @aturon
  • [x] @cramertj
  • [x] @dtolnay
  • [x] @eddyb
  • [x] @nikomatsakis
  • [x] @nrc
  • [ ] @pnkfelix
  • [x] @sfackler
  • [x] @withoutboats

Tidak ada kekhawatiran saat ini terdaftar.

Setelah mayoritas pengulas menyetujui (dan tidak ada yang keberatan), ini akan memasuki periode komentar terakhirnya. Jika Anda menemukan masalah besar yang belum diangkat pada titik mana pun dalam proses ini, silakan angkat bicara!

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

:bell: Ini sekarang memasuki periode komentar terakhir , sesuai ulasan di atas . :lonceng:

Kami membahas ini dalam pertemuan hari ini. Saya tetap bingung apakah akan mengubah fallback sekarang atau hanya di zaman baru. Berikut berbagai poinnya.

Dampak yang diharapkan dari perubahan itu kecil. Jadi secara realistis ini tidak membuat banyak perbedaan. Anda dapat melihat analisis lengkapnya di sini , tetapi ringkasannya adalah:

Dan itu hanya menyisakan dua regresi aktual: oplog-0.2.0 (sebenarnya ketergantungannya bson-0.3.2 yang rusak) dan rspec-1.0.0-beta.4. Keduanya mengandalkan perilaku fallback lama, meskipun untungnya mereka rusak pada waktu kompilasi, bukan waktu berjalan. Saya akan mengirimkan PR untuk memperbaiki peti ini.

Tetapi secara teknis ini masih merupakan perubahan yang melanggar (dan sukarela). Tidak ada alasan khusus mengapa kita harus mengubah fallback untuk variabel tipe, kecuali bahwa () adalah pilihan yang sangat buruk dan sangat jarang bagi kode untuk mengandalkannya. Kami juga sudah memperingatkan tentang ini sejak lama.

Saya pikir ini lebih merupakan pertanyaan filosofi: di masa lalu, kami membuat perubahan kecil seperti ini karena kebutuhan. Tetapi sekarang setelah kita memiliki mekanisme epochal, ia menawarkan kita cara yang lebih berprinsip untuk melakukan transisi semacam itu tanpa merusak apa pun secara teknis. Mungkin layak untuk digunakan, hanya di luar prinsip. Di sisi lain, itu berarti terkadang menunggu bertahun-tahun untuk membuat perubahan seperti ini. Dan tentu saja kita harus mempertahankan kedua versi untuk selamanya, membuat spesifikasi bahasa dll menjadi jauh lebih kompleks.

Saya agak bingung tetapi saya berpikir tentang keseimbangan, setelah terhuyung-huyung, saya saat ini condong ke arah "mari kita ubah secara universal", seperti yang direncanakan.

Saya tahu saya bias, tetapi saya melihat perubahan perilaku mundur sebagai lebih dari perbaikan bug dibandingkan dengan hal-hal seperti dyn Trait dan catch .

Lebih jauh lagi, jika seseorang ingin melakukan ini dan berkata, "Ah ha! Bagaimanapun juga, Rust merusak kompatibilitas mundur! Janji stabilitas mereka ketika mencapai 1.0 adalah bohong!" kemudian diskusi yang sangat mendalam tentang masalah ini menunjukkan bahwa keputusan itu tidak dibuat dengan mudah, bahkan dengan dampak praktis yang dapat diabaikan.

OK, saya katakan mari kita ubah saja.

Maka, sebagai kompromi, saya ingin memberikan catatan bermanfaat untuk kesalahan yang memberi tahu orang-orang bahwa perilaku telah berubah -- tampaknya masuk akal. Idenya akan seperti ini:

  • Jika kami melihat kesalahan seperti !: Foo untuk beberapa sifat, dan sifat itu diterapkan untuk (): Foo , dan kami telah melakukan fallback (kami dapat memberi tahu kode pelaporan kesalahan fakta ini), maka kami menambahkan simpul tambahan.

Periode komentar terakhir sekarang selesai.

Apakah kotak centang yang tertunda di pos teratas belum dicentang? Apakah ada sesuatu yang dibatalkan dalam daftar itu?

@earthengine Saya tidak berpikir dua yang saya lihat sangat relevan - untuk item teratas, tentang set impls, saya kira itu sekarang diputuskan untuk saat ini.

Pada dasarnya menjadi set yang sangat minim.

@nikomatsakis Saya baru saja membuat ringkasan masalah di sini: https://github.com/rust-lang/rust/issues/48950

Apakah ada pertimbangan untuk membuat ! pola yang cocok dengan nilai dari tipe ! ? (Saya telah melakukan grep cepat melalui masalah ini dan masalah RFC asli, tetapi tidak menemukan sesuatu yang relevan).

Saya akan menemukan ini berguna untuk jenis seperti StreamFuture yang membuat jenis kesalahan gabungan dari kemungkinan jenis kesalahan ! . Dengan menggunakan pola ! dalam map_err , jika jenis kesalahan di beberapa titik di masa mendatang berubah, maka panggilan map_err akan berhenti mengkompilasi alih-alih berpotensi melakukan sesuatu yang berbeda.

Sebagai contoh, diberikan

let foo: Result<String, (!, String)> = Ok("hello".to_owned());

ini akan memungkinkan penulisan yang lebih eksplisit

let bar: Result<String, String> = foo.map_err(|(!, _)| unreachable!());

alih-alih berpotensi rawan kesalahan

let bar: Result<String, String> = foo.map_err(|_| unreachable!());

atau sedikit kurang rawan kesalahan

let bar: Result<String, String> = foo.map_err(|(e, _)| e);

EDIT: Membaca ulang https://github.com/rust-lang/rfcs/pull/1872 komentar menyebutkan memperkenalkan pola ! . Itu murni dalam konteks ekspresi match , saya tidak yakin apakah itu digeneralisasi ke argumen penutupan/fungsi.

@Nemo157

Apakah ada pertimbangan untuk membuat ! pola yang cocok dengan nilai ! Tipe?

Menarik. Saya pikir rencananya hanya untuk membiarkan Anda melepaskan senjata seperti itu, dalam hal ini -- jika jenisnya berubah di masa mendatang -- Anda masih akan mendapatkan kesalahan, karena kecocokan Anda sekarang tidak lagi lengkap. Masalah dengan pola ! adalah, jika cocok, berarti lengan tidak mungkin, yang agak canggung; kami ingin menjelaskannya, saya kira, dengan tidak mengharuskan Anda memberikan ekspresi untuk dieksekusi. Namun, mungkin bagus untuk memiliki cara untuk mengatakan secara eksplisit bahwa kasus ini tidak dapat terjadi.

Sebenarnya, memperluas penanganan pola yang tidak terjangkau bisa menjadi cara alternatif untuk menyelesaikannya. Jika lengan korek api terbukti tidak mungkin, mungkin kode di dalam lengan dapat diabaikan. Kemudian ketika Anda memiliki

fn foo() -> Result<String, String> {
    let bar: Result<String, (!, String)> = Ok("hello".to_owned());
    Ok(bar?)
}

ini masih akan berkembang seperti hari ini (_Saya tidak 100% yakin ini adalah ekspansi yang benar, tetapi tampaknya cukup dekat_)

fn foo() -> Result<String, String> {
    let bar: Result<String, (!, String)> = Ok("hello".to_owned());
    Ok(match Try::into_result(bar) {
        Result::Ok(e) => e,
        Result::Err(e) => return Try::from_err(From::from(e)),
    })
}

tetapi cabang Result::Err tidak akan diperiksa jenisnya dan Anda tidak akan mendapatkan kesalahan the trait bound `std::string::String: std::convert::From<(!, std::string::String)>` is not satisfied Anda dapatkan hari ini.

Saya tidak berpikir itu kompatibel dengan apa yang diusulkan RFC 1872, karena ada lengan eksplisit yang melakukan pertandingan dangkal, itu tidak harus bergantung pada validitas nilai ! , dan berpotensi jalankan cabang terkait "dengan aman" selama cabang itu tidak menggunakan e.0 sama sekali.

! adalah tipe positif, seperti bool , jadi agak tidak mungkin untuk mencocokkan pola dalam daftar argumen penutupan tanpa memperluas sintaks penutupan untuk mengizinkan banyak cabang. misalnya. mungkin kita bisa mengizinkan penutupan bool -> u32 untuk ditulis seperti (sintaks hipotetis jelek) [~ |false| 23, |true| 45 ~] -> u32 , lalu penutupan dari jenis kosong apa pun ke u32 dapat dengan mudah ditulis [~ ~] -> u32 .

wrt contoh asli Anda, Anda dapat menulis foo.map_err(|_: (!, _)| unreachable!()) meskipun saya lebih suka foo.map_err(|(e, _)| e) karena menghindari menggunakan unreachable! .

! adalah tipe positif

Apakah yang Anda maksud: tipe positif

agak tidak mungkin untuk mencocokkan pola dalam daftar argumen penutupan tanpa memperluas sintaks penutupan untuk memungkinkan banyak cabang

Daftar argumen sudah mendukung sub-pola yang tak terbantahkan, misalnya

let foo: Result<String, ((), String)> = Ok("hello".to_owned());
let bar: Result<String, String> = foo.map_err(|((), s)| s);

Saya hanya akan mempertimbangkan pola ! sebagai pola tak terbantahkan yang cocok dengan semua 0 nilai dari tipe ! , sama seperti () adalah pola tak terbantahkan yang cocok dengan semua 1 nilai jenis () .

Saya hanya akan mempertimbangkan ! pattern sebagai pola tak terbantahkan yang cocok dengan semua 0 nilai ! type, sama dengan () adalah pola tak terbantahkan yang cocok dengan semua 1 nilai dari tipe ().

Yah, tapi 0 != 1. ;) Tidak ada gunanya bahkan menulis kode apa pun setelah mencocokkan pada ! -- jadi misalnya jika tipe argumennya adalah (!, i32) , maka |(!, x)| code_goes_here konyol karena kode itu sudah mati. Saya mungkin akan menulis |(x, _)| match x {} untuk membuatnya sangat eksplisit bahwa x adalah elemen dari ! . Atau, menggunakan fungsi absurd saya usulkan di atas , |(x, _)| absurd(x) . Atau mungkin |(x, _)| x.absurd() ?

Orang bisa membayangkan sintaks berbeda yang memungkinkan seseorang tidak menulis kode, seperti yang ditulis @canndrew di atas. match memiliki sejumlah cabang, jadi sangat mungkin untuk tidak memiliki cabang -- tetapi penutupan memiliki tepat satu kasus, jadi satu-satunya pola yang masuk akal adalah pola yang memiliki tepat 1 cara untuk dicocokkan. Bukan 0 cara.

Sayang sekali kami tidak dapat menambahkan impl<T> From<!> for T sekarang. (Ini tumpang tindih dengan From<T> for T , kecuali sesuatu yang khusus?) Kami mungkin tidak dapat menambahkannya nanti. (Dalam siklus rilis setelah ! telah distabilkan, beberapa peti mungkin menerapkan From<!> for SomeConcreteType .)

@SimonSapin poin bagus! Ini adalah blok bangunan yang sangat berguna untuk banyak hal, dan saya tidak ingin kehilangannya selamanya. Saya benar-benar akan mempertimbangkan peretasan satu kali untuk mengizinkan kedua instance. Mereka bertepatan secara semantik dalam kasus yang tumpang tindih, jadi tidak ada "inkoherensi operasional".

Saya benar-benar akan mempertimbangkan peretasan satu kali

Maka peretasan itu perlu mendarat dalam beberapa hari ke depan. (Atau di-backport selama beta.)

@SimonSapin Seberapa cepat/mudah peretasan seperti itu dapat ditambahkan? Akan sangat menyebalkan untuk tidak pernah bisa memiliki From<!> for T .

Atau, kita dapat dengan cepat memberikan peringatan agar tidak menerapkan From<!> for SomeConcreteType .

Saya tidak tahu, tergantung hacknya apa. Saya pikir spesialisasi sifat dapat mengaktifkan dua impls yang tumpang tindih jika ada yang ketiga untuk persimpangan, tetapi apakah itu membutuhkan sifat untuk "dikhususkan" secara publik?

Sayangnya, spesialisasi:

  • Akan membutuhkan From::from untuk diubah menjadi default fn , yang akan "terlihat" di luar std. Saya tidak tahu apakah kita ingin melakukan itu dalam sifat std selama spesialisasi tidak stabil.
  • Seperti yang diterima di RFC 1210, secara khusus tidak mendukung fitur bahasa yang saya pikirkan (disambiguasi dua impl yang tumpang tindih di mana tidak ada yang lebih spesifik daripada yang lain dengan menulis impl ketiga untuk persimpangan).

Di sini kedua impls melakukan hal yang sama persis. Jika kita hanya meretas mematikan pemeriksaan koherensi, akan sulit untuk menentukan impl mana yang digunakan, yang biasanya sangat tidak sehat, tetapi dalam kasus ini baik-baik saja. Mungkin membuat beberapa kepanikan dalam kompilasi mengharapkan misalnya satu hasil tetapi kita dapat meretasnya.

Dengan kata lain, saya pikir ini jauh lebih mudah daripada mempercepat spesialisasi apa pun, untungnya.

Apakah ada RFC yang ada untuk memformalkan ide "izinkan tumpang tindih sewenang-wenang ini jika semua impls yang terlibat tidak dapat dijangkau"? Sepertinya pendekatan yang menarik untuk dipertimbangkan.

Tidak yang saya tahu. Untuk menjadi liontin, impl itu sendiri tidak dapat dijangkau, melainkan metode di dalamnya semua tidak dapat dipanggil (termasuk tidak ada tipe atau konstanta terkait karena selalu "dapat dipanggil"). Saya kira saya dapat dengan cepat menulis satu jika dianggap peretasan diblokir pada hal seperti itu.

Sebut saja masalah #49593 yang mungkin menyebabkan ! untuk mendapatkan un-stabil lagi untuk findability yang lebih baik.

Saya ingin tahu impls apa lagi selain T: From<!> tidak dapat ditambahkan setelah ! distabilkan. Kita mungkin harus mencoba untuk menangani semua impls yang masuk akal tersebut sebelum menstabilkan, berbeda dari impls untuk ! pada umumnya, di mana tidak ada ketergesaan seperti itu.

Posting silang dari komentar ini :

Saya bertanya-tanya -- apa contoh klasik mengapa fallback ke () "harus" diubah? Artinya, jika kita terus mundur ke (), tetapi masih menambahkan !, itu pasti akan menghindari masalah semacam itu -- dan jatuh kembali ke ! tidak optimal, mengingat bahwa ada kasus di mana fallback terjadi pada variabel tipe yang akhirnya memengaruhi kode "langsung" (maksudnya tentu saja sebaliknya, tetapi sulit untuk mencegah kebocoran, seperti yang kami temukan).

Ada beberapa kemunduran sebagai akibat dari perubahan itu dan saya harus mengatakan bahwa saya tidak sepenuhnya nyaman dengannya:

(Menominasikan untuk diskusi tim lang seputar poin ini.)

Selama pertemuan, kami mundur sedikit beberapa opsi di sini -- Saya merasa enggan untuk membuat perubahan, terutama karena mengubah semantik kode yang ada dalam beberapa kasus sudut, dan tidak jelas bahwa aturan baru lebih baik . Tetapi sebagian besar saya pikir kami memutuskan bahwa akan sangat bagus untuk mencoba dan mengumpulkan pro/kontra setiap desain lagi sehingga kami dapat berdiskusi lebih lengkap. Saya ingin melihat daftar kasus penggunaan serta jebakan. @cramertj menyebutkan keinginan mereka untuk mundur dalam kasus varian -- misalnya, Ok(33) memiliki tipe yang jatuh kembali ke Result<i32, !> , alih-alih membuat kesalahan seperti yang akan dilakukan hari ini; Saya pikir mereka kemudian mengatakan bahwa menjaga fallback dari return sebagai () akan tidak konsisten dengan perubahan seperti itu (walaupun ortogonal), dan mungkin menciptakan konflik di jalan. Ini adil.

Tantangan dengan Err feedback (#40801) adalah bahwa ada juga kasus Edge di sana, seperti:

let mut x = Ok(22);
x = Err(Default::default());

di mana jenis x akhirnya disimpulkan menjadi Result<T, !> .

Saya teringat akan rencana terpisah yang harus saya coba dan lihat apakah kami dapat menentukan (dan mungkin memperingatkan? atau kesalahan?) apakah ! fallback pernah "terpengaruh" kode langsung -- ini terbukti cukup sulit, meskipun tampaknya seperti kita dapat membuat banyak kasus dalam praktik (misalnya, kasus itu, dan mungkin kasus @SimonSapin ).

Ada juga diskusi apakah kita bisa menghapus fallback ini sama sekali di edisi baru. Saya menduga itu akan merusak banyak makro, tetapi mungkin patut dicoba?

FYI, fitur ini menyebabkan regresi di 1.26: #49932

(Menyetel label per https://github.com/rust-lang/rust/issues/35121#issuecomment-368669041.)

Haruskah item daftar periksa tentang paksaan menjadi ! mengarah ke https://github.com/rust-lang/rust/issues/50350 alih-alih merujuk ke masalah ini?

Jika saya ingin ini distabilkan sesegera mungkin, di mana tempat terbaik untuk mengarahkan energi saya?

@remexre Saya pikir deskripsi terbaik dari masalah yang mengarah pada pengembalian stabilisasi ada di https://github.com/rust-lang/rust/issues/49593

Jadi, apakah solusi yang bisa diterapkan adalah Kotak kasus khusus sebagai subtipe dari
Kotak? Apakah ada sifat objectsafe yang tidak dapat ditangani dengan cara ini?

Pada Minggu, 8 Juli 2018, 8:12 Ralf Jung [email protected] menulis:

@remexre https://github.com/remexre Saya pikir deskripsi terbaik dari
masalah yang mengarah pada pengembalian stabilisasi ada di #49593
https://github.com/rust-lang/rust/issues/49593


Anda menerima ini karena Anda disebutkan.

Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/35121#issuecomment-403286892 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AEAJtcnsEaFmHrrlHhuQeVOkR8Djzt50ks5uEgVLgaJpZM4JYi9D
.

>

Terima kasih,
Nathan

Diskusi itu benar-benar harus masuk ke https://github.com/rust-lang/rust/issues/49593 , tetapi bagian penting dari itu adalah Box::<T>::new membutuhkan T: Sized , tetapi new dipanggil disimpulkan memiliki T = Error (sifat DST) berdasarkan tipe pengembalian.

Selain inelegance dari special case, apakah ada masalah pada special-casing Box::new menjadi:

Box::new : T -> Box<U>
where T <: U,
      T: Sized

atau

Box::new : T -> Box<U>
where Box<T> <: Box<U>,
      T: Sized

Pertama-tama, kita harus memikirkan berapa ukuran ! seharusnya. Mathemticans akan mengusulkan sesuatu seperti Inf , tetapi pada kenyataannya itu akan menjadi usize::MAX , jadi kami memastikan setiap upaya untuk mengalokasikan ruang untuk jenis ini akan gagal atau setidaknya panic .

Jika ! adalah Sized maka, tidak ada yang menghentikan kita untuk mengkompilasi Box::new(x as !) , tetapi ini pada dasarnya adalah cara lain untuk panic! , karena tidak ada model memori yang benar-benar dapat mengalokasikan usize::MAX byte.

Tampaknya tidak jelas bagi saya bahwa ! harus memiliki ukuran tak terbatas/ usize::MAX versus menjadi ZST?

use std::mem::size_of;
enum Void {}
fn main() { println!("{}", size_of::<Void>()); }

saat ini adalah 0.

Alasannya dijelaskan dalam teks yang diberikan.

bool : dua nilai valid => log(2)/log(2) = 1 bit
() : 1 nilai valid => log(1)/log(2) = 0 bit
! : 0 nilai yang valid => log(0)/log(2) = Inf bit

Sebagai seseorang yang tidak berpengalaman dalam teori yang relevan, saya melihat mengikuti rumus log(x)/log(y) sejauh mengejar keanggunan model teoretis dengan merugikan penggunaan praktis. (AKA terlalu pintar untuk kebaikannya sendiri)

Secara intuitif, sepertinya ! juga harus berukuran nol karena:

  • bool : membutuhkan ruang untuk membedakan antara dua nilai yang valid => log(2)/log(2) = 1 bit selain sistem tipe
  • () : membutuhkan ruang untuk membedakan satu nilai yang valid => tidak diperlukan ruang di luar sistem tipe
  • ! : membutuhkan ruang untuk membedakan tidak ada nilai yang valid => tidak ada ruang yang dibutuhkan di luar sistem tipe

Yah itu -infinity sebenarnya, yang masuk akal, karena menempatkan ! sebagai bidang menjadi struct pada dasarnya mengubah struct menjadi ! juga (c + -inf = -inf). Jadi karena kita berurusan dengan penggunaan, 0 adalah nilai terdekat yang kita miliki untuk mewakilinya. (Saya pikir implementasi aktual di rustc bahkan menggunakan -inf).

Secara intuitif, sepertinya ! juga harus berukuran nol

! tidak membutuhkan ukuran sama sekali, karena tidak akan pernah ada nilai yang dibuat dengan tipe itu. Itu pertanyaan yang tidak relevan.

0 nilai yang valid => log(0)/log(2) = Inf bit

log(0) tidak terdefinisi; itu tidak terbatas.

Di sisi lain, memiliki ukuran usize::MAX adalah cara yang baik untuk menegakkan tidak berpenghuni. Setuju?

@CryZe @varkor

Itu juga cocok dengan konsep yang saya coba pikirkan validitasnya, di mana intuisi saya ingin melihat ! sebagai "ZST di tingkat yang sama sekali lain". (mis. ! untuk sistem tipe seperti () untuk alokasi memori.)

@CryZe
Rumus Anda -Inf + c == -Inf masuk akal, tetapi ketika mengganti -Inf dengan 0usize tidak berlaku lagi.

Di sisi lain, jika aritmatikanya adalah "caped": semua overflow dihitung ke usize::MAX maka usize::MAX cocok dengan rumus dengan sempurna.

Model otak: Jika Anda memiliki objek bertipe ! , untuk mengalokasikan struct yang memuatnya, Anda memerlukan struct yang lebih besar dari ! , tetapi ukuran struct terbesar yang dapat Anda gambar adalah usize::MAX . Jadi ruang yang Anda butuhkan masih usize::MAX .

Mengapa tidak "menjepit" ukuran jenis apa pun yang berisi jenis kosong ( ! , atau enum Void {} ditentukan pengguna) ke size=0,alignment=0 ? Ini tampaknya jauh lebih masuk akal secara semantik bagi saya ...

@remexre
Karena ini tidak berhasil. Contoh: sizeof(Result<usize,!>) == sizeof(usize) .

Nah, Result<usize, !> tidak mengandung ! secara langsung; sizeof(Result::Ok(usize)) == 8 , sizeof(Result::Err(!)) == 0

Tidak, sizeof(Result::Err(!)) == usize::MAX dalam proposal saya, karena Result::Err(!) :: Result<!,!> .

Aturannya harus: dalam enum s, ukuran ! dianggap kurang dari semua nilai ukuran lainnya, tetapi dalam structs , itu adalah ukuran maksimum yang pernah ada gambar.

Kesimpulan:

  • ! bukan DST. DST bukan Sized bukan karena tidak memiliki ukuran, tetapi karena informasi ukuran disembunyikan. Tapi untuk ! kami tahu segalanya tentang itu.
  • ! harus berukuran dan Box::new(x as !) harus diizinkan setidaknya untuk dikompilasi.
  • struct s berisi ! harus berukuran seolah-olah ! , enum s berisi ! langsung dalam varian harus berukuran sebagai jika varian itu tidak ada.

Entah kita mempertimbangkan sizeof(!)==0 atau sizeof(!)==usize::MAX , kita harus memperkenalkan beberapa aritmatika khusus untuk memungkinkan hal di atas:

sizeof(!)==0 : dalam struct, 0 + n = 0 :khawatir:, dalam enum, max(0,n) = n : blush:.
sizeof(!)==usize::MAX : dalam struct, usize::MAX + n =usize::MAX :neutral_face:, dalam enum, max(usize::MAX,n) = n : flushed:.

Ada keuntungan yang disukai usize::MAX : ini secara teknis mencegah upaya apa pun, bahkan yang unsafe , untuk membuat objek seperti itu, karena tidak mungkin dalam sistem nyata mana pun.

Ada keuntungan yang lebih baik dari usize::MAX: secara teknis mencegah setiap upaya, bahkan yang tidak aman, untuk membangun objek seperti itu, karena tidak mungkin dalam sistem nyata apa pun.

Maksud saya, jika unsafe shenanigans diperbolehkan: *transmute::<Box<usize>, Box<!>>(Box::new(0)) memberi saya ! terlepas dari ukurannya. usize::MAX membuatnya lebih mungkin kompiler akan error jika seseorang mencoba melakukan std::mem::zeroed<!>() , saya kira, tetapi saya akan menempatkan tanggung jawab pada orang yang menulis kode tidak aman itu, daripada matematika keanehan di atas.

Perhatikan bahwa ! memiliki ukuran _actually_ 0 -- bukan MAX atau -INF -saturated-to-0 -- diperlukan untuk menangani inisialisasi parsial, seperti sebelumnya ditemukan di https://github.com/rust-lang/rust/issues/49298#issuecomment -380844923 (dan diimplementasikan di https://github.com/rust-lang/rust/pull/50622).

Jika Anda ingin mengubah cara kerjanya, silakan buat edisi atau PR baru untuk itu; ini bukan tempatnya.

@remexre

Box::new : T -> Box<U>
where T <: U,
      T: Sized

Box adalah (kebanyakan) jenis perpustakaan dan metode new didefinisikan dalam sintaks Rust di src/liballoc/boxed.rs . Deskripsi Anda mengasumsikan operator <: yang tidak ada di Rust. Ini karena subtipe di Rust hanya ada melalui parameter seumur hidup. Jika Anda ingin membaca atau menonton lebih banyak:

! dapat dipaksa jenis apa pun, tapi ini lebih lemah daripada menjadi jenis apa pun. (Misalnya, Vec<&'static Foo> juga Vec<&'a Foo> untuk 'a , tetapi tidak ada konversi implisit dari Vec<!> ke Vec<Foo> : http:/ /play.rust-lang.org/?gist=82d1c1e1fc707d804a57c483a3e0198f&version=nightly&mode=debug&edition=2015)

Saya pikir idenya adalah Anda dapat melakukan apa pun yang menurut Anda pantas dalam mode unsafe , tetapi Anda harus menghadapi UB jika Anda tidak melakukannya dengan bijak. Jika ukuran resmi ! adalah usize::MAX , ini akan cukup mengingatkan pengguna bahwa jenis ini tidak boleh dibuat instance.

Juga, saya berpikir tentang penyelarasan. Jika tipe yang tidak berpenghuni memiliki ukuran usize::MAX , wajar untuk mengatur perataannya juga usize::MAX . Ini secara efektif membatasi pointer yang valid menjadi pointer nol. Tetapi menurut definisi, pointer nol tidak valid, jadi kami bahkan tidak memiliki poin yang valid untuk tipe ini, yang sempurna untuk kasus ini.

@ScottAbbey
Saya berfokus pada masalah untuk memastikan Box::new(x as !) tidak mengalami masalah sehingga kami dapat melanjutkan untuk menstabilkan ini. Cara yang diusulkan adalah membuat ! memiliki ukuran. Jika ZST adalah standar maka itu akan baik-baik saja. Saya tidak berdebat lagi. Cukup implementasikan sedemikian rupa sehingga sizeof(!)==0 akan berhasil.

itu wajar untuk mengatur perataannya juga menggunakan :: MAX

usize::MAX bukan pangkat dua, jadi bukan perataan yang valid. Juga, LLVM tidak mendukung penyelarasan lebih dari 1 << 29 . Dan ! seharusnya tidak memiliki keselarasan yang besar, karena let x: (!, i32); x.1 = 4; seharusnya hanya mengambil 4 byte tumpukan, bukan gigabyte-atau-lebih.

Silakan buat thread baru untuk diskusi ini, @earthengine , jika Anda ingin melanjutkannya.

Masalah yang terjadi di sini adalah Box::new(!) adalah Box<$0> mana kita mengharapkan Box<Error> . Dengan anotasi tipe yang memadai, kita akan memerlukan Box<$0> untuk dipaksa ke Box<Error> , yang nantinya akan meninggalkan kita dengan $0 default ke ! .

Namun, $0 adalah variabel tipe, jadi paksaan tidak dilakukan dan kami kemudian mendapatkan $0 = Error .

Salah satu opsi peretasan untuk memperbaiki sesuatu adalah menemukan bahwa kita memiliki batasan $0: Sized (mengikuti subtipe?) dan memasukkan paksaan yang dikunci pada itu, sehingga Box masih dipanggil pada tipe tersebut.

Kita sudah melakukan sesuatu seperti ini untuk mendeteksi Fn di penutupan, jadi ini tidak akan peregangan seperti itu. Masih jelek.

Itu saja, jika selama pemaksaan kami menemukan kewajiban dalam bentuk $0: Unsize<Y> mana Y pasti tidak berukuran dan $0 pasti berukuran, jangan anggap itu sebagai ambiguitas melainkan perlakukan itu sebagai "ok" dan lanjutkan dengan paksaan yang tidak terukur.

@arielb1

Jadi bagaimana ini berbeda dari paksaan Box::new(()) ke Box<Debug> ? std::error::Error adalah sifat seperti Debug , bukan tipe seperti [u8] . std::io::Error di sisi lain adalah tipe, tetapi Sized .

Kami tidak pernah mengalami kesulitan sebagai berikut:

use std::fmt::Debug;

fn f(x:()) -> Box<Debug> {
    Box::new(x)
}

fn main() {
}

@earthengine perbedaan antara masalah ini dan kode Anda adalah:

| Tertulis | Box::new(!): Box<Debug> | Box::new(()): Box<Debug> |
| ------ | ------ | ------- |
| Dilakukan | Box::new(! as Debug): Debug | (Box::new(()) as Box<Debug>): Debug |

Pada dasarnya, karena ! dapat memaksa apa pun, ia dipaksa ke Debug sebelum panggilan ke Box::new . Itu bukan opsi dengan () , jadi tidak ada masalah dalam kasus kedua -- pemaksaan di sana terjadi setelah Box::new .

@RalfJung

Model otak saya adalah bahwa DST tidak dapat eksis di alam liar - mereka harus berasal dari beberapa jenis yang lebih konkret. Ini bahkan benar ketika kita mengizinkan DST di posisi parameter - jika tidak di posisi kembali. Yang mengatakan, sampai nilai sebenarnya sudah ditempatkan di belakang sebuah pointer, itu seharusnya tidak dapat digabungkan ke DST, bahkan ! .

Misalnya yang berikut ini tidak boleh dikompilasi:

let v: str = !;
let v: [u8] = !;
let v: dyn Debug = !;

Hanya karena Anda tidak dapat mengganti ! dengan ekspresi Rust yang ada untuk membuatnya dikompilasi.

Sunting

Itu bukan pilihan dengan ()

Jadi ada yang bisa menjelaskan kenapa? Jika () as dyn Debug tidak dikompilasi, ! as dyn Debug tidak akan dikompilasi dan sebaliknya. &() as &Debug mengkompilasi, begitu juga &! as &Debug , tidak ada masalah. Jika () as dyn Debug suatu hari nanti dapat dikompilasi, masalah yang kita miliki hari ini akan diulang untuk () , sehingga pelaksana RFC DST harus menyelesaikannya, dan itu akan menyelesaikan masalah yang sama yang kita miliki untuk ! .

! memaksa apa pun karena tidak mungkin ada. "Ex faalso quodlibet". Jadi semua contoh Anda harus dikompilasi -- tidak ada alasan teoretis yang bagus untuk membuat pengecualian khusus untuk tipe yang tidak berukuran.

Ini bahkan tidak melanggar "DTS tidak bisa ada" karena ! juga tidak ada. :)

() as dyn Debug tidak dapat dikompilasi

Saya tidak tahu apa rencananya untuk nilai yang tidak berukuran, tetapi saya kira mereka dapat membuat kompilasi ini?
Intinya adalah, melakukan ini untuk ! tidak mengharuskan kita untuk mengimplementasikan nilai yang tidak berukuran karena ini hanya dapat terjadi dalam kode mati.

Namun, mungkin Anda menunjuk pada solusi yang mungkin di sini (dan mungkin itulah yang juga disarankan oleh @arielb1 di atas): Apakah mungkin untuk membatasi paksaan ! hanya berlaku jika tipe target (dikenal) berukuran ? Tidak ada alasan teoretis yang bagus untuk melakukan ini selain alasan praktis. :D Yaitu, bahwa mungkin membantu untuk memecahkan masalah ini.

Ketika saya mengatakan "DTS tidak bisa ada", maksud saya secara sintaksis. Tidak mungkin memiliki variabel lokal yang diketik adalah str , misalnya.

Di sisi lain, ! tidak dapat eksis adalah semantik. Kamu bisa menulis

let v = exit(0);

untuk memiliki v sintaksis mengetik ! dalam konteks, tetapi karena pengikatan bahkan tidak akan berjalan dan karenanya tidak keluar di dunia nyata.

Jadi inilah alasannya: kami mengizinkan ! memaksa ke jenis apa pun, hanya jika Anda dapat menulis ekspresi yang memiliki jenis yang sama. Jika suatu tipe bahkan tidak dapat eksis secara sintaksis, itu tidak boleh diizinkan.

Ini juga berlaku untuk nilai yang tidak berukuran, jadi sebelum kita memiliki nilai yang tidak berukuran, memaksa ! ke jenis yang tidak berukuran tidak diperbolehkan, sampai kita tahu cara menangani nilai yang tidak berukuran. Dan hanya pada saat itu kita dapat mulai memikirkan masalah ini (satu solusi yang jelas tampaknya memungkinkan Box::new menerima nilai yang tidak berukuran).

Tidak mungkin memiliki variabel lokal yang diketik adalah str, misalnya.

Itu hanya batasan dari implementasi saat ini: https://github.com/rust-lang/rust/issues/48055

@RalfJung

Bukan itu masalahnya di sini.

Sekali lagi, misalkan kita berada pada situasi di mana Box::new(!: $0): Box<dyn fmt::Debug> mana $0 adalah variabel tipe.

Kemudian kompiler dapat dengan mudah menyimpulkan bahwa $0: Sized (karena $0 sama dengan parameter tipe Box::new , yang memiliki T: Sized terikat).

Masalahnya muncul di mana kompiler perlu mencari tahu jenis paksaan mana yang digunakan untuk mengubah Box<$0> menjadi Box<dyn fmt::Debug> . Dari POV "lokal", ada 2 solusi:

  1. Memiliki CoerceUnsized paksaan, membutuhkan Box<$0>: CoerceUnsized<Box<dyn fmt::Debug>> . Ini adalah program yang valid untuk $0 = ! dan default akan membuatnya dikompilasi untuk itu.
  2. Memiliki pemaksaan identitas, dengan $0 = dyn fmt::Debug . Ini tidak konsisten dengan persyaratan $0: Sized .

Kompilator tidak ingin terlalu banyak hal terbuka saat mempertimbangkan ambiguitas, karena hal itu dapat menyebabkan masalah kinerja dan masalah yang sulit di-debug, jadi ia memilih paksaan mana yang akan digunakan dengan cara yang cukup bodoh (khususnya, kami tidak 't have T: CoerceUnsized<T> , jadi jika kompiler memilih opsi 1 itu bisa "macet" dengan mudah), dan akhirnya memilih opsi 2, yang gagal. Saya punya ide untuk membuatnya sedikit lebih pintar dan memilih opsi 1.

Jadi memikirkannya dari POV ini, ide yang tepat mungkin memicu paksaan yang tidak seberapa jika

  1. Paksaan yang tidak terukur bersifat konsisten (itu akan menjadi aturan saat ini, kecuali ambiguitas itu OK).
  2. tidak memicu paksaan yang tidak terukur adalah tidak konsisten. Kita harus melihat bahwa ini dapat dihitung tanpa merusak kinerja pada fungsi yang lebih besar.

Memiliki pemaksaan identitas, dengan $0 = dyn fmt::Debug. Ini tidak konsisten dengan $0: Persyaratan ukuran.

Saya hampir tidak dapat melihat mengapa kita harus mengizinkan let v: str=! tetapi tidak let v: str; . Jika Anda bahkan tidak dapat mendeklarasikan variabel dengan tipe tertentu, mengapa Anda dapat mengikat nilai (mungkin tidak mungkin) padanya?

Ketika nilai tidak berukuran tidak dikirimkan, cara yang konsisten adalah tidak mengizinkan paksaan apa pun untuk jenis yang tidak berukuran, karena ini akan membuat (mungkin sementara) nilai tidak berukuran, bahkan ketika ini hanya terjadi dalam imajinasi.

Jadi kesimpulan saya adalah bahwa masalah Box::new(!) as Box<Error> adalah masalah pemblokiran untuk nilai yang tidak berukuran, bukan untuk tipe ! . Aturan paksaan ! harus:

Anda dapat menulis let v: T , jika dan hanya jika Anda dapat menulis let v: T = ! .

Makna yang sebenarnya kemudian akan dievaluasi dengan bahasa. Terutama, setelah kami memiliki nilai yang tidak berukuran, kami memiliki Box::new(! as dyn Debug) tetapi sebelum itu, saya akan mengatakan tidak.

Jadi apa yang bisa kita lakukan untuk maju?

Tambahkan gerbang fitur pada kode yang memicu paksaan tidak berukuran, jika nilai tidak terukur aktif, coba paksaan ini, jika tidak, lewati. Jika ini adalah satu-satunya paksaan yang tersedia, pesan kesalahan seharusnya "sifat terikat str: std::marker::Sized tidak puas", seperti dalam kasus serupa. Jadi

Kita harus melihat bahwa ini dapat dihitung tanpa merusak kinerja pada fungsi yang lebih besar.

ditujukan: hanya pemeriksaan fitur sederhana.

Sementara itu, tambahkan masalah baru untuk nilai yang tidak berukuran , dan tambahkan tautan ke masalah pelacakan, jadi untuk memastikan masalah ini ditangani dengan benar.

Menarik.

Ini

fn main() {
    let _:str = *"";
}

mengkompilasi, tapi

fn main() {
    let v:str = *"";
}

tidak. Ini bahkan tidak terkait dengan tipe ! . Haruskah saya membuat masalah untuk ini?

Saya tidak tahu apakah yang terakhir itu benar-benar bug. Contoh kedua tidak dapat dikompilasi karena, tanpa dukungan nilai yang tidak berukuran, kompiler ingin mengetahui secara statis berapa banyak ruang tumpukan yang dialokasikan untuk variabel lokal v tetapi tidak bisa karena itu adalah DST.

Pada contoh pertama, pola _ bersifat khusus karena tidak hanya cocok dengan apa pun (seperti pengikatan variabel lokal), tetapi bahkan tidak membuat variabel sama sekali. Jadi kode itu sama dengan fn main() { *""; } tanpa let . Mendereferensi referensi (bahkan DST) dan kemudian tidak melakukan apa pun dengan hasilnya tidak berguna, tetapi tampaknya valid dan saya tidak yakin itu tidak valid.

Benar. Tapi itu benar-benar membingungkan, terutama yang berikut

fn main() {
    let _v:str = *"";
}

tidak mengkompilasi juga. Dengan teori Anda tentang _ ini seharusnya sama kecuali bahwa kami menyebut hal yang tidak digunakan _v daripada hanya _ .

Dari apa yang saya ingat, satu-satunya perbedaan antara _v dan v adalah bahwa garis bawah utama menekan peringatan tentang nilai yang tidak digunakan.

Di sisi lain, _ secara khusus berarti "buang" dan ditangani secara khusus untuk memungkinkannya muncul di lebih dari satu tempat dalam suatu pola (mis. Tuple membongkar, daftar argumen, dll.) tanpa menyebabkan kesalahan tentang tabrakan nama.

Di sisi lain, _ secara khusus berarti "buang" dan ditangani secara khusus untuk memungkinkannya muncul di lebih dari satu tempat dalam suatu pola (mis. Tuple membongkar, daftar argumen, dll.) tanpa menyebabkan kesalahan tentang nama tabrakan.

Benar. Selain apa yang Anda katakan, let _ = foo() menyebabkan drop dipanggil segera, sedangkan _v hanya akan dihapus ketika keluar dari ruang lingkup (seperti v akan ).

Memang ini yang saya maksud dengan “ _ is special”.

Jadi sekarang sepertinya menolak semua nilai yang tidak berukuran (kecuali fitur "nilai tidak berukuran" diaktifkan) di alam liar akan menjadi perubahan yang melanggar, meskipun efeknya akan sedikit menurut saya.

Saya kemudian masih menyarankan untuk memiliki gerbang fitur untuk melarang paksaan dari ! ke nilai yang tidak berukuran. Ini akan menolak kode seperti let _:str = return; , tapi saya rasa tidak ada yang akan menggunakannya dalam kode.

Selama tipe ! tidak stabil, kita dapat membuat perubahan yang melanggar di mana ia dapat atau tidak dapat dipaksakan.

Pertanyaannya adalah, jika kita menstabilkannya tanpa paksaan ke DST untuk memperbaiki https://github.com/rust-lang/rust/issues/49593 , apakah kita ingin mengembalikan paksaan tersebut nanti ketika kita menambahkan nilai yang tidak berukuran dan akankah kita dapat melakukannya tanpa merusak https://github.com/rust-lang/rust/issues/49593 lagi?

Proposal saya sebelumnya termasuk ini. #49593 seharusnya menjadi masalah pemblokiran untuk nilai yang tidak berukuran.

Saran pribadi saya untuk solusi terakhir, adalah membiarkan Box::new (mungkin juga menyertakan beberapa metode penting lainnya) menerima parameter yang tidak berukuran, dan mengizinkan ambiguitas dalam situasi serupa.

FWIW, _ tidak seperti nama variabel khusus. Ini adalah sintaks pola yang sepenuhnya terpisah yang tidak melakukan apa pun pada tempat yang dicocokkan. Pencocokan pola berfungsi di tempat , bukan nilai .
Yang paling dekat dengan pengikatan variabel adalah ref _x , tetapi meskipun demikian Anda mungkin memerlukan NLL untuk menghindari peminjaman yang saling bertentangan yang dihasilkan darinya. Ini berfungsi:

// The type `str` is the type of the place being matched. `x` has type `&str`.
let ref x: str = *"foo";
// Fully equivalent to this:
let x: &str = &*"foo";

@eddyb

Anda tidak mengerti maksud saya. Asumsi saya adalah bahwa di Rust saat ini tidak ada nilai yang tidak berukuran dapat muncul dalam kode sama sekali. Namun ternyata mereka muncul di let _:str = *"" . Meskipun nilai seperti itu dapat hidup untuk waktu yang singkat, di tingkat sintaksis itu hidup. Sesuatu seperti hantu...

Sebagai gantinya, contoh Anda, secara teknis sepenuhnya legal dan bukan itu intinya. str berukuran, tetapi &str berukuran.

Namun ternyata mereka muncul di let _:str = *"". Meskipun nilai seperti itu dapat hidup untuk waktu yang singkat, di tingkat sintaksis itu hidup. Sesuatu seperti hantu...

Ada "hantu" serupa dalam ekspresi seperti &*foo . Pertama Anda dereference foo dan kemudian mengambil alamat hasil tanpa memindahkannya . Jadi tidak sama dengan & { *foo } , misalnya.

@earthengine @SimonSapin Tidak ada tak terukur ("ekspresi nilai") di sana. Hanya lvalues ​​("tempat ekspresi") yang melakukannya. *"foo" adalah tempat , dan pencocokan pola tidak memerlukan nilai, hanya tempat.

Nama "lvalue" dan "rvalue" adalah peninggalan dan agak menyesatkan di C juga, tetapi bahkan lebih buruk di Rust, karena RHS let adalah "lvalue" (ekspresi tempat), terlepas dari namanya.
(Ekspresi penugasan adalah satu-satunya tempat di mana nama dan aturan C masuk akal di Rust)

Dalam contoh Anda, let x = *"foo"; , nilai diperlukan untuk mengikatnya ke x .
Demikian pula, &*"foo" membuat ekspresi tempat kemudian meminjamnya, tanpa perlu membuat nilai yang tidak berukuran, sementara {*"foo"} selalu merupakan ekspresi nilai dan dengan demikian tidak mengizinkan jenis yang tidak berukuran.

Di https://github.com/rust-lang/rust/pull/52420 saya tekan itu

let Ok(b): Result<B, !> = ...;
b

tidak lagi berfungsi. Apakah itu disengaja?

AFAIK ya -- cerita pola infalibel rumit dan memiliki gerbang fitur terpisah dari ! itu sendiri. Lihat juga https://github.com/rust-lang/rfcs/pull/1872 dan https://github.com/rust-lang/rust/issues/48950.

@Ericson2314 khususnya fitur yang perlu Anda tambahkan ke liballoc adalah exhaustive_patterns .

Terima kasih!!

Percakapan menarik di internal:

Jika Anda akan melakukan itu, mengapa tidak mengobati! sendiri sebagai variabel inferensi?

Cukup buat pembungkus di sekitar ! dengan PhandomData untuk membedakan jenis item.

https://github.com/rust-lang/rust/issues/49593 kini telah diperbaiki. Ini adalah alasan untuk kembalinya stabilisasi sebelumnya. Laporan stabilisasi sebelumnya ada di sini . Mari kita coba ini lagi!

@rfcbot penggabungan

Saya pikir rfcbot mungkin mendukung lebih dari satu FCP dalam masalah yang sama. Mari kita buka masalah baru untuk putaran stabilisasi ini?

https://github.com/rust-lang/rust/pull/50121 tidak hanya mengembalikan stabilisasi tetapi juga semantik mundur. Apakah ini sesuatu yang ingin kita tinjau kembali?

Kotak centang tersisa yang tidak dicentang dalam deskripsi masalah adalah:

Sifat apa yang harus kita terapkan untuk ! ? PR awal #35162 termasuk Ord dan beberapa lainnya. Ini mungkin lebih merupakan masalah T-libs, jadi saya menambahkan tag itu ke masalah ini.

Kita bisa menambahkan impls nanti, bukan? Atau ini pemblokir?

@SimonSapin Dibuka https://github.com/rust-lang/rust/issues/57012. Saya berharap fallback ke ! akan diaktifkan lagi sebagai bagian dari perubahan ini, ya (meskipun mari kita bahas itu pada masalah stabilisasi).

Rupanya ada bug/lubang karena tidak pernah mengetik di belakang gerbang fitur, dan mungkin untuk merujuknya di Stable: https://github.com/rust-lang/rust/issues/33417#issuecomment -467053097

Sunting: diajukan https://github.com/rust-lang/rust/issues/58733.

Menambahkan penggunaan tipe dalam contoh kode yang ditautkan di atas:

trait MyTrait {
    type Output;
}

impl<T> MyTrait for fn() -> T {
    type Output = T;
}

type Void = <fn() -> ! as MyTrait>::Output;

fn main() {
    let _a: Void;
}

Ini dikompilasi dalam Rust 1.12.0 yang menurut saya adalah yang pertama dengan https://github.com/rust-lang/rust/pull/35162. Di 1.11.0, kesalahan dengan:

error: the trait bound `fn() -> !: MyTrait` is not satisfied [--explain E0277]
  --> a.rs:12:13
   |>
12 |>     let _a: Void;
   |>             ^^^^
help: the following implementations were found:
help:   <fn() -> T as MyTrait>

error: aborting due to previous error

Apa keadaan yang satu ini?

Sejauh yang saya tahu statusnya tidak berubah secara signifikan sejak ringkasan saya di https://github.com/rust-lang/rust/issues/57012#issuecomment -467889706.

Apa keadaan yang satu ini?

@hosunrise : Saat ini diblokir di https://github.com/rust-lang/rust/issues/67225

Saya mungkin jauh di sini tetapi masalah spesifik yang disebutkan dalam diskusi lint ( https://github.com/rust-lang/rust/issues/66173 ) tampaknya dapat dipecahkan jika setiap enum memiliki cabang ! di sistem tipe?

Saya akan mencatat bahwa tidak berlaku untuk masalah yang disebutkan dalam OP https://github.com/rust-lang/rust/issues/67225 , yang masih akan menjadi masalah.

Saya mungkin jauh di sini tetapi masalah spesifik yang disebutkan dalam diskusi lint (#66173) keduanya tampaknya dapat dipecahkan jika setiap enum memiliki cabang ! dalam sistem tipe?

Sebenarnya, setiap enum dapat diancam karena ada jumlah varian yang tak terbatas yang tidak akan pernah terjadi, dan dengan demikian mereka semua tidak layak disebut.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat