Rust: Masalah pelacakan untuk RFC 1937: `?` di `main`

Dibuat pada 18 Jul 2017  ·  183Komentar  ·  Sumber: rust-lang/rust

Ini adalah masalah pelacakan untuk RFC " ? in main " (rust-lang/rfcs#1937).

Langkah:

Stabilisasi:

  • [x] Stabilkan main dengan tipe pengembalian non-() (https://github.com/rust-lang/rust/issues/48453) Digabungkan dalam https://github.com/rust-lang/ karat/tarik/49162
  • [x] Stabilkan pengujian unit dengan tipe pengembalian non-() (https://github.com/rust-lang/rust/issues/48854)

Masalah terkait:

  • [x] Pesan kesalahan untuk pengujian unit tidak bagus (https://github.com/rust-lang/rust/issues/50291)

Pertanyaan yang belum terselesaikan:

B-RFC-approved C-tracking-issue E-mentor T-compiler T-lang WG-compiler-middle

Komentar yang paling membantu

Permintaan maaf karena menimpali pada titik di mana mungkin sudah terlambat untuk melakukan sesuatu tentang ini, tetapi saya ingin meninggalkan umpan balik saya di sini jika ada. Saya memang membaca sebagian besar utas ini, jadi saya berbicara dengan konteks itu dalam pikiran. Namun, utas ini panjang, jadi jika sepertinya saya telah mengabaikan sesuatu, maka saya mungkin melakukannya dan saya akan menghargai jika itu ditunjukkan kepada saya. :-)

TL;DR - Saya pikir menampilkan pesan kesalahan Debug adalah kesalahan, dan pilihan yang lebih baik adalah menggunakan pesan kesalahan Display .

Inti dari keyakinan saya adalah bahwa, sebagai seseorang yang _secara rutin membangun program CLI di Rust_, saya tidak pernah ingat terlalu peduli tentang apa pesan Debug dari Error itu. Yaitu, Debug dari kesalahan, menurut desain, untuk pengembang, bukan untuk pengguna akhir. Saat Anda membuat program CLI, antarmukanya pada dasarnya dimaksudkan untuk dibaca oleh pengguna akhir, jadi pesan Debug memiliki utilitas yang sangat sedikit di sini. Artinya, jika ada program CLI yang saya kirimkan ke pengguna akhir menunjukkan representasi debug dari nilai Rust dalam operasi normal, maka saya akan menganggap bahwa bug harus diperbaiki. Saya biasanya berpikir ini harus benar untuk setiap program CLI yang ditulis dalam Rust, meskipun saya mengerti itu mungkin titik di mana orang yang berakal bisa tidak setuju. Dengan demikian, implikasi yang agak mengejutkan menurut pendapat saya adalah bahwa kami telah secara efektif menstabilkan fitur di mana mode operasi default memulai Anda dengan bug (sekali lagi, IMO) yang harus diperbaiki.

Dengan menunjukkan Debug representasi kesalahan secara default, saya juga berpikir kami mendorong praktik yang buruk. Secara khusus, sangat umum dalam menulis program Rust CLI untuk mengamati bahwa bahkan Display impl kesalahan tidak cukup baik untuk dikonsumsi oleh pengguna akhir, dan bahwa pekerjaan nyata harus dilakukan untuk memperbaikinya. Contoh nyata dari ini adalah io::Error . Menampilkan io::Error tanpa jalur file yang sesuai (dengan asumsi itu berasal dari membaca/menulis/membuka/membuat file) pada dasarnya adalah bug, karena sulit bagi pengguna akhir untuk melakukan apa pun dengannya. Dengan memilih untuk menampilkan Debug representasi kesalahan secara default, kami telah mempersulit bug semacam itu untuk ditemukan oleh orang-orang yang membuat program CLI. (Selain itu, Debug dari io::Error jauh lebih tidak berguna daripada Display , tetapi itu sendiri bukan masalah besar dalam pengalaman saya. )

Akhirnya, untuk melengkapi argumen saya, saya juga mengalami kesulitan membayangkan keadaan di mana saya akan menggunakan ?-in-main bahkan dalam contoh. Yaitu, saya telah mencoba untuk menulis contoh yang paling cocok dengan program dunia nyata, dan ini umumnya melibatkan penulisan hal-hal seperti ini:

use std::error::Error;
use std::process;

fn try_main() -> Result<(), Box<Error>> {
    // do stuff with `?`
}

fn main() {
    if let Err(err) = try_main() {
        eprintln!("{}", err);
        process::exit(1);
    }
}

Sekilas, akan _indah_ untuk mengganti ini dengan ?-in-main , tapi saya tidak bisa, karena itu tidak akan menunjukkan Display dari kesalahan. Artinya, ketika menulis program CLI nyata, saya sebenarnya akan menggunakan pendekatan di atas, jadi jika saya ingin contoh saya mencerminkan kenyataan, maka saya pikir saya harus menunjukkan apa yang saya lakukan dalam program nyata dan tidak mengambil jalan pintas (sampai batas yang wajar ). Saya benar-benar berpikir hal semacam ini sangat penting, dan satu efek samping dari ini secara historis adalah bahwa hal itu menunjukkan kepada orang-orang bagaimana menulis kode Rust idiomatik tanpa memercikkan unwrap di mana-mana. Tetapi jika saya kembali menggunakan ?-in-main dalam contoh saya, maka saya baru saja mengingkari tujuan saya: Saya sekarang menyiapkan orang-orang yang mungkin tidak tahu lebih baik untuk hanya menulis program yang, secara default, memancarkan sangat pesan kesalahan yang tidak membantu.

Pola "pancarkan pesan kesalahan dan keluar dengan kode kesalahan yang sesuai" sebenarnya digunakan dalam program yang dipoles. Misalnya, jika ?-in-main menggunakan Display , maka saya dapat menggabungkan fungsi main dan run di ripgrep hari ini:

https://github.com/BurntSushi/ripgrep/blob/64317bda9f497d66bbeffa71ae6328601167a5bd/src/main.rs#L56 -L86

Tentu saja, saya dapat menggunakan ?-in-main di masa mendatang dengan memberikan impl saya sendiri untuk sifat Termination setelah sifat itu stabil, tetapi mengapa saya repot-repot melakukannya jika saya dapat menuliskan main fungsi impl itu dalam contoh untuk membuatnya sesuai dengan kenyataan, dan pada saat itu, saya sebaiknya tetap menggunakan contoh yang saya miliki hari ini (menggunakan main dan sebuah try_main ).

Dari kelihatannya, tampaknya memperbaiki ini akan menjadi perubahan yang menghancurkan. Artinya, kode ini dikompilasi di Rust stable hari ini:

#[derive(Debug)]
struct OnlyDebug;

fn main() -> Result<(), OnlyDebug> {
    Err(OnlyDebug)
}

Saya pikir beralih ke Display akan merusak kode ini. Tapi saya tidak tahu pasti! Jika ini benar-benar masalah kapal-telah berlayar, maka saya mengerti dan tidak ada gunanya menjelaskan intinya, tetapi saya merasa cukup kuat tentang ini untuk setidaknya mengatakan sesuatu dan melihat apakah saya tidak dapat meyakinkan orang lain dan melihat jika ada yang bisa dilakukan untuk memperbaikinya. (Mungkin juga saya bereaksi berlebihan di sini, tetapi sejauh ini ada beberapa orang yang bertanya kepada saya "mengapa Anda tidak menggunakan ?-in-main ?" Dalam contoh CSV saya, dan jawaban saya pada dasarnya adalah, "Saya tidak melihat bagaimana mungkin untuk melakukan itu." Mungkin itu bukan masalah yang dimaksudkan untuk diselesaikan oleh ?-in-main , tetapi beberapa orang pasti memiliki kesan itu. Dengan implementasinya saat ini , saya bisa melihatnya berguna dalam tes dokumen dan tes unit, tetapi saya kesulitan memikirkan situasi lain di mana saya akan menggunakannya.)

Semua 183 komentar

Bagaimana status keluar akan ditangani?

Komentar oleh @Screwtapello ini tampaknya dibuat terlalu dekat dengan akhir FCP untuk perubahan apa pun yang harus dilakukan pada RFC sebagai tanggapannya.

Singkatnya: RFC mengusulkan pengembalian 2 pada kegagalan dengan alasan yang, meskipun beralasan, tidak jelas dan menghasilkan hasil yang sedikit tidak biasa; hal yang paling tidak mengejutkan adalah mengembalikan 1 ketika program tidak memiliki indikasi bahwa ia menginginkan lebih banyak detail dari sekadar keberhasilan atau kegagalan. Apakah ini cukup bikesheddy sehingga dapat didiskusikan tanpa terasa seperti kita memutarbalikkan proses RFC, atau apakah kita sekarang terkunci dalam detail implementasi khusus ini?

Ini bukan detail implementasi, bukan?

Beberapa skrip menggunakan kode keluar sebagai cara untuk mendapatkan informasi dari sub-proses.

Ini secara khusus tentang kasus ketika sub-proses (diimplementasikan dalam Rust) tidak memiliki informasi untuk diberikan, di luar biner "semuanya baik-baik saja"/"ada yang tidak beres".

Beberapa skrip menggunakan kode keluar sebagai cara untuk mendapatkan informasi dari sub-proses.

Perilaku itu selalu sangat bergantung pada program yang disebut _except_ dalam arti kegagalan yang bukan nol. Mengingat bahwa std::process::exit dengan pembungkus fungsi utama dan tabel pencarian akan tetap menjadi pilihan terbaik bagi mereka yang menginginkan status keluar yang lebih jelas, apa pun yang dilakukan, ini sepertinya detail yang sebagian besar tidak signifikan.

Saya tidak berpikir SemVer memiliki pengecualian "kebanyakan detail yang tidak signifikan".

Saya pikir kode keluar harus ditambahkan ke daftar pertanyaan yang belum terselesaikan. @zackw juga membuka utas internal terkait.

Banyak orang setuju bahwa kode keluar seharusnya 1 jika gagal (bukan 2 ):
https://www.reddit.com/r/rust/comments/6nxg6t/the_rfc_using_in_main_just_got_merged/

@arielb1 apakah Anda akan menerapkan rfc ini?

@bkchr

Tidak, hanya untuk membimbingnya. Saya ditugaskan agar saya tidak lupa menulis catatan pendampingan.

Ahh bagus, saya akan tertarik untuk melakukannya :)
Tapi, saya tidak tahu harus mulai dari mana :D

@bkchr

Itu sebabnya saya di sini :-). Saya harus segera menulis instruksi mentoring.

Oke, kalau begitu saya tunggu instruksi Anda.

Instruksi Mentoring

Ini adalah masalah [WG-compiler-middle]. Jika Anda ingin mencari bantuan, Anda dapat bergabung dengan #rustc di irc.mozilla.org (Saya arielby) atau https://gitter.im/rust-impl-period/WG-compiler-middle (Saya @arielb1 di sana).

Ada readme kompiler WIP di #44505 - ini menjelaskan beberapa hal di kompiler.

Rencana kerja untuk RFC ini:

  • [ ] - tambahkan item lang Termination ke libcore
  • [ ] - izinkan menggunakan Termination di main
  • [ ] - izinkan menggunakan Termination di doctests
  • [ ] - izinkan menggunakan Termination di #[test]

tambahkan Termination lang-item ke libcore

Pertama, Anda perlu menambahkan sifat Termination ke libcore/ops/termination.rs , bersama dengan beberapa dokumentasi. Anda juga harus menandainya sebagai tidak stabil dengan atribut #[unstable(feature = "termination_trait", issue = "0")] - ini akan mencegah orang menggunakannya sebelum distabilkan.

Kemudian, Anda perlu menandainya sebagai lang-item di src/librustc/middle/lang_items.rs . Ini berarti kompilator dapat menemukannya saat memeriksa jenis main (misalnya lihat 0c3ac648f85cca1e8dd89dfff727a422bc1897a6).
Itu berarti:

  1. menambahkannya ke daftar item-lang (dalam librustc/middle/lang_items.rs )
  2. menambahkan #[cfg_attr(not(stage0), lang = "termination")] ke sifat Termination . Alasan Anda tidak dapat menambahkan atribut #[lang = "termination"] begitu saja adalah karena kompilator "stage0" (selama bootstrap) tidak akan mengetahui bahwa termination adalah sesuatu yang ada, sehingga tidak akan dapat kompilasi libstd. Kami akan menghapus cfg_attr secara manual saat kami memperbarui kompiler stage0.
    Lihat dokumentasi bootstrap di XXX untuk detailnya.

izinkan menggunakan Termination di main

Ini adalah bagian menarik yang saya tahu bagaimana menghadapinya. Ini berarti membuat main yang mengembalikan () bukan type-check (saat ini Anda mendapatkan kesalahan main function has wrong type ) dan berhasil.

Untuk membuatnya tipe-check, Anda harus terlebih dahulu menghapus kesalahan yang ada di:
https://github.com/rust-lang/rust/blob/9a00f3cc306f2f79bfbd54f1986d8ca7a74f6661/src/librustc_typeck/lib.rs#L171 -L218

Kemudian, Anda perlu menambahkan tanda centang bahwa tipe pengembalian mengimplementasikan sifat Termination (Anda menambahkan kewajiban sifat menggunakan register_predicate_obligation - cari kegunaannya). Itu bisa dilakukan di sini:
https://github.com/rust-lang/rust/blob/9a00f3cc306f2f79bfbd54f1986d8ca7a74f6661/src/librustc_typeck/check/mod.rs#L1100 -L1108

Bagian lain membuatnya bekerja. Itu seharusnya agak mudah. Seperti yang dikatakan RFC, Anda ingin membuat lang_start generik di atas tipe pengembalian.

lang_start saat ini didefinisikan di sini:
https://github.com/rust-lang/rust/blob/9a00f3cc306f2f79bfbd54f1986d8ca7a74f6661/src/libstd/rt.rs#L32

Jadi, Anda harus mengubahnya menjadi generik dan cocok dengan RFC:

#[lang = "start"]
fn lang_start<T: Termination>
    (main: fn() -> T, argc: isize, argv: *const *const u8) -> !
{
    use panic;
    use sys;
    use sys_common;
    use sys_common::thread_info;
    use thread::Thread;

    sys::init();

    sys::process::exit(unsafe {
        let main_guard = sys::thread::guard::init();
        sys::stack_overflow::init();

        // Next, set up the current Thread with the guard information we just
        // created. Note that this isn't necessary in general for new threads,
        // but we just do this to name the main thread and to give it correct
        // info about the stack bounds.
        let thread = Thread::new(Some("main".to_owned()));
        thread_info::set(main_guard, thread);

        // Store our args if necessary in a squirreled away location
        sys::args::init(argc, argv);

        // Let's run some code!
        let exitcode = panic::catch_unwind(|| main().report())
            .unwrap_or(101);

        sys_common::cleanup();
        exitcode
    });
}

Dan kemudian Anda harus memanggilnya dari create_entry_fn . Saat ini, ia membuat instance monomorfik lang_start menggunakan Instance::mono , dan Anda harus mengubahnya untuk menggunakan monomorphize::resolve dengan subst yang tepat.

https://github.com/rust-lang/rust/blob/9a00f3cc306f2f79bfbd54f1986d8ca7a74f6661/src/librustc_trans/base.rs#L697

izinkan menggunakan Termination di doctests

Saya tidak begitu mengerti cara kerja doctests. Mungkin bertanya pada @alexcrichton (itu yang akan saya lakukan)?

izinkan menggunakan Termination di #[test]

Saya tidak begitu mengerti cara kerja libtest. Mungkin bertanya pada @alexcrichton (itu yang akan saya lakukan)? Tes unit pada dasarnya dihasilkan oleh makro, jadi Anda perlu mengubah makro itu, atau pemanggilnya, untuk menangani tipe pengembalian yang bukan () .

@bkchr

Bisakah Anda setidaknya bergabung dengan IRC/gitter?

@bkchr baru saja check-in -- Saya melihat Anda dan @arielb1 sedang mengobrol di gitter beberapa waktu lalu, ada kemajuan? Mengisap di suatu tempat?

Maaf, tidak ada kemajuan sampai sekarang. Saat ini saya memiliki banyak hal yang harus dilakukan, tetapi saya berharap bahwa saya akan menemukan waktu minggu ini untuk memulai ini.

@bkchr Jika Anda butuh bantuan, beri tahu saya!

Saat ini saya agak macet, saya ingin membuat Obligation. Untuk membuat Obligation saya membutuhkan TraifRef, untuk TraitRef saya membutuhkan DefId. Dapatkah seseorang mengarahkan saya ke beberapa kode tentang cara membuat DefId dari Sifat Penghentian?

Ya bukan itu masalahnya, saya sudah melakukannya. Saya perlu memeriksa sifat terminasi di fungsi check_fn. Saya ingin menggunakan register_predicate_obligation dan untuk itu saya membutuhkan defid dari sifat terminasi.

Oh, maka yang Anda butuhkan hanyalah tcx.require_lang_item(TerminationTraitLangItem) .

@bkchr bagaimana? Baru check in lagi. =) Jangan khawatir jika Anda sibuk, hanya ingin memastikan Anda mendapatkan semua bantuan yang Anda butuhkan.

Maaf, saat ini sibuk :/ Sampai sekarang, saya mendapatkan semua bantuan yang saya butuhkan :)

Ini adalah kode untuk memeriksa TerminationTrait: https://github.com/bkchr/rust/blob/f185e355d8970c3350269ddbc6dfe3b8f678dc44/src/librustc_typeck/check/mod.rs#L1108

Saya pikir saya tidak memeriksa jenis pengembalian fungsi? Saya mendapatkan kesalahan berikut:

error[E0277]: the trait bound `Self: std::ops::Termination` is not satisfied
  --> src/rustc/rustc.rs:15:11
   |
15 | fn main() { rustc_driver::main() }
   |           ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::ops::Termination` is not implemented for `Self`
   |
   = help: consider adding a `where Self: std::ops::Termination` bound

Apa yang perlu saya ubah, untuk memeriksa tipe pengembalian fungsi?

@bkchr Saya akan merekomendasikan bergabung dengan compiler-middle working group gitter di https://gitter.im/rust-impl-period/WG-compiler-middle untuk umpan balik, serta mencoba #rust-internals IRC channel di https ://chat.mibbit.com/?server=irc.mozilla.org%3A%2B6697&channel=%23rust-internals . :)

@bstrie ya terima kasih, saya sudah menjadi bagian dari obrolan gitter dan bisa menyelesaikan masalah saya. :)

@bkchr masalah Anda ada di baris ini . Referensi sifat yang ingin Anda buat di sana adalah sesuatu seperti R: Termination di mana R adalah tipe pengembalian fungsi. Ini ditentukan dengan membangun "subst" yang sesuai, yang merupakan kumpulan nilai untuk menggantikan parameter tipe sifat (dalam hal ini, Self ).

Namun, Anda menggunakan metode Substs::identity_for_item pada sifat tersebut. Ini akan memberi Anda kembali substitusi yang akan digunakan di dalam definisi sifat itu sendiri . yaitu, dalam hal ini Anda memetakan parameter Self yang dideklarasikan pada sifat Termination ke Self . Ini akan sesuai jika Anda memeriksa definisi beberapa fungsi di dalam sifat Terminator , tetapi tidak terlalu banyak di sini.

Yang Anda inginkan adalah mendapatkan tipe pengembalian dari fungsi entri. Ini hanyalah salah satu variabel ret_ty atau actual_ret_ty . Keduanya baik-baik saja, tetapi saya kira ret_ty lebih baik -- yang sesuai dengan tipe pengembalian yang dideklarasikan pengguna (sedangkan actual_ret_ty adalah tipe kode aktual yang dikembalikan).

Anda dapat membuat pengganti yang Anda inginkan hanya dengan memanggil metode mk_substs() dari file tcx. Dalam hal ini, hanya ada satu parameter, tipe, jadi sesuatu seperti let substs = fcx.tcx.mk_substs(&[ret_ty]); akan bekerja, saya pikir.

Saya percaya hal yang digunakan adalah tcx.mk_substs_trait(ret_ty, &[]) .

@bkchr baru saja check in -- punya kesempatan untuk menggunakan saran itu? (Juga, untuk tanggapan yang lebih cepat, mungkin bijaksana untuk bertanya di gitter .)

Ya, saya bisa menyelesaikan masalah dengan gitter :)

@bkchr bagaimana? Baru saja check-in.

Semuanya baik-baik saja, saya mungkin akan mendapatkan waktu minggu ini untuk melihat ke dalam kode.

Apakah ada ruang untuk satu orang lagi untuk membantu dalam hal ini? Saya ingin mulai berkontribusi pada komunitas Rust sebelum akhir tahun dan ingin membantu dengan fitur ini. Mudah-mudahan tidak akan terlalu membingungkan untuk memiliki dua orang yang berkolaborasi dalam hal ini.

@U007D

Ini adalah fitur kecil dan @bkchr hampir selesai.

Ah, ok-- senang mengetahuinya, terima kasih. Saya akan mengawasi hal lain yang bisa saya bantu.

@U007D Pernahkah Anda melihat https://www.rustaceans.org/findwork ?

@lnicola Ya, saya punya! Saya mencoba menemukan sesuatu di persimpangan sesuatu yang saya yakini dapat saya kerjakan (yaitu menjadi positif bersih) dan saya bersemangat. Sejujurnya, meskipun saya telah belajar Rust selama sekitar satu tahun, masih sedikit menakutkan untuk melangkah menjadi sukarelawan untuk sesuatu. FWIW, itu sama sekali bukan kesalahan komunitas Rust--komunitas Rust telah berusaha sekuat tenaga untuk menjadikan ini budaya yang terbuka, ramah, dan inklusif--yang terbaik yang pernah saya rasakan. (Saya menduga ini lebih berkaitan dengan bekas luka pertempuran lama dari pengalaman bertahun-tahun dalam industri teknologi di mana tim cenderung kompetitif daripada kolaboratif.)

Bagaimanapun, itu adalah tujuan saya untuk memilih sesuatu tahun ini dan setidaknya mulai memberikan kontribusi positif. Sudah waktunya bagi saya untuk terlibat! :)

Terima kasih atas sarannya, @lnicola. Itu adalah sumber yang bagus.

@bkchr ada pembaruan?

Saya ada di dalamnya (https://github.com/rust-lang/rust/pull/46479). Sekarang saya punya hari libur dan waktu untuk bekerja di komentar di permintaan tarik. Maaf untuk semua keterlambatan :/

Oh, maaf, tidak menyadari bahwa Anda memiliki permintaan tarik. Cross-link itu.

Halo, um. Jadi saya pikir saya akan memulai karir kontributor Rust potensial saya dengan bikeshedding, seperti tradisi. Secara khusus, tentang yang satu ini:

  • [ ] Nama sifat yang diperkenalkan

Bagaimana dengan Exit ? Ini singkat dan to the point, sesuai dengan kosakata Rust yang ada. Exit-as-a-noun adalah padanan alami untuk exit-as-a-verb yang, bagi sebagian besar, adalah kata yang akrab untuk mengakhiri proses "dari dalam" dengan cara yang terkendali.

Khusus untuk programmer C++, "penghentian" mengingatkan std::terminate yang defaultnya adalah penghentian abnormal (memanggil abort ) dan pada dasarnya C++ setara dengan panik (tetapi tidak seperti panik, tidak pernah melepas tumpukan).

Tunggu, abaikan komentar itu, sepertinya RFC meninggalkan yang secara eksplisit terbuka untuk diskusi.

Saya suka Exit sebagai nama sifat.

Saya memperkirakan bahwa fitur tersebut akan distabilkan jauh sebelum sifat tersebut, seperti yang terjadi pada Carrier .

FWIW itu kasus lain di mana saya sangat senang bahwa nama sementara diubah sebelum stabilisasi :D

Sebagai penulis RFC, saya tidak keberatan mengubah nama sifat menjadi Exit , atau yang lainnya. Saya tidak terlalu pandai menamai sesuatu dan senang orang lain memiliki ide yang lebih baik.

https://github.com/rust-lang/rust/blob/5f7aeaf6e2b90e247a2d194d7bc0b642b287fc16/src/libstd/lib.rs#L507

Apakah sifat itu seharusnya

  1. ditempatkan di libstd alih-alih libcore, dan
  2. baru saja dipanggil std::Termination , bukan std::ops::Termination ?

Sifat tidak dapat ditempatkan ke libcore , karena implementasi untuk Result mengharuskan untuk mencetak ke stderr dan itu tidak dapat dilakukan di libcore .

@bkchr Impl berada di libstd tidak berarti sifat tersebut harus ada di libstd juga.

@kennytm Saya tahu, tetapi Hasil juga didefinisikan dalam libcore, jadi Pemutusan tidak dapat diterapkan untuk Hasil di libstd.

@zackw +1 suara lagi untuk Exit sebagai nama sifat.

@U007D : Bisakah Anda menggunakan tombol reaksi (misalnya, 👍) alih-alih memposting pesan seperti itu? Itu akan memungkinkan Anda menghindari masalah pelanggan yang mengganggu dengan melakukan ping kepada mereka secara tidak perlu.

Bisakah saya check-in libtest / libsyntax , jika language_feature diaktifkan (dalam peti)? @arielb1 @nikomatsakis @alexcrichton

@bkchr di libsyntax Anda mungkin harus meneruskannya tetapi secara teori mungkin, tetapi dalam libtest itu sendiri saat runtime saya tidak percaya Anda dapat memeriksanya.

@bkchr bagaimana di sini?

Saya masih mengerjakannya, tetapi saat ini saya tidak memiliki pertanyaan lagi :)

Saya pikir impl ini terlalu ketat:

#[unstable(feature = "termination_trait", issue = "43301")]
impl<T: Termination, E: Error> Termination for Result<T, E> {
    fn report(self) -> i32 {
        match self {
            Ok(val) => val.report(),
            Err(err) => {
                print_error(err);
                exit::FAILURE
            }
        }
    }
}


#[unstable(feature = "termination_trait", issue = "43301")]
fn print_error<E: Error>(err: E) {
    eprintln!("Error: {}", err.description());

    if let Some(ref err) = err.cause() {
        eprintln!("Caused by: {}", err.description());
    }
}

Ada beberapa kesalahan umum yang tidak mengimplementasikan Error , yang paling penting Box<::std::error::Error> dan failure::Error . Saya juga berpikir itu adalah kesalahan untuk menggunakan metode description alih-alih menampilkan impl dari kesalahan ini.

Saya akan mengusulkan untuk mengganti impl ini dengan impl yang lebih luas ini:

#[unstable(feature = "termination_trait", issue = "43301")]
impl<T: Termination, E: Display> Termination for Result<T, E> {
    fn report(self) -> i32 {
        match self {
            Ok(val) => val.report(),
            Err(err) => {
                eprintln!("Error: {}", err)
                exit::FAILURE
            }
        }
    }
}

Itu memang kehilangan rantai penyebab yang mengecewakan.

Menggunakan tampilan impl daripada deskripsi jelas merupakan hal yang lebih baik untuk dilakukan.

Rantai penyebab adalah masalah yang menarik. Secara khusus, implementasi ini hanya mencetak dua anggota pertama dari rantai penyebab.

kegagalan harus berurusan dengan cara menangani rantai penyebab dan menyelesaikan perilaku ini secara default (misalnya jika Anda hanya membuat kesalahan menggunakan .context :

  • {} hanya mencetak kesalahan ini
  • {:?} mencetak kesalahan ini serta penyebabnya (secara rekursif)

Kami dapat memutuskan untuk menggunakan :? di sini dan mengikatnya Debug alih-alih Tampilan. Tidak yakin.

Ya, saya sudah tahu bahwa saya perlu meningkatkan impl untuk mendukung. Saya terbuka pada apa yang bisa kita lakukan. Mengikat ke Debug mungkin merupakan ide yang bagus.

Hmm, ini yang rumit. Saya kira itu tergantung pada apakah kita berpikir bahwa program "dipoles" akan menggunakan sifat impl ini. Saya cenderung berpikir tidak apa-apa untuk mengatakan bahwa "tidak, mereka tidak akan" -- pada dasarnya, program yang dipoles akan (a) menangkap output dan menanganinya dengan cara lain atau (b) menggunakan beberapa tipe baru atau sesuatu yang mengimplementasikan Debug jalan yang benar. Ini berarti kita dapat mengoptimalkan impl untuk membuang informasi yang berguna tetapi harus dalam bentuk tercantik (yang tampaknya seperti peran Debug ).

Ini mungkin pilihan yang tepat untuk membuat ini ditargetkan dengan sangat jelas pada pembuatan prototipe dengan menggunakan Debug , karena saya tidak berpikir kita dapat secara otomatis menangani kesalahan dengan cara yang benar untuk sebagian besar kasus penggunaan produksi.

@tanpa perahu saya setuju.

@nikomatsakis Saya berasumsi maksud Anda " belum tentu dalam bentuk tercantik"? Jika demikian, ya, saya setuju.

Pembaruan: setelah mengerjakan ini selama beberapa hari, saya membalik ini. Lihat di bawah.

:+1: pada Debug , di sini; Saya suka "jenis analog dengan pengecualian yang tidak tertangkap" @nikomatsakis dari https://github.com/rust-lang/rfcs/pull/1937#issuecomment -284509933 . Sebuah komentar dari Diggsey juga menyarankan Debug : https://github.com/rust-lang/rfcs/pull/1937#issuecomment -289248751

FYI, saya telah membalik masalah "lebih lengkap" vs "lebih ramah pengguna" (yaitu Debug vs Display terikat sifat).

TL;DR adalah saya sekarang percaya kita harus menetapkan batas menjadi Display (sesuai posting asli @withoutboats ') untuk memberikan output ringkasan yang lebih bersih dalam kasus 'tidak melakukan apa-apa'.

Inilah alasan saya:

Pada masalah RFC sifat termination @zackw membuat poin menarik bahwa Rust memiliki sistem panic / Result ganda karena panic s adalah untuk bug dan Result s untuk kesalahan. Dari sini, saya pikir kasus yang menarik dapat dibuat untuk mengevaluasi presentasi kesalahan default secara independen dari presentasi panik default.

Tentu saja tidak ada satu default yang akan memuaskan semua orang, jadi menerapkan prinsip paling tidak mengejutkan, saya bertanya pada diri sendiri default mana yang lebih tepat?

  • Kesalahan sering kali tidak ditangani oleh design , yang dimaksudkan untuk dikomunikasikan kepada pengguna bahwa ada sesuatu (mungkin dapat diperbaiki) yang tidak beres (file tidak ditemukan, dll.). Dengan demikian, kasus penggunaan ada dan dapat dianggap umum bahwa pengguna adalah audiens yang dituju.

  • Seperti yang ditunjukkan @nikomatsakis , terlepas dari default yang kami pilih, pengembang mana pun yang ingin mengubah perilaku dapat menggunakan pola tipe baru atau mengembangkan implementasi khusus di main().

Dan akhirnya di sisi yang lebih subjektif, Dalam bekerja dengan fitur ini selama beberapa hari terakhir, saya telah menemukan output Debug membuat saya merasa bahwa "aplikasi Rust" saya terasa lebih kasar :

$ foo
Error: Custom { kind: Other, error: StringError("returned Box<Error> from main()") }
$

vs

$ foo
Error: returned Box<Error> from main()
$

Sifat Dispay sepertinya merupakan default yang jauh lebih beradab untuk kesalahan (sebagai lawan dari bug), bukan?

@U007D tunggu, mana dari dua output yang Anda sukai?

(a) Error: Custom { kind: Other, error: StringError("returned Box<Error> from main()") }

atau

(b) Error: returned Box<Error> from main()

Dia lebih suka opsi (b).

@nikomatsakis Awalnya, saya baik-baik saja dengan a) Debug sebagai konsep di kepala saya, tetapi setelah bekerja dengannya selama beberapa hari benar-benar melihat hasilnya, saya sekarang lebih suka b) Display as default. Saya pikir preferensi saya untuk b) akan menjadi lebih jelas jika saya memodelkan kesalahan berantai.

Saya tidak berpikir "dipoles" atau "beradab" adalah tujuan dari ini, karena saya mengerti utasnya sudah menerima ini sebagai sebagian besar untuk contoh, dengan orang-orang sepenuhnya diharapkan untuk menambahkan penanganan khusus saat program matang.

Dalam kasus tersebut, bagi saya "paling tidak mengejutkan" adalah keluaran berorientasi pengembang seperti unwrap .

Apakah perlu membahas {:#?} di sini, jika ada kekhawatiran tentang kesalahan yang panjang?

Pelaporan kesalahan untuk pengguna akhir akan berbeda untuk setiap alat dan setiap kasus penggunaan, tetapi pelaporan kesalahan untuk pengembang harus menyerupai apa yang dilakukan Rust dalam situasi lain seperti .unwrap() . Karena hanya ada satu default, dan perangkat lunak yang dipoles tetap perlu mengganti output, saya memilih default yang berorientasi pengembang dengan Debug .

Saya pikir inti dari diskusi ini adalah "siapa target audiens untuk pesan default?"

Katakanlah sejenak bahwa kita semua sepakat bahwa target audiens default adalah pengembang. Saya pikir ikatan default Debug akan menjadi pilihan langsung.

Sekarang katakanlah sejenak bahwa kita sepakat bahwa target audiens default adalah pengguna, maka di sinilah, secara khusus, saya tidak setuju dengan beberapa orang lain dan merasa bahwa kualitas subjektif seperti output "poles" dan "beradab" memang memiliki bagian penting untuk bermain. Bagi sebagian orang, presentasi pengguna akhir "poles" mungkin merupakan alasan terbaik untuk menghindari Display . (Saya tidak sependapat dengan pandangan itu, tetapi saya memahami dan menghormatinya.)

Jadi ya, saya pasti bisa melihat argumen yang masuk akal untuk kedua grup sebagai target default. Saya pikir jika konsensus kuat berkembang di sekitar audiens mana yang harus menjadi target default, maka pilihan untuk sifat terikat akan menjadi jelas (er)... :)

(Saya tidak sepenuhnya berpengalaman dalam seluruh topik ini, tetapi) tidakkah dapat dibayangkan bahwa mungkin ada utilitas kecil di mana output kesalahan default dengan Termination akan sangat memadai, asalkan dalam beberapa format yang dapat dilihat pengguna seperti Display ? Dalam hal ini, satu-satunya alasan penulis harus mencapai "penanganan khusus" adalah jika kita membuatnya.

Dapatkah seseorang memberikan contoh seperti apa keluarannya dalam setiap kasus (saya berasumsi itu juga tergantung pada jenis E tertentu yang digunakan?), dan langkah apa yang sebenarnya perlu diambil oleh penulis jika mereka ingin "penanganan khusus" alih-alih? Aku hanya akan hipotetis di atas.

(Apakah output benar-benar terlihat seperti apa yang ditempelkan oleh @U007D di atas? Mengapa ia mencetak "Kotak yang dikembalikan\Kotak<Kesalahan>?)

Seberapa sering bahkan Display dari pesan kesalahan cukup ramah pengguna? Misalnya, program berikut:

fn main() {
    if let Err(e) = std::fs::File::open("foo") {
        println!("{}", e)
    }
}

memancarkan pesan berikut:

No such file or directory (os error 2)

Yang, menurut saya, bukan UX yang bagus, terutama dengan nama file yang tidak disebutkan. Setidaknya tidak, kecuali program benar-benar menggunakan satu nama file sebagai input. Di sisi lain, ini juga bukan pengalaman pengembang yang hebat, kehilangan file sumber/nomor baris/jejak tumpukan. Output Debug jelas merupakan pengalaman pengguna yang lebih buruk, dan juga tidak menambahkan informasi yang berguna untuk deceloper.

Jadi saya kira apa yang saya coba katakan adalah bahwa tanpa memperbaiki konten informasi dari kesalahan perpustakaan itu sendiri, baik Debug maupun Display tidak bagus.

Apakah output benar-benar terlihat seperti yang ditempelkan @U007D di atas? Mengapa itu mencetak "Kotak yang dikembalikandari main()" alih-alih... isi sebenarnya dari Kotak itu?

@glaebhoerl Anda benar--dalam hal ini, "isi sebenarnya dari Box<Error> " itu adalah bidang message khusus yang saya buat untuk menguji termination_trait , ditampilkan kata demi kata . Saya bisa saja menulis "foo bar baz" atau apa pun di sana (tapi itu mungkin tidak berguna bagi pengguna yang menjalankan tes kompiler).

Menggunakan contoh @jdahlstrom , berikut adalah output aktual untuk perpustakaan standar Box ed "file tidak ditemukan" Error (perhatikan, seperti yang Anda tunjukkan dengan benar, tidak menyebutkan tinju di mana pun):
Debug :

$ foo
Error { repr: Os { code: 2, message: "No such file or directory" } }
$

dan Display :

$ foo
No such file or directory (os error 2)
$

@jdahlstrom Saya pikir Anda membuat poin yang bagus. Saya setuju bahwa meskipun kedua pesan tersebut mungkin kurang terlayani oleh audiens target mereka dan saya ingin menyoroti bahwa memberikan pesan yang salah bahkan lebih buruk (seperti yang saya pikir Anda singgung):

Memberikan Display kepada pengembang memiliki semua kelemahan Debug plus melewatkan kekhususan jenis kesalahan apa yang ditampilkan.

Memberikan Debug kepada pengguna memiliki semua kelemahan Display plus menambahkan lebih banyak informasi teknis yang tidak dibutuhkan pengguna dan mungkin tidak dapat dipahami.

Jadi ya, saya setuju bahwa pesannya seringkali tidak cukup tepat sasaran, baik untuk audiens. Saya pikir ini menyoroti alasan penting lainnya bagi kami untuk memperjelas siapa yang kami targetkan sehingga kami memberikan pengalaman terbaik yang kami bisa (meskipun ada kekurangan) untuk grup itu.

Saya memerlukan bantuan untuk menerapkan dukungan untuk ? di #[test] . Implementasi saya saat ini dapat ditemukan di sini: https://github.com/rust-lang/rust/compare/master...bkchr :termination_trait_in_tests

Mengkompilasi tes dengan perubahan saya menghasilkan kesalahan berikut:

error: use of unstable library feature 'test' (see issue #27812)
  |
  = help: add #![feature(test)] to the crate attributes to enable

@eddyb mengatakan saya tidak boleh menggunakan quote_item!/expr! lagi, karena itu adalah warisan.
Apa yang harus saya lakukan sekarang, beralih ke makro quote! yang baru atau mengerjakan ulang semuanya ke bangunan ast manual?

Saya menghargai bantuan apa pun :)

Saya pikir menghasilkan permintaan makro ke beberapa makro yang ditentukan dalam libtest dapat bekerja dengan sangat baik.

@eddyb Saya tidak yakin saya mengerti saran Anda di sini:

Saya pikir menghasilkan permintaan makro ke beberapa makro yang ditentukan dalam libtest bisa bekerja dengan sangat baik.

Oh, saya kira mungkin saya lakukan. Anda mengatakan -- tentukan makro di libtest dan kemudian buat kode yang memanggilnya? Ide yang menarik. Bukankah nama makro itu semacam "bocor"? (yaitu, itu menjadi bagian dari antarmuka publik libtest?)


@bkchr

Mengkompilasi tes dengan perubahan saya menghasilkan kesalahan berikut:

Apakah Anda tahu mengapa kesalahan itu dihasilkan? Hanya dari membaca diff, saya tidak, tetapi saya dapat mencoba membangun secara lokal dan mencari tahu.

Saya seharusnya tidak menggunakan quote_item!/expr! lagi, karena itu adalah warisan.

Saya tidak memiliki pendapat yang kuat di sini. Saya setuju dengan @eddyb mereka adalah warisan, tetapi saya tidak yakin apakah mereka adalah jenis warisan di mana menambahkan beberapa kegunaan lagi akan membuat mereka lebih sulit untuk dihapus -- yaitu, setelah kami mendapatkan pengganti baru-baru ini, apakah akan mudah? @eddyb untuk berpindah dari satu ke yang lain?

Pembuatan AST manual tentu saja merepotkan, meskipun saya kira kami memiliki beberapa pembantu untuk itu.

Biasanya kita hanya perlu melakukan sedikit editan, bukan? yaitu, ubah dari menjalankan fungsi menjadi menguji hasil report() ?

PS, kita mungkin ingin menghasilkan sesuatu seperti Termination::report(...) daripada menggunakan notasi .report() , untuk menghindari ketergantungan pada sifat Termination yang ada dalam ruang lingkup?

Tidak, saya tidak tahu dari mana kesalahan itu berasal :(

RFC mengusulkan untuk menghasilkan fungsi pembungkus yang memanggil fungsi pengujian asli. Itu juga cara saya saat ini untuk melakukannya.
Saya pikir kita juga dapat menghapus fungsi pembungkus, tetapi kemudian kita akan memerlukan penunjuk fungsi yang diketik, karena setiap fungsi pengujian dapat mengembalikan tipe yang berbeda.

Hmm, karena tes sudah mengimpor barang lain, tidak terlalu rumit untuk mengimpor sifat Termination.

@alexcrichton apakah Anda mungkin tahu dari mana kesalahan ini berasal?

@nikomatsakis libtest tidak stabil dan tidak bisakah kita juga menandai makro sebagai tidak stabil meskipun sebenarnya tidak?

@eddyb oh, poin yang bagus.

RFC mengusulkan untuk menghasilkan fungsi pembungkus yang memanggil fungsi pengujian asli. Itu juga cara saya saat ini untuk melakukannya.

Fungsi pembungkus tampaknya baik-baik saja bagi saya.

@eddyb dengan makro maksud Anda sesuatu seperti create_test yang didefinisikan dalam libtest ? Tapi yang saya tidak mengerti adalah "tandai makro sebagai tidak stabil". Apa yang Anda maksud dengan itu? Bisakah Anda memberi saya contoh?

@bkchr Menempatkan atribut #[unstable(...)] pada definisi makro, misalnya: https://github.com/rust-lang/rust/blob/3a39b2aa5a68dd07aacab2106db3927f666a485a/src/libstd/thread/local.rs#L159 -L165

Jadi, haruskah kotak centang pertama ini ...

Menerapkan RFC

...diperiksa sekarang bahwa PR yang ditautkan telah digabungkan?

@ErichDonGubler Selesai :)

Hmm, saat ini hanya setengah rfc yang diimplementasikan dengan pr gabungan ^^

Dipisahkan menjadi 3 kotak centang :)

Saya telah mencoba menggunakan fitur ini di main dan saya merasa cukup frustasi. Implikasi yang ada dari sifat penghentian tidak memungkinkan saya untuk dengan mudah "mengakumulasikan" beberapa jenis kesalahan -- misalnya, saya tidak dapat menggunakan failure::Fail , karena tidak mengimplementasikan Error ; Saya tidak dapat menggunakan Box<Error> , alasan yang sama. Saya pikir kita harus memprioritaskan perubahan ke Debug . =)

Hai, @nikomatsakis ,

Saya merasakan frustrasi yang sama persis seperti yang Anda rasakan ketika saya mencoba menggunakan termination_trait .

Itu, dikombinasikan dengan posting Anda tentang peretasan pada kompiler menginspirasi saya untuk memecahkan masalah ini awal bulan ini. Saya telah memposting impl untuk Display (dan untuk Debug di komit sebelumnya) bersama dengan tes di sini: https://github.com/rust-lang/rust/pull/47544 . (Ini sangat kecil, tapi tetap saja, PR kompiler Rust pertama saya! :tada :) :)

Pemahaman saya adalah tim lang akan membuat panggilan tentang sifat mana yang harus diikuti, tetapi bagaimanapun juga, implementasinya sudah siap.

Sebuah pertanyaan yang masih saya minati: misalkan Anda tidak ingin bergantung pada keluaran pesan kesalahan default (apakah Debug atau Display ), dan menginginkan milik Anda sendiri, bagaimana Anda melakukannya itu? (Maaf jika ini sudah ditulis di suatu tempat dan saya melewatkannya.) Anda tidak perlu berhenti menggunakan ? -in- main seluruhnya, bukan? Ini seperti menulis hasil Anda sendiri dan/atau jenis kesalahan dan/atau impl ? (Tampaknya disayangkan bagi saya jika ? -in- main hanya mainan, dan segera setelah Anda ingin "serius" Anda harus kembali ke cara yang kurang ergonomis.)

@glaebhoerl Ini sangat mudah:

  1. Buat tipe baru.
  2. Terapkan From jenis kesalahan lama Anda.
  3. Terapkan Debug (atau Display ).
  4. Ganti jenis di tanda tangan main .

Terima kasih!

Tampaknya agak aneh bagi saya untuk menulis implementasi kustom dari Debug yang tidak berorientasi pada debugging, tapi saya rasa ini bukan akhir dari dunia.

@glaebhoerl Itu sebabnya Result impl harus menggunakan Display daripada Debug IMO.

Tidak bisakah sifat Termination memiliki metode tambahan yang disebut message , atau error_message atau semacamnya, yang memiliki implementasi default yang menggunakan Debug / Display untuk menampilkan pesan? Maka Anda hanya perlu menerapkan satu metode daripada membuat tipe baru.

@glaebhoerl @Thomasdezeeuw Sesuatu seperti metode error_message ada di draf asli RFC, tetapi dibatalkan karena kurangnya konsensus. Perasaan saya saat itu adalah bahwa akan lebih baik untuk mendapatkan fitur dasar mendarat (tidak harus distabilkan) dan kemudian beralih.

@zackw Setuju, saya pribadi akan baik-baik saja tanpa pesan, hanya nomor, katakanlah 0 dan 1 untuk kode kesalahan. Tetapi jika kita ingin mendapatkan pesan di iterasi pertama, saya pikir saya akan lebih menyukai Termination::message daripada apa pun di Debug atau Display .

Apakah metode message itu akan mengembalikan String ? Bukankah itu tidak kompatibel dengan Termination berada di libcore?

@SimonSapin Termination saat ini didefinisikan dalam libstd .

Hmm, tapi saya tidak berpikir bahwa menambahkan metode message ke sifat akan menjadi implementasi terbaik. Apa yang akan dikembalikan metode untuk tipe seperti i32 ? Bagaimana memutuskan kapan harus mencetak pesan ini? Saat ini implementasi Termination untuk Result , mencetak kesalahan dalam fungsi report . Itu berhasil, karena Result tahu, bahwa itu adalah Err . Kita bisa mengintegrasikan cek report() != 0 di suatu tempat dan kemudian mencetak, tetapi itu tidak terasa benar.
Masalah berikutnya adalah, bahwa kami ingin menyediakan implementasi standar untuk Result , tetapi apa yang perlu diimplementasikan oleh tipe Error untuk dicetak? Ini akan membawa kita kembali ke pertanyaan saat ini Debug atau Display .

Sakit kepala lain yang muncul jika Anda ingin menggunakan ? di main dalam program "serius" adalah, dalam beberapa keadaan, program baris perintah ingin keluar dengan status bukan nol tetapi tanpa mencetak _anything_ (pertimbangkan grep -q ). Jadi sekarang Anda memerlukan Termination impl untuk sesuatu yang _bukan_ Error , yang tidak mencetak apa pun, yang memungkinkan Anda mengontrol status keluar ... kembali mengembalikan hal itu _after_ mem-parsing argumen baris perintah.

Inilah yang saya pikirkan:

Mengembalikan Result<T, E> harus menggunakan impl debug untuk E. Ini adalah sifat yang paling nyaman -- ini diterapkan secara luas, dan memenuhi kasus penggunaan "keluaran cepat dan kotor" serta kasus penggunaan pengujian unit. Saya lebih suka untuk tidak menggunakan Display keduanya karena kurang diterapkan secara luas dan karena memberi kesan bahwa ini adalah keluaran yang dipoles, yang menurut saya sangat tidak mungkin.

Tetapi juga harus ada cara untuk mendapatkan hasil yang terlihat profesional. Untungnya, sudah ada dua cara seperti itu. Pertama, seperti yang dikatakan @withoutboats , orang dapat membuat jembatan "tampilan-dari-debug" jika mereka ingin menggunakan E: Display atau memberikan output gaya profesional. Tetapi jika ini terasa terlalu rumit, maka Anda juga dapat menentukan tipe Anda sendiri untuk mengganti hasil. misalnya, Anda mungkin melakukan:

fn main() -> ProfessionalLookingResult {
    ...
}

dan kemudian menerapkan Try untuk ProfessionalLookingResult . Kemudian Anda dapat mengimplementasikan Terminate juga, apa pun:

impl Terminate for ProfessionalLookingResult {
    fn report(self) -> i32 {
        ...
        eprintln!("Something very professional here.");
        return 1;
        ...
    }
}

Saya setuju dengan @nikomatsakis bahwa ini harus menggunakan Debug .

Saya juga berpikir bahwa untuk hasil yang dipoles, menulis beberapa kode di main mungkin lebih baik daripada membuat tipe baru untuk mengimplementasikan Try dan Terminate . Maksud dari Terminate menurut saya adalah untuk hal-hal yang dapat dengan mudah dibuat oleh perpustakaan sebagai default yang baik, yang tidak pernah terjadi pada situasi di mana bagaimana program berakhir penting bagi pengguna akhir (misalnya CLI profesional) .

Tentu saja orang lain mungkin memiliki pendapat yang berbeda, dan ada beberapa cara untuk menggunakan ciri-ciri yang terlibat untuk menyuntikkan kode alih-alih menulisnya langsung di main . Apa yang hebat adalah bahwa kita memiliki banyak pilihan dan kita tidak harus selalu menemukan satu cara yang diberkati untuk menangani kesalahan.

Biarkan saya menuliskan beberapa pemikiran, meskipun ada beberapa masalah dengan mereka.

Saya ingin melihat yang berikut ini

fn main() -> i32 {
    1
}

Yang dapat ditulis lebih umum sebagai:

fn main() -> impl Display {
    1
}

Kedua fungsi utama ini harus mengembalikan kode keluar 0 dan println! Display dari 1.

Ini harus sesederhana berikut (saya pikir).

impl<T> Termination for T where T: Display {
    fn report(self) -> i32 {
        println!("{}", self);
        EXIT_SUCCESS
    }
}

Kemudian untuk kesalahan kita dapat memiliki:

impl<T: Termination, E: Debug> Termination for Result<T, E> { ... }

di mana implementasinya sama seperti di RFC hanya dengan "{:?}" untuk digunakan
format Debug .

Seperti disebutkan sebelumnya, orang yang membutuhkan kontrol lebih besar atas output dapat dengan mudah
menulis:

fn main() -> Result<i32, MyError> { ... }
impl Termination for Result<i32, MyError> { ... }

Meskipun ini tidak dapat diputuskan dengan kompiler kami saat ini, kurasa, karena
akan melihat implementasi yang bertentangan... Jadi kami melakukan apa yang disarankan @nikomatsakis
dan tulis:

fn main() -> MyResult { ... }
impl Termination for MyResult { ... }
or, if you want something more general.
impl<T, E> Termination for MyResult<T, E> { ... }

Saya tahu ini sebagian menyatakan kembali hal-hal yang telah dikatakan, tetapi saya pikir saya akan menyajikan visi saya sama sekali, menunjukkan bahwa solusi yang lebih umum untuk menampilkan nilai balik, bukan hanya hasil. Sepertinya banyak dari komentar ini yang memperdebatkan implementasi apa dari Termination yang kami kirimkan secara default. Juga komentar ini bertentangan dengan implementasi seperti impl Termination for bool seperti yang dijelaskan dalam RFC. Saya pribadi berpikir bahwa kode keluar bukan nol harus ditangani secara eksklusif oleh Results atau tipe khusus yang mengimplementasikan Termination .

Ini adalah pertanyaan menarik tentang bagaimana menangani ? pada Option tipe main karena mereka tidak memiliki implementasi untuk Display .

TL;DR: Saya setuju dengan Debug .

Penjelasan lebih detail:
Saya menghabiskan beberapa waktu memikirkan hal ini kemarin dan hari ini dengan tujuan untuk mengungkap asumsi implisit yang saya miliki atau sedang buat untuk membantu sampai pada jawaban terbaik.

Asumsi Kunci: Dari semua jenis aplikasi yang dapat ditulis di Rust, saya pikir aplikasi konsol paling diuntungkan/dipengaruhi oleh keputusan ini. Pemikiran saya adalah bahwa ketika menulis perpustakaan, judul game AAA, IDE atau sistem kontrol berpemilik, seseorang mungkin tidak akan mengharapkan sifat penghentian utama default untuk memenuhi kebutuhan seseorang di luar kotak (memang, seseorang bahkan mungkin tidak memiliki utama). Jadi bias saya terhadap Display sebagai default berasal dari apa yang kami harapkan untuk dilihat saat menggunakan aplikasi baris perintah kecil--misalnya:

$ cd foo
bash: cd: foo: No such file or directory

Sebagian besar dari kita tidak mengharapkan bantuan debugging apa pun, hanya indikator singkat tentang apa yang salah. Saya hanya menganjurkan ini sebagai posisi default.

Ketika saya berpikir untuk menulis impl Terminate untuk mendapatkan output sederhana seperti ini, saya menyadari bahwa ? dari fitur utama tidak jauh berbeda dengan stable rust hari ini (dalam hal jumlah kode yang ditulis ), di mana Result -aware "inner_main()" sering dibuat untuk menangani E .

Dengan asumsi ini, sebagai latihan pemikiran, saya mencoba untuk menentukan apakah saya merasa kuat bahwa dominasi implementasi gaya " inner_main() " yang ada saat ini adalah dari rasa Display yang lebih kasual (lebih dari rasa Debug yang lebih teknis). Pemikiran saya adalah bahwa ini akan menjadi indikasi bagaimana fitur tersebut kemungkinan akan benar-benar digunakan.

Saya tidak dapat meyakinkan diri sendiri bahwa inilah masalahnya. (Artinya, saya rasa saat ini tidak ada bias yang kuat terhadap Display dalam implementasi yang ada). Memang, ketika melihat melalui repositori saya sendiri yang telah saya tulis selama 16 bulan terakhir, saya juga menemukan cukup banyak dari kedua kasus yang saya tidak dapat mengatakan bahwa menerapkan Display secara default akan menghasilkan penghematan bersih.

Berpegang pada asumsi "penerima manfaat utama adalah aplikasi cli", ada sejumlah besar aplikasi konsol yang menyediakan bantuan dan informasi penggunaan. Sebagai contoh:

$ git foo
git: 'foo' is not a git command. See 'git --help'.

The most similar command is
    log

Jadi, bahkan untuk aplikasi konsol, sulit bagi saya untuk mengidentifikasi "kelompok yang terluka" dengan menggunakan Debug .

Dan akhirnya, saya akan lebih bahagia dengan impl Debug daripada dengan fitur yang ditahan selama 6 bulan, juga, jadi, egois, begitulah :).

Jadi ada proses pemikiran saya ditata di depan umum. Untuk meringkas, saya pikir Debug tidak akan lebih baik dan tidak lebih buruk dari Display , dan karena itu, harus baik-baik saja sebagai implementasi default.

Seperti banyak dari Anda, saya yakin, saya berharap ada implementasi yang membuat saya lebih bersemangat--seperti, "YA, ITU!!!", TBH. Tapi mungkin itu hanya harapan saya yang tidak realistis... Mungkin begitu kami memiliki solusi yang bekerja dengan failure mengurangi boilerplate dalam proyek saya, itu akan tumbuh pada saya. :)

Catatan Saya membuka PR untuk mendukung sifat terminatio dalam pengujian: #48143 (berdasarkan karya @bkchr ).

Saya mengambil kebebasan untuk memperluas sifat Termination dengan metode untuk memproses hasil tes. Ini menyederhanakan implementasi, tetapi juga masuk akal, karena kegagalan pengujian mungkin menginginkan lebih banyak keluaran verbose daripada kegagalan yang dapat dieksekusi.

Termination harus diganti namanya menjadi Terminate setelah preferensi umum kita untuk kata kerja untuk ciri-ciri di libstd.

@withoutboats Saya pikir di beberapa titik ada diskusi bahwa sifat-sifat kata kerja sebagian besar adalah mereka yang memiliki metode tunggal dengan nama yang sama dengan sifat tersebut. Bagaimanapun, bolehkah saya melayangkan lagi saran bikeshed saya sendiri, Exit ?

Bikeshedding serampangan: ini adalah sifat metode tunggal. Jika kita ingin memberi mereka nama yang sama, mungkin ToExitCode / to_exit_code ?

Menstabilkan pengembalian Result dapat dilakukan secara terpisah dari menstabilkan sifat, bukan?

Bagi saya, ini terasa seperti ? di mana sebagian besar nilainya berasal dari fitur bahasa, dan kami dapat menunda untuk mengetahui sifat tersebut. Diskusi RFC bahkan membuat saya bertanya-tanya apakah sifat _ever_ perlu distabilkan, karena memasukkan kode ke dalam inner_main tampaknya lebih mudah daripada impl sifat untuk ini...

Ya, kita tidak perlu menstabilkan sifat - meskipun ini berguna pada stable untuk hal-hal seperti kerangka kerja, yang tidak terlalu bergantung pada inner_main .

@SimonSapin Saya menganggap To mengacu pada konversi tipe, dan ini tidak. Tapi kita bisa menamai metodenya terminate (saya juga tidak berpikir batasan ini tentang kapan memberi nama ciri-ciri kata kerja berlaku. Try adalah contoh tandingan yang jelas.)

Saya telah mengusulkan agar kita menstabilkan fn main() -> T di mana T bukan unit. Hal ini membuat banyak detail -- khususnya nama/lokasi/detail sifat tersebut -- tidak stabil, tetapi beberapa hal telah diperbaiki. Detail di sini:

https://github.com/rust-lang/rust/issues/48453

Tolong beri tanggapan Anda!

terminate tampaknya lebih deskriptif daripada report . Kami selalu terminate , tetapi dapat menghilangkan report ing.

Tetapi tidak seperti misalnya std::process::exit metode ini tidak menghentikan apa pun. Itu hanya mengubah nilai pengembalian main() menjadi kode keluar (setelah secara opsional mencetak Result::Err ke stderr).

Suara lain untuk Exit . Saya suka itu singkat, cukup deskriptif dan konsisten dengan konsep tradisional kode keluar/status keluar.

Saya pasti lebih suka Exit daripada Terminate karena kami keluar dengan anggun dengan kembali dari main, daripada tiba-tiba sulit menghentikan kami karena ada yang tidak beres.

Saya menambahkan https://github.com/rust-lang/rust/issues/48854 untuk mengusulkan uji unit penstabil yang mengembalikan hasil.

Oh hei, saya menemukan tempat yang tepat untuk membicarakan hal ini.

Menggunakan ? di doctests

Cara kerja doctests saat ini adalah seperti ini:

  • rustdoc memindai doctest untuk mengetahui apakah itu mendeklarasikan fn main

    • (saat ini hanya melakukan pencarian teks baris demi baris untuk "fn main" yang tidak setelah // )

  • Jika fn main ditemukan, itu tidak akan menyentuh apa yang sudah ada
  • Jika fn main tidak ditemukan, itu akan membungkus sebagian besar* dari doctest dalam fn main() { } dasar

    • *Versi lengkap: itu akan mengekstrak deklarasi #![inner_attributes] dan extern crate dan meletakkannya di luar fungsi utama yang dihasilkan, tetapi yang lainnya masuk ke dalam.

  • Jika doctest tidak menyertakan pernyataan peti eksternal (dan peti yang didokumentasikan tidak disebut std ) maka rustdoc juga akan memasukkan pernyataan extern crate my_crate; tepat sebelum fungsi utama yang dihasilkan.
  • rustdoc kemudian mengkompilasi dan menjalankan hasil akhir sebagai biner mandiri, sebagai bagian dari rangkaian uji.

(Saya meninggalkan beberapa detail, tetapi saya dengan mudah membuat artikel lengkap di sini.)

Jadi, untuk menggunakan ? dengan mulus di doctest, bit yang perlu diubah adalah bagian di mana ia menambahkan fn main() { your_code_here(); } Mendeklarasikan fn main() -> Result<(), Error> Anda sendiri akan bekerja segera setelah Anda bisa melakukannya bahwa dalam kode biasa - rustdoc bahkan tidak perlu dimodifikasi di sana. Namun, untuk membuatnya bekerja tanpa mendeklarasikan main secara manual akan membutuhkan sedikit tweak. Karena tidak mengikuti fitur ini dengan cermat, saya tidak yakin apakah ada solusi satu ukuran untuk semua. Apakah fn main() -> impl Termination mungkin?

Apakah fn main() -> impl Pemutusan mungkin?

Dalam arti yang dangkal, ya: https://play.rust-lang.org/?gist=8e353379f77a546d152c9113414a88f7&version=nightly

Sayangnya, saya pikir -> impl Trait pada dasarnya merepotkan dengan ? karena konversi kesalahan bawaan, yang memerlukan konteks inferensi untuk memberi tahu jenis apa yang akan digunakan: https://play.rust- lang.org/?gist=23410fa4fa684710bc75e16f0714ec4b&version=nightly

Secara pribadi saya membayangkan ? -in-doctests bekerja melalui sesuatu seperti https://github.com/rust-lang/rfcs/pull/2107 sebagai fn main() -> Result<(), Box<Debug>> catch { your_code_here(); } (menggunakan sintaks dari https:// github.com/rust-lang/rust/issues/41414#issuecomment-373985777).

Versi impl Trait keren, dan mungkin berfungsi jika ada semacam dukungan "lebih suka jenis yang sama jika tidak dibatasi" yang dapat digunakan rustc secara internal di ? desugar, tetapi ingatan saya adalah bahwa ide fitur seperti itu cenderung membuat orang-orang yang memahami cara kerjanya mundur dengan ngeri :sweat_smile: Tapi mungkin itu hal internal yang hanya berlaku jika outputnya impl Trait akan layak...

Ooh, itu kekhawatiran yang sangat nyata. Saya sudah lupa tentang bagaimana itu akan mengacaukan inferensi tipe. Jika blok tangkap mulai membungkus Ok seperti masalah terkait itu, maka itu sepertinya jalan yang jauh lebih mudah (dan lebih cepat, dari segi stabilisasi).

Satu-satunya hal yang saya ingin tahu adalah bagaimana hal itu akan terpengaruh oleh transisi edisi. Bukankah sintaks catch berubah di zaman 2018? Rustdoc mungkin ingin mengkompilasi doctests dalam edisi yang sama dengan library yang dijalankannya, jadi Rustdoc perlu membedakan antara sintaks berdasarkan flag Epoch yang diberikan.

Saya khawatir ini sekarang stabil tetapi kasus sederhana tampaknya masih ICE: https://github.com/rust-lang/rust/issues/48890#issuecomment -375952342

fn main() -> Result<(), &'static str> {
    Err("An error message for you")
}
assertion failed: !substs.has_erasable_regions(), librustc_trans_utils/symbol_names.rs:169:9

https://play.rust-lang.org/?gist=fe6ae28c67e7d3195a3731839d4aac84&version=nightly

Pada titik apa kita mengatakan "ini terlalu buggy dan kita harus tidak stabil"? Sepertinya jika ini mengenai saluran stabil dalam bentuknya saat ini, itu akan menyebabkan banyak kebingungan.

@frewsxcv Saya pikir masalahnya sudah diperbaiki sekarang, bukan?

@nikomatsakis masalah yang saya angkat di https://github.com/rust-lang/rust/issues/48389 diselesaikan dalam 1,26-beta, jadi ya dari sudut pandang saya.

Yap, ICE yang saya khawatirkan sudah diperbaiki sekarang!

Permintaan maaf karena menimpali pada titik di mana mungkin sudah terlambat untuk melakukan sesuatu tentang ini, tetapi saya ingin meninggalkan umpan balik saya di sini jika ada. Saya memang membaca sebagian besar utas ini, jadi saya berbicara dengan konteks itu dalam pikiran. Namun, utas ini panjang, jadi jika sepertinya saya telah mengabaikan sesuatu, maka saya mungkin melakukannya dan saya akan menghargai jika itu ditunjukkan kepada saya. :-)

TL;DR - Saya pikir menampilkan pesan kesalahan Debug adalah kesalahan, dan pilihan yang lebih baik adalah menggunakan pesan kesalahan Display .

Inti dari keyakinan saya adalah bahwa, sebagai seseorang yang _secara rutin membangun program CLI di Rust_, saya tidak pernah ingat terlalu peduli tentang apa pesan Debug dari Error itu. Yaitu, Debug dari kesalahan, menurut desain, untuk pengembang, bukan untuk pengguna akhir. Saat Anda membuat program CLI, antarmukanya pada dasarnya dimaksudkan untuk dibaca oleh pengguna akhir, jadi pesan Debug memiliki utilitas yang sangat sedikit di sini. Artinya, jika ada program CLI yang saya kirimkan ke pengguna akhir menunjukkan representasi debug dari nilai Rust dalam operasi normal, maka saya akan menganggap bahwa bug harus diperbaiki. Saya biasanya berpikir ini harus benar untuk setiap program CLI yang ditulis dalam Rust, meskipun saya mengerti itu mungkin titik di mana orang yang berakal bisa tidak setuju. Dengan demikian, implikasi yang agak mengejutkan menurut pendapat saya adalah bahwa kami telah secara efektif menstabilkan fitur di mana mode operasi default memulai Anda dengan bug (sekali lagi, IMO) yang harus diperbaiki.

Dengan menunjukkan Debug representasi kesalahan secara default, saya juga berpikir kami mendorong praktik yang buruk. Secara khusus, sangat umum dalam menulis program Rust CLI untuk mengamati bahwa bahkan Display impl kesalahan tidak cukup baik untuk dikonsumsi oleh pengguna akhir, dan bahwa pekerjaan nyata harus dilakukan untuk memperbaikinya. Contoh nyata dari ini adalah io::Error . Menampilkan io::Error tanpa jalur file yang sesuai (dengan asumsi itu berasal dari membaca/menulis/membuka/membuat file) pada dasarnya adalah bug, karena sulit bagi pengguna akhir untuk melakukan apa pun dengannya. Dengan memilih untuk menampilkan Debug representasi kesalahan secara default, kami telah mempersulit bug semacam itu untuk ditemukan oleh orang-orang yang membuat program CLI. (Selain itu, Debug dari io::Error jauh lebih tidak berguna daripada Display , tetapi itu sendiri bukan masalah besar dalam pengalaman saya. )

Akhirnya, untuk melengkapi argumen saya, saya juga mengalami kesulitan membayangkan keadaan di mana saya akan menggunakan ?-in-main bahkan dalam contoh. Yaitu, saya telah mencoba untuk menulis contoh yang paling cocok dengan program dunia nyata, dan ini umumnya melibatkan penulisan hal-hal seperti ini:

use std::error::Error;
use std::process;

fn try_main() -> Result<(), Box<Error>> {
    // do stuff with `?`
}

fn main() {
    if let Err(err) = try_main() {
        eprintln!("{}", err);
        process::exit(1);
    }
}

Sekilas, akan _indah_ untuk mengganti ini dengan ?-in-main , tapi saya tidak bisa, karena itu tidak akan menunjukkan Display dari kesalahan. Artinya, ketika menulis program CLI nyata, saya sebenarnya akan menggunakan pendekatan di atas, jadi jika saya ingin contoh saya mencerminkan kenyataan, maka saya pikir saya harus menunjukkan apa yang saya lakukan dalam program nyata dan tidak mengambil jalan pintas (sampai batas yang wajar ). Saya benar-benar berpikir hal semacam ini sangat penting, dan satu efek samping dari ini secara historis adalah bahwa hal itu menunjukkan kepada orang-orang bagaimana menulis kode Rust idiomatik tanpa memercikkan unwrap di mana-mana. Tetapi jika saya kembali menggunakan ?-in-main dalam contoh saya, maka saya baru saja mengingkari tujuan saya: Saya sekarang menyiapkan orang-orang yang mungkin tidak tahu lebih baik untuk hanya menulis program yang, secara default, memancarkan sangat pesan kesalahan yang tidak membantu.

Pola "pancarkan pesan kesalahan dan keluar dengan kode kesalahan yang sesuai" sebenarnya digunakan dalam program yang dipoles. Misalnya, jika ?-in-main menggunakan Display , maka saya dapat menggabungkan fungsi main dan run di ripgrep hari ini:

https://github.com/BurntSushi/ripgrep/blob/64317bda9f497d66bbeffa71ae6328601167a5bd/src/main.rs#L56 -L86

Tentu saja, saya dapat menggunakan ?-in-main di masa mendatang dengan memberikan impl saya sendiri untuk sifat Termination setelah sifat itu stabil, tetapi mengapa saya repot-repot melakukannya jika saya dapat menuliskan main fungsi impl itu dalam contoh untuk membuatnya sesuai dengan kenyataan, dan pada saat itu, saya sebaiknya tetap menggunakan contoh yang saya miliki hari ini (menggunakan main dan sebuah try_main ).

Dari kelihatannya, tampaknya memperbaiki ini akan menjadi perubahan yang menghancurkan. Artinya, kode ini dikompilasi di Rust stable hari ini:

#[derive(Debug)]
struct OnlyDebug;

fn main() -> Result<(), OnlyDebug> {
    Err(OnlyDebug)
}

Saya pikir beralih ke Display akan merusak kode ini. Tapi saya tidak tahu pasti! Jika ini benar-benar masalah kapal-telah berlayar, maka saya mengerti dan tidak ada gunanya menjelaskan intinya, tetapi saya merasa cukup kuat tentang ini untuk setidaknya mengatakan sesuatu dan melihat apakah saya tidak dapat meyakinkan orang lain dan melihat jika ada yang bisa dilakukan untuk memperbaikinya. (Mungkin juga saya bereaksi berlebihan di sini, tetapi sejauh ini ada beberapa orang yang bertanya kepada saya "mengapa Anda tidak menggunakan ?-in-main ?" Dalam contoh CSV saya, dan jawaban saya pada dasarnya adalah, "Saya tidak melihat bagaimana mungkin untuk melakukan itu." Mungkin itu bukan masalah yang dimaksudkan untuk diselesaikan oleh ?-in-main , tetapi beberapa orang pasti memiliki kesan itu. Dengan implementasinya saat ini , saya bisa melihatnya berguna dalam tes dokumen dan tes unit, tetapi saya kesulitan memikirkan situasi lain di mana saya akan menggunakannya.)

Saya setuju bahwa menampilkan Display di atas Debug lebih baik untuk aplikasi CLI. Saya tidak setuju bahwa itu seharusnya Display alih -alih Debug , karena itu secara drastis membatasi kesalahan apa yang sebenarnya bisa ? -ed dan mengalahkan tujuan ?-in-main . Sejauh yang saya tahu (Bisa jadi benar-benar salah karena saya tidak punya waktu untuk mengkompilasi stdlib dan saya tidak tahu apakah spesialisasi mencakup ini) tidak ada alasan kami tidak dapat menambahkan impl berikut ke Pemutusan. Ini akan memberikan cara yang tidak putus-putus untuk menggunakan Display bila tersedia dan kembali ke Debug bila tidak tersedia.

#[unstable(feature = "termination_trait_lib", issue = "43301")]
impl<E: fmt::Display> Termination for Result<!, E> {
    fn report(self) -> i32 {
        let Err(err) = self;
        eprintln!("Error: {}", err);
        ExitCode::FAILURE.report()
    }
}

Akhirnya, untuk melengkapi argumen saya, saya juga mengalami kesulitan membayangkan keadaan di mana saya akan menggunakan ?-in-main bahkan dalam contoh.

Saya harus setuju dengan @BurntSushi secara umum untuk program CLI nyata, tetapi untuk skrip acak dan alat internal yang hanya saya rencanakan untuk digunakan, ini sangat nyaman. Juga, sangat nyaman untuk prototipe dan mainan. Kami selalu dapat mencegah untuk benar-benar menggunakannya dalam kode produksi, bukan?

Bagian dari tujuan dengan ?-in-main adalah untuk menghindari kode unidiomatik dalam contoh kode, yaitu unwraps. Tetapi jika ?-in-main itu sendiri menjadi unidiomatik, maka itu merusak seluruh fitur. Saya sangat ingin menghindari hasil apa pun yang akan menyebabkan kita tidak boleh menggunakannya, dalam kode produksi atau lainnya.

Saya telah mencoba menggunakan fitur ini di main dan saya merasa cukup frustasi. Implikasi yang ada dari sifat terminasi tidak memungkinkan saya untuk dengan mudah "mengakumulasikan" beberapa jenis kesalahan -- misalnya, saya tidak dapat menggunakan failure::Fail, karena tidak mengimplementasikan Error; Saya tidak bisa menggunakan Kotak, alasan yang sama. Saya pikir kita harus memprioritaskan perubahan ke Debug. =)

Jika kita menggunakan Display , kita dapat memperkenalkan tipe cepat dan kotor seperti MainError untuk mengakumulasi beberapa jenis kesalahan:

pub struct MainError {
    s: String,
}

impl std::fmt::Display for MainError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.s.fmt(f)
    }
}

impl<T> From<T> for MainError where T: std::error::Error {
    fn from(t: T) -> Self {
        MainError {
            s: t.to_string(),
        }
    }
}

Ini akan memungkinkan sesuatu seperti di bawah ini mirip dengan Box<Error> :

fn main() -> Result<(), MainError> {
    let _ = std::fs::File::open("foo")?;
    Ok(())
}

Diskusi sebelumnya tentang Tampilan vs Debug ada di bagian tersembunyi di sini, mulai sekitar https://github.com/rust-lang/rust/issues/43301#issuecomment -362020946.

@BurntSushi Saya setuju dengan Anda. Jika ini tidak dapat diperbaiki, mungkin ada solusi:

use std::fmt;
struct DisplayAsDebug<T: fmt::Display>(pub T);

impl<T: fmt::Display> Debug for DisplayAsDebug {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(&self.0, f)
    }
}

impl<T: fmt::Display> From<T> for DisplayAsDebug {
    fn from(val: T) -> Self {
        DisplayAsDebug(val)
    }
}

Jika ini di std::fmt , kita bisa menggunakan sesuatu seperti ini:

use std::{fmt, io};

fn main() -> Result<(), fmt::DisplayAsDebug<io::Error>> {
    let mut file = File::open("/some/file")?;
    // do something with file
}

Saya masih tidak punya waktu untuk berkontribusi lagi untuk ini di masa mendatang, tetapi saya juga setuju bahwa ? di main harus dapat digunakan dalam program CLI yang serius, dan ada fitur di draf asli RFC yang dimaksudkan untuk memfasilitasi itu. Mereka dijatuhkan atas nama inkrementalisme tetapi mungkin mereka harus ditinjau kembali.

Saya pikir tidak apa-apa untuk memperbaikinya dengan cara yang tidak kompatibel jika dilakukan segera, sebelum banyak kode di luar sana menggunakannya pada stabil.

Sebagai seseorang yang menulis banyak program CLI di Rust, dan yang bertanggung jawab atas gaya Rust di tempat kerja, saya cukup setuju dengan @burntsushi di sini. Saya akan dengan senang hati menggunakan versi fitur ini yang ditentukan dalam RFC.

Tetapi saya menganggap menunjukkan implementasi Debug kepada pengguna sebagai bug, dan tidak jauh lebih baik daripada hanya memanggil unwrap di mana-mana dalam program CLI. Jadi bahkan dalam kode contoh, saya tidak dapat membayangkan pernah menggunakan versi fitur ini. Ini terlalu buruk, karena menghapus boilerplate dari main akan menyederhanakan pembelajaran bagi pengembang Rust baru di tempat kerja, dan kami tidak perlu menjelaskan quick_main! atau yang setara lagi.

Cukup banyak satu-satunya keadaan di mana saya dapat membayangkan menggunakan ini adalah untuk menghilangkan unwrap dari doctests. Tapi saya tidak tahu apakah itu didukung.

Jika kita menggunakan Display , kita dapat memperkenalkan tipe cepat dan kotor seperti MainError untuk mengakumulasi beberapa jenis kesalahan:

Secara pribadi, solusi semacam ini akan menambah kerumitan yang cukup sehingga lebih mudah untuk menghindari ? -in- main sepenuhnya.

@Aaronepower :

Sejauh yang saya tahu (Bisa jadi benar-benar salah karena saya tidak punya waktu untuk mengkompilasi stdlib dan saya tidak tahu apakah spesialisasi mencakup ini) tidak ada alasan kami tidak dapat menambahkan impl berikut ke Pemutusan. Ini akan memberikan cara yang tidak putus-putus untuk menggunakan Tampilan saat itu tersedia dan kembali ke Debug saat tidak.

Jika ini memungkinkan saya untuk menulis fn main() -> Result<(), failure::Error> dan mendapatkan kesalahan yang bagus dan dapat dibaca manusia menggunakan Display , itu pasti akan memuaskan kekhawatiran utama saya. Namun, saya kira ini masih akan meninggalkan pertanyaan untuk menampilkan backtrace secara opsional di failure::Error ketika RUST_BACKTRACE=1 disetel—tetapi itu mungkin di luar cakupan, atau setidaknya masalah yang seharusnya diambil dengan failure .

Ini telah distabilkan beberapa waktu lalu , dan saya rasa kita tidak dapat benar-benar mengubah perilaku Result . Saya pribadi tetap cukup senang dengan perilaku saat ini untuk kasus penggunaan yang cenderung saya lakukan — skrip cepat dan kotor dan sejenisnya — tetapi saya setuju bahwa skrip yang lebih kompleks tidak akan menginginkannya. Namun, pada saat diskusi, kami juga membahas sejumlah cara Anda dapat mengontrol perilaku itu (sepertinya saya tidak dapat menemukan komentar itu sekarang karena github menyembunyikan sesuatu dari saya).

Misalnya, Anda dapat menentukan "pembungkus" Anda sendiri untuk jenis kesalahan, dan menerapkan From untuk itu:

struct PrettyPrintedError { ... }
impl<E: Display> From<E> for PrettyPrintedError { }

impl Debug { /* .. invoke Display .. */ }

Sekarang Anda dapat menulis sesuatu seperti ini, yang berarti Anda dapat menggunakan ? di main :

fn main() -> Result<(), PrettyPrintedError> { ... }

Mungkin tipe seperti itu harus menjadi bagian dari quick-cli atau semacamnya?

@nikomatsakis Ya, saya benar-benar mendapatkan solusi itu, tapi saya merasa itu mengalahkan tujuan ringkas menggunakan ?-in-main . Saya merasa "mungkin untuk menggunakan ?-in-main menggunakan solusi ini" sayangnya merusak ?-in-main itu sendiri. Misalnya, saya tidak akan menuliskan solusi Anda dalam contoh ringkas dan saya juga tidak akan memaksakan ketergantungan pada quicli untuk setiap contoh yang saya tulis. Saya juga tidak akan menggunakannya untuk program cepat, karena saya ingin Display output dari kesalahan pada dasarnya setiap program CLI yang saya tulis. Pendapat saya adalah bahwa output debug dari kesalahan adalah bug dalam program CLI yang Anda letakkan di depan pengguna.

Alternatif untuk ?-in-main adalah fungsi ~4-baris tambahan. Jadi jika solusi untuk memperbaiki ?-in-main untuk menggunakan Display lebih dari itu, maka saya pribadi tidak melihat banyak alasan untuk menggunakannya sama sekali (di luar tes dokumen atau tes unit, karena disebutkan di atas).

Apakah ini sesuatu yang bisa diubah dalam edisi?

@BurntSushi Bagaimana perasaan Anda tentang solusi yang ada di std , jadi Anda hanya perlu menulis deklarasi tipe pengembalian sedikit lebih lama pada main() ?

@Kixunil Perasaan awal saya adalah mungkin enak. Belum terlalu memikirkannya.

@BurntSushi

Alternatif untuk ?-in-main adalah fungsi ~4-baris tambahan.

Pada akhirnya, perilakunya stabil, dan menurut saya kita tidak dapat mengubahnya secara realistis saat ini. (Maksud saya itu bukan pelanggaran kesehatan atau semacamnya.)

Yang mengatakan, saya masih menemukan pengaturan saat ini cukup bagus dan lebih disukai daripada Display , tapi saya kira itu masalah apa yang Anda inginkan "default" - yaitu, pengaturan dapat dibuat untuk bekerja seperti lainnya melalui berbagai tipe baru. Jadi kami memilih skrip "quick-n-dirty" secara default, yang menginginkan Debug , atau produk akhir yang dipoles. Saya cenderung berpikir produk akhir yang dipoles adalah tempat saya dapat membeli impor tambahan dengan cukup mudah, dan juga tempat saya ingin memilih dari berbagai format yang mungkin (misalnya, apakah saya hanya menginginkan kesalahan, atau apakah saya ingin menyertakan argv [0], dll).

Untuk lebih konkrit, saya membayangkan akan terlihat seperti ini:

use failure::format::JustError;

fn main() -> Result<(), JustError> { .. }

atau, untuk mendapatkan format yang berbeda, mungkin saya melakukan ini:

use failure::format::ProgramNameAndError;

fn main() -> Result<(), ProgramNameAndError> { .. }

Keduanya terasa cukup bagus dibandingkan dengan fungsi 4-baris yang harus Anda tulis sebelumnya:

use std::sys;

fn main() {
  match inner_main() {
    Ok(()) => { }
    Err(error) => {
      println!("{}", error);
      sys::exit(1);
    }
}

fn inner_main() -> Result<(), Error> {
  ...
}

Saya membahas topik ini karena, untuk kualitas produksi, menambahkan apa yang pada dasarnya merupakan jenis kesalahan khusus untuk output sepertinya merupakan arah yang baik untuk mendorong orang. Ini tampaknya paralel dengan bagaimana panic! , misalnya, memberikan pesan kualitas debug secara default.

@nikomatsakis Itu mungkin pandangan jangka panjang yang lebih baik, dan saya menemukan hasil akhir Anda menggugah selera. Alangkah baiknya jika jenis kesalahan tersebut berakhir di std suatu hari nanti sehingga dapat digunakan dalam contoh dengan overhead sesedikit mungkin. :-)

Catatan proses: Saya mencoba membingkai umpan balik awal saya di sini dengan "apakah ada yang bisa dilakukan" daripada "mari kita ubah sesuatu yang sudah stabil," dalam upaya untuk fokus pada tujuan tingkat tinggi daripada meminta sesuatu yang kita pasti tidak bisa. :-)

Nah itu tidak butuh waktu lama: https://crates.io/crates/exitfailure

Saya bertanya-tanya berapa banyak orang yang terkejut dengan menggunakan sifat Debug versus Display sifat. Baik draf diskusi awal dan RFC akhir menggunakan Display . Ini mirip dengan insiden sifat impl universal, di mana orang mengira mereka mendapatkan satu hal, tetapi baru tahu mereka mendapatkan yang lain setelah itu distabilkan. Juga mengingat banyak orang yang terkejut tidak akan menggunakan fitur ini, saya merasa tidak akan ada backslash besar jika kita mengubahnya di edisi berikutnya.

@WiSaGaN Detail RFC dapat dan akan berubah dari waktu ke waktu. Ini diharapkan dan sehat. RFC adalah dokumen desain dan bukan representasi sempurna dari apa yang ada dan akan selalu ada. Penemuan informasi itu sulit, dan itu salahku karena tidak memberi perhatian lebih. Saya berharap bahwa kita dapat menghindari mengurangi stabilitas fitur ini dan sebagai gantinya berfokus pada sasaran tingkat yang lebih tinggi yang dapat kita tindak lanjuti dengan layak.

@nikomatsakis Masalah utama yang saya lihat dengan mendukung kasus quick-n-dirty adalah bahwa sudah ada cara untuk mengatasinya: .unwrap() . Jadi dari perspektif itu, ? hanyalah cara lain untuk menulis hal-hal yang cepat dan kotor, namun tidak ada cara yang sama mudahnya untuk menulis hal-hal yang halus.

Tentu saja kapal telah berlayar, jadi kami kebanyakan kacau. Saya ingin melihat ini diubah di edisi berikutnya, jika memungkinkan.

@Kixunil , unwrap benar-benar bukan cara yang baik untuk mengatasi berbagai hal, karena judul utas ini menyatakan kami ingin dapat menggunakan gula sintaksis ? di main . Saya bisa mengerti dari mana Anda berasal (sebenarnya itu adalah bagian dari keraguan awal saya dengan penggunaan ? untuk penanganan opsi) tetapi dengan dimasukkannya ? dalam bahasa masalah ini adalah kegunaan yang cukup bagus, menangkan IMHO. Jika Anda membutuhkan output yang lebih bagus, maka ada opsi untuk Anda. Anda tidak merilis sesuatu kepada pengguna tanpa mengujinya terlebih dahulu, dan penemuan bahwa Anda memerlukan tipe kustom untuk output main akan menjadi realisasi yang cukup cepat.

Adapun "mengapa" kami ingin ? di main , pertimbangkan betapa anehnya sekarang. Anda dapat menggunakannya secara praktis di tempat lain (karena Anda memiliki kendali atas tipe pengembalian). Fungsi main kemudian berakhir dengan perasaan yang agak istimewa, yang seharusnya tidak.

.unwrap() adalah solusi cepat dan kotor yang tidak menulis, jadi Anda tidak dapat memasukkan dan mengeluarkan kode dari main() saat Anda membuat sketsa program.

Sebagai perbandingan, ? adalah solusi cepat dan kotor yang membuat, dan membuatnya tidak cepat dan kotor adalah masalah menempatkan tipe pengembalian yang benar pada main() , bukan memodifikasi kode itu sendiri.

@Screwtapello ah, sekarang masuk akal bagi saya. Terima kasih!

Saya hanya ingin mengungkapkan bahwa saya pikir tujuannya adalah untuk membawa ? dalam main untuk semua aplikasi tanpa harus menggunakan pembungkus tambahan... Jika hanya untuk pengujian, saya tidak melihat banyak manfaat di dalamnya dan akan tetap berpegang pada .unwrap() , sayangnya.

@oblitum Ini bukan hanya untuk pengujian. Hanya saja (secara default) tidak akan menggunakan format Display . Ini mungkin atau mungkin bukan keluaran yang Anda cari, tetapi hampir pasti akan lebih baik daripada keluaran .unwrap . Dengan demikian, mungkin membuat kode Anda "lebih baik" untuk digunakan ? . Bagaimanapun, masih mungkin untuk menyesuaikan output dari kesalahan ? di main seperti yang telah dijelaskan oleh @nikomatsakis di atas.

Apakah mungkin untuk memperkenalkan tipe baru di stdlib untuk etalase? Sesuatu seperti:

#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[must_use]
struct DisplayResult<E: fmt::Display>(Result<(), E>)

impl<E> Termination for DisplayResult<E> {
    // ... same as Result, but use "{}" instead of "{:?}"
}

impl<E> From<Result<(), E>> for DisplayResult<E> {}
impl<E> Deref<Result<(), E>> for DisplayResult<E> {}
impl<E> Try for DisplayResult<E> {}
// ...

Ini akan memungkinkan pengguna untuk menulis:

fn main() -> DisplayResult<MyError> {
    // Ordinary code; conversions happen automatically via From, Try, etc.
}

Motivasi utama di sini adalah:

  • Tampilan digunakan ketika kesalahan dikembalikan
  • Pengguna tidak perlu melakukan pembungkusan tambahan untuk jenis kesalahan atau hasil mereka; mereka hanya dapat menggunakan ? dalam fungsi utama mereka.

Tindak lanjut: Terpikir oleh saya bahwa semantik ? dan From mungkin tidak memungkinkan ini berfungsi secara implisit seperti yang saya inginkan. Saya tahu bahwa ? akan mengonversi antara jenis Kesalahan melalui From , tetapi saya tidak tahu apakah itu melakukan hal yang sama untuk pelaksana berbeda dari Try . Artinya, ia akan mengonversi dari Result<?, io::Error> ke Result<?, FromIoError> , tetapi tidak harus dari Result<?, Err> ke DisplayResult<Result<?, Err>> . Pada dasarnya, saya mencari sesuatu seperti ini untuk bekerja:

fn main() -> DisplayResult<io::Error> {
    let f = io::File::open("path_to_file")?;
    let result = String::new();
    f.read_to_string(result)?
}

Dengan asumsi bahwa ini tidak berhasil, mungkin sebagai gantinya ini bisa dibuat flag kompilasi bersyarat sederhana di Cargo.toml?

@Lucretiel : Anda tampaknya menganggap programmer terutama hanya ingin mencetak pesan keluar ke konsol. Ini tidak berlaku untuk aplikasi desktop non-teknis, daemon dengan pipa logging, dll. Saya lebih suka kita tidak menambahkan hal-hal yang 'berguna secara umum' ke perpustakaan standar tanpa bukti kuat (misalnya angka) yang seharusnya ada di sana.

Saya berasumsi bahwa programmer yang mengembalikan Result<(), E> where E: Error dari main tertarik untuk mencetak pesan kesalahan ke konsol, ya. Bahkan perilaku saat ini, yang dicetak melalui Debug , adalah "cetak pesan keluar ke konsol".

Tampaknya ada kesepakatan luas di utas ini bahwa sesuatu harus dicetak ke stderr; keputusan utama yang harus dibuat adalah "Haruskah dicetak dengan "{}" atau "{:?}" , atau sesuatu yang lain, dan seberapa dapat dikonfigurasi, jika ada".

@Lucretiel , bagi saya nilai mengembalikan Result mengontrol status keluar dengan baik. Apakah akan mencetak sesuatu yang tidak terbaik diserahkan kepada penangan panik? Tidak hanya pertanyaan apakah akan mencetak sesuatu ke stderr, juga apa yang harus dicetak (lacak balik, pesan kesalahan, dll?). Lihat https://github.com/rust-lang/rust/issues/43301#issuecomment -389099936, khususnya. bagian terakhir.

Saya menggunakan ini baru-baru ini dan berharap itu disebut Display . Cukup yakin kami membuat panggilan yang salah di sini.

Kegagalan proses yang saya perhatikan adalah bahwa keputusan dibuat oleh tim lang (terutama Niko dan saya) daripada tim libs, tetapi kode yang dimaksud sepenuhnya hidup di std. Mungkin lib akan membuat keputusan yang lebih baik.

@withoutboats apakah sudah terlambat untuk berubah? Saya perhatikan bahwa fitur tersebut masih ditandai sebagai khusus malam hari/eksperimental di docs , tetapi mungkin saya tidak memahami beberapa perincian di balik proses stabilisasi.

Saya juga ingin melihat perubahan ini dan menyesal tidak menjadi pendukung yang lebih kuat untuk itu ketika tim meminta saya untuk mengubah implementasi dari Display menjadi Debug .

@Lucretiel Termination sifat adalah nightly, tetapi fitur itu sendiri (mengembalikan Termination implementasi dari main /tests) sudah stabil.

Apakah ada tiket atau garis waktu untuk menstabilkan nama sifat?

@xfix Itu menyiratkan bahwa mungkin untuk mengubah implementasinya, bukan? Perilaku tidak berubah ( Termination dikembalikan dari main ), tetapi sifat itu sendiri akan berubah dari:

impl<E: Debug> Termination for Result<(), E> ...

ke:

impl<E: Display> Termination for Result<(), E> ...

itu mungkin untuk mengubah implementasi, kan?

Bukan tanpa melanggar kompatibilitas ke belakang. Kode ini dikompilasi dalam Rust stabil hari ini:

#[derive(Debug)]
struct X;

fn main() -> Result<(), X> {
    Ok(())
}

Dengan perubahan yang diusulkan memerlukan Display , itu akan berhenti dikompilasi.

Seperti yang telah disebutkan , mungkin sesuatu seperti spesialisasi dapat membantu, tetapi pemahaman saya saat ini tentang spesialisasi adalah itu hanya akan berguna untuk jenis yang mengimplementasikan Display dan Debug (yaitu sedemikian rupa sehingga ada satu implementasi yang lebih istimewa).

trait ResultTerm {
    fn which(&self);
}
impl<T: Debug> ResultTerm for T {
    default fn which(&self) {
        println!("{:?}", self)
    }
}
impl<T: Debug + Display> ResultTerm for T {
    fn which(&self) {
        println!("{}", self)
    }
}

enum MyResult<T, E> {
    Ok(T),
    Err(E),
}

impl<T, E> Termination for MyResult<T, E>
where
    E: ResultTerm,
{
    fn report(self) -> i32 {
        match self {
            MyResult::Err(e) => {
                e.which();
                1
            }
            _ => 0,
        }
    }
}

tempat bermain

Ini mungkin cukup untuk mencakup semua kasus umum, tetapi ini mengekspos spesialisasi secara publik.

Ah, saya mengerti apa yang Anda maksud tentang karat yang stabil. Untuk beberapa alasan saya tidak menyadari bahwa ini tersedia di stable.

Apakah ini sesuatu yang dapat diubah pada waktu kompilasi dengan bendera Kargo? Dengan cara yang sama seperti hal-hal lain yang dapat dialihkan, seperti panic = "abort" .

Mungkin? Saya bisa melihatnya layak untuk memiliki perilaku yang berbeda untuk ? di main di edisi Rust lainnya. Mungkin bukan 2018, tapi mungkin 2021?

Peti yang berbeda dalam program yang sama dapat berada dalam edisi yang berbeda tetapi menggunakan std yang sama, sehingga ciri-ciri yang tersedia di std dan implikasinya harus sama di seluruh edisi. Apa yang bisa kita lakukan secara teori (saya tidak menganjurkan ini) adalah memiliki dua sifat, Termination dan Termination2 , dan memerlukan tipe pengembalian main() untuk mengimplementasikan satu atau lainnya tergantung pada edisi peti yang mendefinisikan main() .

Saya tidak berpikir kita perlu mencela apa pun. Saya akan menggemakan sentimen @Aaronepower bahwa membatasi hanya jenis Tampilan dapat berfungsi untuk membuat pengguna skrip cepat tidak puas (mendapatkan Debug itu sepele, mengimplementasikan Tampilan tidak) dengan cara yang sama seperti situasi saat ini tidak memuaskan pengguna produksi. Bahkan jika kita bisa, saya tidak berpikir itu pada akhirnya akan bermanfaat. Saya pikir pendekatan spesialisasi yang ditetapkan oleh @shepmaster menjanjikan, karena itu akan menciptakan perilaku berikut:

  1. Jenis yang memiliki Debug tetapi tidak Display akan memiliki keluaran Debug yang dicetak secara default
  2. Jenis yang memiliki Debug dan Tampilan akan memiliki output Tampilan yang dicetak secara default
  3. Jenis yang memiliki Tampilan tetapi tidak Debug menghasilkan kesalahan kompiler

Seseorang mungkin keberatan dengan poin 2, jika Anda memiliki tipe dengan Tampilan tetapi Anda ingin mencetak Debug karena alasan apa pun; Saya pikir kasus ini akan ditangani oleh proposal Niko tentang berbagai format keluaran yang telah dirancang sebelumnya Jenis kesalahan (yang mungkin perlu ditelusuri bahkan jika kita menggunakan rute spesialisasi).

Adapun poin 3, ini sedikit tersendat (mungkin kita seharusnya melakukan trait Display: Debug kembali di 1.0?), tetapi jika seseorang kesulitan menulis tampilan impl maka tidak banyak yang meminta mereka menampar satu turunan (yang, saya kira, mungkin mereka miliki ... apakah ada orang di luar sana yang pada prinsipnya keberatan memiliki Debug pada tipe Tampilan mereka?).

membatasi hanya jenis Tampilan yang dapat berfungsi hanya untuk membuat pengguna skrip cepat tidak puas (mendapatkan Debug itu sepele, mengimplementasikan Tampilan tidak)

Kasus di mana Display lebih disukai daripada Debug dalam skrip cepat menggunakan &'static str atau String sebagai jenis kesalahan. Jika Anda memiliki jenis kesalahan khusus sama sekali untuk menampar #[derive(Debug)] ke Anda sudah menghabiskan beberapa energi non sepele pada penanganan kesalahan.

@SimonSapin Saya tidak akan mengatakan itu lebih disukai di lingkungan skrip cepat, atau lebih tepatnya tidak cukup disukai sehingga saya akan berada dalam posisi di mana saya ingin String dicetak sebagai format Display tanpa ingin berusaha memiliki kesalahan yang diformat dengan benar, Bagi saya Debug lebih disukai karena format kesalahan ketika String tidak terbentuk dengan baik sehingga memiliki Err("…") di sekitarnya memungkinkan saya dengan mudah memilih kesalahan secara visual dari yang lain keluaran yang mungkin dikeluarkan.

FWIW yang diformat bukan nilai Result<_, E> , hanya nilai E di dalamnya. Jadi Anda tidak akan melihat Err( di output. Namun kode saat ini menambahkan awalan Error: , yang mungkin akan tetap ada jika Display digunakan. Selain tidak mencetak tanda kutip, Display juga tidak akan menghindari garis miring terbalik dari isi string yang diinginkan IMO ketika harus menampilkan pesan (kesalahan) kepada pengguna.

https://github.com/rust-lang/rust/blob/cb6eeddd4dcefa4b71bb4b6bb087d05ad8e82145/src/libstd/process.rs#L1527 -L1533

Saya pikir mengingat situasinya, hal terbaik yang dapat kita lakukan adalah spesialisasi untuk tipe yang mengimplementasikan Display + Debug untuk mencetak Display impl. Sangat disayangkan bahwa tipe yang tidak mengimplementasikan Debug tidak dapat digunakan sebagai kesalahan (sampai kami mendukung interseksi impls), tetapi ini adalah yang terbaik yang bisa kami lakukan.

Pertanyaan utamanya adalah apakah impl itu termasuk dalam kebijakan kami saat ini untuk impls khusus di std. Kami umumnya memiliki aturan bahwa kami hanya menggunakan spesialisasi di std di mana kami tidak memberikan jaminan yang stabil bahwa perilaku tidak akan berubah nanti (karena spesialisasi itu sendiri adalah fitur yang tidak stabil). Apakah kami nyaman memberikan impl ini sekarang dengan kesadaran bahwa mungkin suatu hari nanti jenis ini akan kembali mencetak output Debug mereka jika pada akhirnya kami menghapus spesialisasi sebagai fitur?

Sebenarnya, saya pikir kita tidak bisa membiarkan spesialisasi ini sekarang, karena itu bisa digunakan untuk memperkenalkan ketidaksehatan. Impl ini sebenarnya adalah jenis spesialisasi yang menyebabkan banyak masalah bagi kita!

use std::cell::Cell;
use std::fmt::*;

struct Foo<'a, 'b> {
     a: &'a Cell<&'a i32>,
     b: &'b i32,
}

impl<'a, 'b> Debug for Foo<'a, 'b> {
    fn fmt(&self, _: &mut Formatter) -> Result {
        Ok(())
    }
}

impl<'a> Display for Foo<'a, 'a> {
    fn fmt(&self, _: &mut Formatter) -> Result {
        self.a.set(self.b);
        Ok(())
    }
}

Karena cara spesialisasi bekerja saat ini, saya dapat memanggil report pada Foo<'a, 'b> di mana 'b tidak hidup lebih lama dari 'a , dan saat ini sangat mungkin bahwa kompiler akan memilih impl yang memanggil ke instance Display meskipun persyaratan masa pakai tidak terpenuhi, memungkinkan pengguna untuk memperpanjang masa pakai referensi secara tidak valid.

Saya tidak tahu tentang kalian, tetapi ketika saya menulis "skrip" cepat, saya hanya unwrap() omong kosong dari segalanya. Ini 100 kali lebih baik, karena memiliki jejak balik yang memberi saya informasi kontekstual. Tanpa itu, jika saya memiliki let ... = File::open() lebih dari sekali dalam kode saya, saya sudah tidak dapat mengidentifikasi mana yang gagal.

Saya tidak tahu tentang kalian, tetapi ketika saya menulis "skrip" cepat, saya hanya unwrap() omong kosong dari segalanya. Ini 100 kali lebih baik, karena memiliki jejak balik yang memberi saya informasi kontekstual. Tanpa itu, jika saya memiliki let ... = File::open() lebih dari sekali dalam kode saya, saya sudah tidak dapat mengidentifikasi mana yang gagal.

Inilah sebenarnya mengapa saya merasa sangat kuat tentang Display , karena umumnya apa yang saya lakukan dalam kasus ini adalah melakukan map_err yang melampirkan nama file dan informasi kontekstual lain yang relevan. Umumnya output debug kurang membantu untuk melacak apa yang sebenarnya salah, menurut pengalaman saya.

@Kixunil : Atau, saya lebih suka strategi penanganan kesalahan yang lebih rumit dan/atau logging dan/atau debugging yang lebih ekstensif.

Saya kira "Implement the RFC" dapat dicentang, bukan? Setidaknya menurut ini ! :senyum:

Maafkan saya jika ini benar-benar tidak mengerti, tetapi tidak bisakah Anda menggunakan spesialisasi untuk melaporkan rantai kesalahan jika memungkinkan:

use std::error::Error;

impl<E: fmt::Debug> Termination for Result<!, E> {
    default fn report(self) -> i32 {
        let Err(err) = self;
        eprintln!("Error: {:?}", err);
        ExitCode::FAILURE.report()
    }
}

impl<E: fmt::Debug + Error> Termination for Result<!, E> {
    fn report(self) -> i32 {
        let Err(err) = self;
        eprintln!("Error: {:?}", err);

        for cause in Error::chain(&err).skip(1) {
            eprintln!("Caused by: {:?}", cause);
        }
        ExitCode::FAILURE.report()
    }
}

https://github.com/rust-lang/rfcs/blob/f4b8b61a414298ba0f76d9b786d58ccdc34a44bb/text/1937-ques-in-main.md#L260 -L270

impl<T: Termination, E: Display> Termination for Result<T, E> {
    fn report(self) -> i32 {
        match self {
            Ok(val) => val.report(),
            Err(ref err) => {
                print_diagnostics_for_error(err);
                EXIT_FAILURE
            }
        }
    }
}

Apakah ada alasan mengapa bagian RFC ini tidak diterapkan?

Kami memang membuat beberapa perubahan pada kumpulan sifat dan impls yang tepat untuk digunakan, saya tidak ingat detailnya pada saat ini -- dan saya yakin masih ada beberapa detail yang tersisa untuk distabilkan. Anda mungkin ingin memeriksa laporan stabilisasi yang ditautkan dari masalah teratas untuk detail selengkapnya.

Apa status fitur ini? Apakah ada proposal untuk stabilisasi?

@GrayJack Masalah ini harus ditutup, karena ini sudah cukup lama.

Benar, dan saya agak bingung, saya dapatkan di sini dari tautan di Termination trait dan saya bertanya tentang itu, apakah ada masalah yang tepat untuk termination_trait_lib ?

Masalah ini harus ditutup

Masalah ini masih terbuka untuk melacak stabilisasi Termination .

Apakah halaman ini membantu?
0 / 5 - 0 peringkat