Rust: Masalah pelacakan untuk spesialisasi (RFC 1210)

Dibuat pada 23 Feb 2016  ·  236Komentar  ·  Sumber: rust-lang/rust

Ini adalah masalah pelacakan untuk spesialisasi (rust-lang / rfcs # 1210).

Langkah-langkah implementasi utama:

  • [x] Tanah https://github.com/rust-lang/rust/pull/30652 =)
  • [] Batasan seputar pengiriman seumur hidup (saat ini lubang kesehatan )
  • [] default impl (https://github.com/rust-lang/rust/issues/37653)
  • [] Integrasi dengan konstanta terkait
  • [] Batas tidak selalu diberlakukan dengan benar (https://github.com/rust-lang/rust/issues/33017)
  • [] Haruskah kita mengizinkan impls kosong jika orang tua tidak memiliki default anggota? https://github.com/rust-lang/rust/issues/48444
  • [] menerapkan "selalu berlaku" berarti https://github.com/rust-lang/rust/issues/48538
  • [] gambarkan dan uji kondisi siklus yang tepat seputar pembuatan grafik spesialisasi (lihat misalnya komentar ini , yang mencatat bahwa kita memiliki logika yang sangat cermat di sini hari ini)

Pertanyaan yang belum terselesaikan dari RFC:

  • Haruskah tipe terkait dapat dikhususkan sama sekali?
  • Kapan proyeksi mengungkapkan default type ? Tidak pernah selama typeck? Atau kapan monomorfik?
  • Haruskah item ciri default dianggap default (yaitu dapat dikhususkan)?
  • Haruskah kita memiliki default impl (dimana semua item default ) atau partial impl (dimana default diikutsertakan); lihat https://github.com/rust-lang/rust/issues/37653#issuecomment -616116577 untuk beberapa contoh relevan tentang batasan default impl .
  • Bagaimana cara menangani pengiriman seumur hidup?

Perhatikan bahwa fitur specialization seperti yang diterapkan saat ini tidak tepat , yang berarti fitur ini dapat menyebabkan Perilaku Tidak Terdefinisi tanpa kode unsafe . min_specialization menghindari sebagian besar jebakan .

A-specialization A-traits B-RFC-approved B-RFC-implemented B-unstable C-tracking-issue F-specialization T-lang

Komentar yang paling membantu

Saya telah menggunakan #[min_specialization] di perpustakaan eksperimental yang telah saya kembangkan jadi saya pikir saya akan berbagi pengalaman saya. Tujuannya adalah menggunakan spesialisasi dalam bentuk yang paling sederhana: untuk memiliki beberapa kasus sempit dengan implementasi yang lebih cepat daripada kasus umum. Secara khusus, agar algoritme kriptografi dalam kasus umum berjalan dalam waktu yang konstan tetapi kemudian jika semua masukan ditandai Public agar memiliki versi khusus yang berjalan dalam waktu variabel yang lebih cepat (karena jika bersifat publik, kami tidak peduli tentang membocorkan info tentang mereka melalui waktu eksekusi). Selain itu, beberapa algoritme lebih cepat tergantung pada apakah titik kurva eliptik dinormalisasi atau tidak. Untuk membuat ini bekerja, kita mulai

#![feature(rustc_attrs, min_specialization)]

Kemudian jika Anda perlu membuat sifat _specialization predicate_ seperti yang dijelaskan dalam spesialisasi minimal maksimal Anda menandai deklarasi sifat dengan #[rustc_specialization_trait] .

Semua spesialisasi saya dilakukan dalam file ini dan berikut adalah contoh sifat predikat spesialisasi.

Fitur ini berfungsi dan melakukan apa yang saya butuhkan. Ini jelas menggunakan penanda internal rustc dan oleh karena itu rentan pecah tanpa peringatan.

Satu-satunya umpan balik negatif adalah saya merasa kata kunci default masuk akal. Pada dasarnya apa yang dimaksud dengan default sekarang adalah: "impl ini dapat dikhususkan jadi tafsirkan impls yang mencakup subset dari yang satu ini sebagai spesialisasi darinya daripada impl yang bertentangan". Masalahnya adalah ini mengarah ke kode yang tampak sangat aneh:

https://github.com/LLFourn/secp256kfun/blob/6766b60c02c99ca24f816801fe876fed79643c3a/secp256kfun/src/op.rs#L196 -L206

Di sini impl kedua mengkhususkan diri pada yang pertama tetapi juga default . Arti dari default sepertinya hilang. Jika Anda melihat impls lainnya, cukup sulit untuk mengetahui impls mana yang mengkhususkan diri. Selain itu, ketika saya membuat implan yang salah yang tumpang tindih dengan implan yang sudah ada, sering kali sulit untuk mengetahui di mana kesalahan saya.

Menurut saya ini akan lebih sederhana jika semuanya dapat dikhususkan dan ketika Anda mengkhususkan sesuatu, Anda menyatakan dengan tepat yang berarti Anda mengkhususkan. Mengubah contoh di RFC menjadi apa yang ada dalam pikiran saya:

impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
{
    // no need for default
    fn extend(&mut self, iterable: T) {
        ...
    }
}

// We declare explicitly which impl we are specializing repeating all type bounds etc
specialize impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
    // And then we declare explicitly how we are making this impl narrower with ‘when’.
    // i.e. This impl is like the first except replace all occurances of ‘T’ with ‘&'a [A]’
    when<'a> T = &'a [A]
{
    fn extend(&mut self, iterable: &'a [A]) {
        ...
    }
}

Semua 236 komentar

Beberapa pertanyaan terbuka tambahan:

  • Haruskah kita meninjau kembali aturan yatim piatu dalam terang spesialisasi? Adakah cara untuk membuat segalanya lebih fleksibel sekarang?
  • Haruskah kita memperluas "aturan rantai" di RFC menjadi sesuatu yang lebih ekspresif, seperti yang disebut "aturan kisi"?
  • Terkait dengan kedua hal di atas, bagaimana penalaran negatif cocok dengan cerita? Bisakah kita memulihkan penalaran negatif yang kita butuhkan dengan penggunaan spesialisasi / aturan yatim yang cukup pintar, atau haruskah kita membuatnya lebih kelas satu?

Saya tidak yakin bahwa spesialisasi mengubah aturan yatim piatu:

  • Aturan yatim piatu "penautan" harus tetap sama, karena jika tidak, Anda tidak akan memiliki penautan yang aman.
  • Saya tidak berpikir aturan yatim piatu "kompatibilitas masa depan" harus berubah. Menambahkan impl yang tidak dapat dikhususkan di bawah Anda masih akan menjadi perubahan yang menghancurkan.

Lebih buruk dari itu, aturan yatim piatu "kompatibilitas masa depan" menjaga spesialisasi lintas peti tetap di bawah kendali yang cukup berat. Tanpa mereka, default-impls membiarkan metode mereka terbuka menjadi jauh lebih buruk.

Saya tidak pernah menyukai penalaran negatif yang eksplisit. Saya pikir spesialisasi penalaran negatif total memberikan kompromi yang bagus.

Haruskah impl ini diizinkan dengan spesialisasi seperti yang diterapkan? Atau apakah saya melewatkan sesuatu?
http://is.gd/3Ul0pe

Sama dengan yang ini, akan diharapkan untuk dikompilasi: http://is.gd/RyFIEl

Sepertinya ada beberapa kebiasaan dalam menentukan tumpang tindih saat jenis terkait dilibatkan. Ini mengkompilasi: http://is.gd/JBPzIX , sedangkan kode yang identik secara efektif ini tidak: http://is.gd/0ksLPX

Berikut adalah potongan kode yang ingin saya kompilasi dengan spesialisasi:

http://is.gd/3BNbfK

#![feature(specialization)]

use std::str::FromStr;

struct Error;

trait Simple<'a> {
    fn do_something(s: &'a str) -> Result<Self, Error>;
}

impl<'a> Simple<'a> for &'a str {
     fn do_something(s: &'a str) -> Result<Self, Error> {
        Ok(s)
    }
}

impl<'a, T: FromStr> Simple<'a> for T {
    fn do_something(s: &'a str) -> Result<Self, Error> {
        T::from_str(s).map_err(|_| Error)
    }
}

fn main() {
    // Do nothing. Just type check.
}

Kompilasi gagal dengan kompilator yang mengutip konflik implementasi. Perhatikan bahwa &str tidak mengimplementasikan FromStr , jadi seharusnya tidak ada konflik.

@ syrif

Saya punya waktu untuk melihat dua contoh pertama. Ini catatan saya.

Contoh 1

Kasus pertama, Anda memiliki:

  • FromSqlRow<ST, DB> for T where T: FromSql<ST, DB>
  • FromSqlRow<(ST, SU), DB> for (T, U) where T: FromSqlRow<ST, DB>, U: FromSqlRow<SU, DB>,

Masalahnya adalah impls ini tumpang tindih tetapi tidak ada yang lebih spesifik dari yang lain:

  • Anda berpotensi memiliki T: FromSql<ST, DB> mana T bukan pasangan (jadi cocok dengan impl pertama tapi bukan yang kedua).
  • Anda berpotensi memiliki (T, U) mana:

    • T: FromSqlRow<ST, DB> ,

    • U: FromSqlRow<SU, DB> , tapi _not_

    • (T, U): FromSql<(ST, SU), DB>

    • (jadi impl kedua cocok, tapi bukan yang pertama)

  • Keduanya menunjukkan tumpang tindih karena Anda dapat memiliki (T, U) sedemikian rupa sehingga:

    • T: FromSqlRow<ST, DB>

    • U: FromSqlRow<SU, DB>

    • (T, U): FromSql<(ST, SU), DB>

Ini adalah jenis situasi yang memungkinkan lattice - Anda harus menulis impl ketiga untuk kasus yang tumpang tindih, dan mengatakan apa yang harus dilakukan. Atau, sifat negatif menyiratkan mungkin memberi Anda cara untuk menyingkirkan tumpang tindih atau mengubah kecocokan mana yang mungkin.

Contoh 2

Kamu punya:

  • Queryable<ST, DB> for T where T: FromSqlRow<ST, DB>
  • Queryable<Nullable<ST>, DB> for Option<T> where T: Queryable<ST, DB>

Ini tumpang tindih karena Anda dapat memiliki Option<T> mana:

  • T: Queryable<ST, DB>
  • Option<T>: FromSqlRow<Nullable<ST>, DB>

Tetapi tidak ada impl yang lebih spesifik:

  • Anda dapat memiliki T sedemikian rupa sehingga T: FromSqlRow<ST, DB> tetapi T bukan Option<U> (cocok dengan impl pertama tetapi tidak yang kedua)
  • Anda dapat memiliki Option<T> sehingga T: Queryable<ST, DB> tetapi tidak Option<T>: FromSqlRow<Nullable<ST>, DB>

@SergioGaul

Kompilasi gagal dengan kompilator yang mengutip konflik implementasi. Perhatikan bahwa &str tidak menerapkan FromStr , jadi seharusnya tidak ada konflik.

Masalahnya adalah kompilator secara konservatif mengasumsikan bahwa &str mungkin akan mengimplementasikan FromStr di masa mendatang. Itu mungkin tampak konyol untuk contoh ini, tetapi secara umum, kami menambahkan impls baru setiap saat, dan kami ingin melindungi kode downstream agar tidak rusak saat kami menambahkan impls tersebut.

Ini adalah pilihan konservatif, dan merupakan sesuatu yang mungkin ingin kita rileks seiring waktu. Anda bisa mendapatkan latar belakangnya di sini:

Terima kasih telah mengklarifikasi kedua kasus tersebut. Sangat masuk akal sekarang

Pada hari Selasa, 22 Mar 2016, 18:34 Aaron Turon [email protected] menulis:

@SergioBenitez https://gub.com/SergioBenitez

Kompilasi gagal dengan kompilator yang mengutip konflik implementasi. Catatan
itu & str tidak mengimplementasikan FromStr, jadi seharusnya tidak ada konflik.

Masalahnya adalah kompilator secara konservatif mengasumsikan bahwa & str
mungkin akan menerapkan FromStr di masa mendatang. Itu mungkin tampak konyol
contoh ini, tetapi secara umum, kami menambahkan impls baru setiap saat, dan kami ingin
melindungi kode downstream dari kerusakan saat kita menambahkan impls tersebut.

Ini adalah pilihan konservatif, dan merupakan sesuatu yang mungkin ingin kita rileks
lembur. Anda bisa mendapatkan latar belakangnya di sini:

-
http://smallcultfollowing.com/babysteps/blog/2015/01/14/little-orphan-impls/

-
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung atau lihat di GitHub
https://github.com/rust-lang/rust/issues/31844#issuecomment -200093757

@aturon

Masalahnya adalah kompilator secara konservatif mengasumsikan bahwa & str mungkin akan mengimplementasikan FromStr di masa mendatang. Itu mungkin tampak konyol untuk contoh ini, tetapi secara umum, kami menambahkan impls baru setiap saat, dan kami ingin melindungi kode downstream agar tidak rusak saat kami menambahkan impls tersebut.

Bukankah ini spesialisasi yang coba diatasi? Dengan spesialisasi, saya berharap bahwa meskipun implementasi FromStr untuk &str ditambahkan di masa mendatang, implementasi langsung dari sifat Simple untuk &str akan diutamakan.

@SergioBenitez Anda harus meletakkan default fn di impl yang lebih umum. Anda
contoh tidak dapat dikhususkan.

Pada Sel, 22 Mar 2016, 18.54 Sergio Benitez [email protected]
menulis:

@aturon https://gub.com/aturon

Masalahnya adalah kompilator secara konservatif mengasumsikan bahwa & str
mungkin akan menerapkan FromStr di masa mendatang. Itu mungkin tampak konyol untuk ini
Misalnya, tetapi secara umum, kami menambahkan impls baru setiap saat, dan kami ingin
melindungi kode downstream dari kerusakan saat kita menambahkan impls tersebut.

Bukankah ini spesialisasi yang coba diatasi? Dengan
spesialisasi, saya berharap bahwa bahkan jika implementasi FromStr
for & str ditambahkan di masa mendatang, implementasi langsung untuk
sifat untuk & str akan diutamakan.

-
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung atau lihat di GitHub
https://github.com/rust-lang/rust/issues/31844#issuecomment -200097995

Saya pikir item ciri "default" secara otomatis dianggap default terdengar membingungkan. Anda mungkin menginginkan kedua parametrik untuk sifat seperti di Haskell, dll. Bersama dengan pelonggaran impl s. Anda juga tidak dapat dengan mudah grep untuk mereka seperti yang Anda bisa untuk default . Tidaklah sulit untuk mengetik kata kunci default dan memberikan implementasi default, tetapi keduanya tidak dapat dipisahkan sebagaimana adanya. Juga, jika seseorang ingin memperjelas bahasanya, maka butir-butir ciri "default" ini dapat diganti namanya menjadi butir "ciri yang diusulkan" dalam dokumentasi.

Catatan dari # 32999 (komentar) : jika kita mengikuti aturan kisi (atau mengizinkan batasan negatif), trik "gunakan sifat perantara" untuk mencegah spesialisasi lebih lanjut dari sesuatu tidak akan berfungsi lagi.

@Tokopedia

Mengapa tidak berhasil? Triknya membatasi spesialisasi pada sifat pribadi. Anda tidak dapat mengkhususkan sifat pribadi jika tidak dapat mengaksesnya.

@ arielb Ah. Poin yang bagus. Dalam kasus saya, sifat tersebut tidak bersifat pribadi.

Saya tidak berpikir alasan "eksternal tidak dapat mengkhususkan diri karena kompatibilitas ke depan + aturan koherensi yatim piatu" sangat menarik atau berguna. Terutama ketika kita tidak berkomitmen pada aturan koherensi khusus kita.

Adakah cara untuk mengakses default impl diganti? Jika demikian, ini bisa membantu dalam membangun tes. Lihat Desain Berdasarkan Kontrak dan libhoare .

Mengizinkan proyeksi tipe terkait default selama pemeriksaan tipe akan memungkinkan penerapan ketimpangan tipe pada waktu kompilasi: https://gist.github.com/7c081574958d22f89d434a97b626b1e4

#![feature(specialization)]

pub trait NotSame {}

pub struct True;
pub struct False;

pub trait Sameness {
    type Same;
}

mod internal {
    pub trait PrivSameness {
        type Same;
    }
}

use internal::PrivSameness;

impl<A, B> Sameness for (A, B) {
    type Same = <Self as PrivSameness>::Same;
}

impl<A, B> PrivSameness for (A, B) {
    default type Same = False;
}
impl<A> PrivSameness for (A, A) {
    type Same = True;
}

impl<A, B> NotSame for (A, B) where (A, B): Sameness<Same=False> {}

fn not_same<A, B>() where (A, B): NotSame {}

fn main() {
    // would compile
    not_same::<i32, f32>();

    // would not compile
    // not_same::<i32, i32>();
}

diedit sesuai komentar @burdges

Hanya fyi @rphmeier yang mungkin harus dihindari is.gd karena tidak menyelesaikan untuk pengguna Tor karena menggunakan CloudFlare. GitHub berfungsi dengan baik dengan URL lengkap. Dan play.rust-lang.org bekerja dengan baik di Tor.

@burdges FWIW play.rust-lang.org sendiri menggunakan is.gd untuk tombol "Shorten" nya.

Ini mungkin dapat diubah, meskipun: https://github.com/rust-lang/rust-playpen/blob/9777ef59b/static/web.js#L333

gunakan seperti ini (https://is.gd/Ux6FNs):

#![feature(specialization)]
pub trait Foo {}
pub trait Bar: Foo {}
pub trait Baz: Foo {}

pub trait Trait {
    type Item;
}

struct Staff<T> { }

impl<T: Foo> Trait for Staff<T> {
    default type Item = i32;
}

impl<T: Foo + Bar> Trait for Staff<T> {
    type Item = i64;
}

impl<T: Foo + Baz> Trait for Staff<T> {
    type Item = f64;
}

fn main() {
    let _ = Staff { };
}

Kesalahan:

error: conflicting implementations of trait `Trait` for type `Staff<_>`: [--explain E0119]
  --> <anon>:20:1
20 |> impl<T: Foo + Baz> Trait for Staff<T> {
   |> ^
note: conflicting implementation is here:
  --> <anon>:16:1
16 |> impl<T: Foo + Bar> Trait for Staff<T> {
   |> ^

error: aborting due to previous error

Apakah feture specialization mendukung ini, dan apakah saat ini ada penerapan lain?

@en

Implikasi ini tidak diperbolehkan oleh desain spesialisasi saat ini, karena baik T: Foo + Bar maupun T: Foo + Baz tidak lebih terspesialisasi daripada yang lain. Artinya, jika Anda memiliki T: Foo + Bar + Baz , tidak jelas implan mana yang harus "menang".

Kami memiliki beberapa pemikiran tentang sistem yang lebih ekspresif yang akan memungkinkan Anda untuk _also_ memberikan impl untuk T: Foo + Bar + Baz dan dengan demikian tidak ambigu, tetapi itu belum sepenuhnya diusulkan.

Jika sifat negatif membatasi trait Baz: !Bar tanah, itu juga dapat digunakan dengan spesialisasi untuk membuktikan bahwa kumpulan jenis yang menerapkan Bar dan yang menerapkan Baz berbeda dan dapat dikhususkan secara individual.

Sepertinya balasan @rphmeier persis seperti yang saya inginkan, artinya T: Foo + Bar + Baz juga akan membantu.

Abaikan saja ini, saya masih ada hubungannya dengan kasus saya, dan selalu menarik untuk specialization dan pendaratan fitur lainnya.

Terima kasih @aturon @rphmeier .

Saya telah bermain-main dengan spesialisasi belakangan ini, dan saya menemukan kasus aneh ini:

#![feature(specialization)]

trait Marker {
    type Mark;
}

trait Foo { fn foo(&self); }

struct Fizz;

impl Marker for Fizz {
    type Mark = ();
}

impl Foo for Fizz {
    fn foo(&self) { println!("Fizz!"); }
}

impl<T> Foo for T
    where T: Marker, T::Mark: Foo
{
    default fn foo(&self) { println!("Has Foo marker!"); }
}

struct Buzz;

impl Marker for Buzz {
    type Mark = Fizz;
}

fn main() {
    Fizz.foo();
    Buzz.foo();
}

Keluaran kompiler:

error: conflicting implementations of trait `Foo` for type `Fizz`: [--explain E0119]
  --> <anon>:19:1
19 |> impl<T> Foo for T
   |> ^
note: conflicting implementation is here:
  --> <anon>:15:1
15 |> impl Foo for Fizz {
   |> ^

boks

Saya percaya bahwa kompilasi _should_ di atas, dan ada dua variasi menarik yang benar-benar berfungsi sebagaimana mestinya:

1) Menghapus where T::Mark: Fizz terikat:

impl<T> Foo for T
    where T: Marker //, T::Mark: Fizz
{
    // ...
}

boks

2) Menambahkan "alias terikat sifat":

trait FooMarker { }
impl<T> FooMarker for T where T: Marker, T::Mark: Foo { }

impl<T> Foo for T where T: FooMarker {
    // ...
}

boks

(Yang _tidak_ berfungsi jika Marker didefinisikan dalam peti terpisah (!), Lihat contoh repo ini )

Saya juga yakin bahwa masalah ini mungkin terkait dengan # 20400

EDIT : Saya telah membuka masalah tentang ini: # 36587

Saya mengalami masalah dengan spesialisasi. Tidak yakin apakah ini masalah implementasi atau masalah dalam cara spesialisasi ditentukan.

use std::vec::IntoIter as VecIntoIter;

pub trait ClonableIterator: Iterator {
    type ClonableIter;

    fn clonable(self) -> Self::ClonableIter;
}

impl<T> ClonableIterator for T where T: Iterator {
    default type ClonableIter = VecIntoIter<T::Item>;

    default fn clonable(self) -> VecIntoIter<T::Item> {
        self.collect::<Vec<_>>().into_iter()
    }
}

impl<T> ClonableIterator for T where T: Iterator + Clone {
    type ClonableIter = T;

    #[inline]
    fn clonable(self) -> T {
        self
    }
}

( boks )
(ngomong-ngomong, alangkah baiknya jika kode ini akhirnya mendarat di stdlib suatu hari nanti)

Kode ini gagal dengan:

error: method `clonable` has an incompatible type for trait:
 expected associated type,
    found struct `std::vec::IntoIter` [--explain E0053]
  --> <anon>:14:5
   |>
14 |>     default fn clonable(self) -> VecIntoIter<T::Item> {
   |>     ^

Mengubah nilai kembali menjadi Self::ClonableIter memberikan kesalahan berikut:

error: mismatched types [--explain E0308]
  --> <anon>:15:9
   |>
15 |>         self.collect::<Vec<_>>().into_iter()
   |>         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found struct `std::vec::IntoIter`
note: expected type `<T as ClonableIterator>::ClonableIter`
note:    found type `std::vec::IntoIter<<T as std::iter::Iterator>::Item>`

Rupanya Anda tidak dapat mengacu pada jenis beton dari jenis terkait default, yang menurut saya cukup membatasi.

@tomaka seharusnya berfungsi, teks RFC memiliki ini:

impl<T> Example for T {
    default type Output = Box<T>;
    default fn generate(self) -> Box<T> { Box::new(self) }
}

impl Example for bool {
    type Output = bool;
    fn generate(self) -> bool { self }
}

(https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md#the-default-keyword)

Yang tampaknya cukup mirip dengan kasus Anda untuk menjadi relevan.

@aatch contoh itu tampaknya tidak dikompilasi dengan definisi intuitif untuk sifat contoh: https://play.rust-lang.org/?gist=97ff3c2f7f3e50bd3aef000dbfa2ca4e&version=nightly&backtrace=0

kode spesialisasi secara eksplisit melarang hal ini - lihat # 33481, yang awalnya saya anggap sebagai kesalahan tetapi ternyata merupakan masalah diagnostik. PR saya untuk meningkatkan diagnostik di sini tidak diketahui, dan saya belum mempertahankannya ke master terbaru untuk beberapa waktu.

@rphmeier teks RFC menyarankan bahwa itu harus diizinkan, contoh itu disalin darinya.

Saya pernah bermain dengan beberapa kode yang bisa mendapatkan keuntungan dari spesialisasi. Saya sangat berpikir kita harus menggunakan aturan kisi daripada berantai - rasanya alami dan satu-satunya cara untuk mendapatkan fleksibilitas yang saya butuhkan (afaict).

Jika kita memilih default pada impl serta item individual, dapatkah kita memaksakan bahwa jika ada item yang diganti maka semuanya harus diganti? Itu akan memungkinkan kita untuk bernalar berdasarkan tipe yang tepat dari tipe assoc default (misalnya) di item lain, yang tampaknya seperti dorongan ekspresif yang berguna.

Haruskah hal-hal berikut ini diizinkan? Saya ingin mengkhususkan tipe sehingga ArrayVec adalah Copy ketika tipe elemennya adalah Salin, dan sebaliknya memiliki destruktor. Saya mencoba menyelesaikannya dengan menggunakan bidang internal yang diganti dengan spesialisasi.

Saya berharap ini akan dikompilasi, yaitu ia menyimpulkan kemampuan copyability dari bidang ArrayVec<A> dari jenis bidang yang dipilih oleh A: Copy + Array terikat (cuplikan kompilasi di taman bermain) .

impl<A: Copy + Array> Copy for ArrayVec<A>
    //where <A as Repr>::Data: Copy
{ }

Klausa yang diberi komentar tidak diinginkan karena memperlihatkan tipe pribadi Repr di antarmuka publik. (Ini juga ICE).

Sunting: Saya lupa saya telah melaporkan masalah # 33162 tentang ini, maaf.

Tindak lanjuti komentar saya, kasus penggunaan saya yang sebenarnya:

// Ideal version

trait Scannable {}

impl<T: FromStr> Scannable for T {}
impl<T: FromStr> Scannable for Result<T, ()> {}

// But this doesn't follow from the specialisation rules because Result: !FromStr
// Lattice rule would allow filling in that gap or negative reasoning would allow specifying it.

// Second attempt

trait FromResult {
    type Ok;
    fn from(r: Result<Self::Ok, ()>) -> Self;
}

impl<T> Scannable for T {
    default type Ok = T;
    default fn from(r: Result<T, ()>) -> Self {...} // error can't assume Ok == T, could do this if we had `default impl`
}

impl<T> Scannable for Result<T, ()> {
    type Ok = T;
    default fn from(r: Result<T, ()>) -> Self { r }
}

fn scan_from_str<T: FromResult>(x: &str) -> T
    where <T as FromResult>::Ok: FromStr  // Doesn't hold for T: FromStr because of the default on T::Ok
{ ... }

// Can also add the FromStr bound to FromResult::Ok, but doesn't help

// Third attempt
trait FromResult<Ok> {
    fn from(r: Result<Ok, ()>) -> Self;
}

impl<T> FromResult<T> for T {
    default fn from(r: Result<Self, ()>) -> Self { ... }
}

impl<T> FromResult<T> for Result<T, ()> {
    fn from(r: Result<T, ())>) -> Self { r }
}


fn scan_from_str<U: FromStr, T: FromResult<U>>(x: &str) -> T { ... }

// Error because we can't infer that U == String
let mut x: Result<String, ()> = scan_from_str("dsfsf");

@tomaka @Aatch

Masalahnya adalah Anda tidak diizinkan untuk mengandalkan nilai item default lainnya. Jadi ketika Anda memiliki impl ini:

impl<T> ClonableIterator for T where T: Iterator {
    default type ClonableIter = VecIntoIter<T::Item>;

    default fn clonable(self) -> VecIntoIter<T::Item> {
    //                           ^^^^^^^^^^^^^^^^^^^^
        self.collect::<Vec<_>>().into_iter()
    }
}

Di tempat saya menyorot, clonable bergantung pada Self::ClonableIter , tetapi karena CloneableIter dideklarasikan sebagai default, Anda tidak dapat melakukan itu. Perhatiannya adalah bahwa seseorang mungkin mengkhususkan dan mengganti CloneableIter tetapi _not_ clonable .

Kami telah membicarakan beberapa kemungkinan jawaban di sini. Salah satunya adalah membiarkan Anda menggunakan default untuk mengelompokkan item di mana, jika Anda menimpanya, Anda harus mengganti semua:

impl<T> ClonableIterator for T where T: Iterator {
    default {
        type ClonableIter = VecIntoIter<T::Item>;
        fn clonable(self) -> VecIntoIter<T::Item> { ... }
    }
}

Ini tidak apa-apa, tapi sedikit "mengarah ke kanan". default juga terlihat seperti ruang lingkup penamaan, padahal sebenarnya bukan. Mungkin ada beberapa varian yang lebih sederhana yang memungkinkan Anda beralih antara "override-any" (seperti hari ini) vs "override-all" (yang Anda butuhkan).

Kami juga berharap dapat bertahan dengan memanfaatkan impl Trait . Idenya adalah bahwa ini paling sering muncul, seperti yang terjadi di sini, ketika Anda ingin menyesuaikan metode tipe pengembalian. Jadi mungkin jika Anda bisa menulis ulang sifat tersebut untuk menggunakan impl Trait :

pub trait ClonableIterator: Iterator {
    fn clonable(self) -> impl Iterator;
}

Ini secara efektif akan menjadi semacam singkatan ketika diimplementasikan untuk grup default yang berisi type dan fn. (Saya tidak yakin apakah akan ada cara untuk melakukan itu murni secara implisit.)

PS, maaf atas keterlambatan lama dalam menjawab pesan Anda, yang saya lihat tanggal dari _July_.

Meskipun impl Trait membantu, tidak ada RFC yang telah diterima atau diimplementasikan yang memungkinkannya digunakan dengan badan sifat dalam bentuk apa pun, jadi mencari RFC ini terasa agak aneh.

Saya tertarik untuk menerapkan fitur default impl (di mana semua item adalah default ).
Apakah Anda akan menerima kontribusi untuk itu?

@giannicic Pasti! Saya akan dengan senang hati membantu membimbing pekerjaan ini juga.

Apakah saat ini ada kesimpulan tentang apakah jenis terkait harus dapat dikhususkan?

Berikut ini adalah penyederhanaan use-case saya, yang mendemonstrasikan kebutuhan akan tipe terkait yang dapat dikhususkan.
Saya memiliki struktur data umum, katakanlah Foo , yang mengkoordinasikan kumpulan objek sifat kontainer ( &trait::Property ). Sifat trait::Property diimplementasikan oleh Property<T> (didukung oleh Vec<T> ) dan PropertyBits (didukung oleh BitVec , vektor bit).
Dalam metode umum pada Foo , saya ingin dapat menentukan struktur data dasar yang tepat untuk T melalui tipe yang terkait, tetapi ini memerlukan spesialisasi untuk memiliki implikasi selimut untuk kasus non-khusus seperti mengikuti.

trait ContainerFor {
    type P: trait::Property;
}

impl<T> ContainerFor for T {
    default type P = Property<T>; // default to the `Vec`-based version
}

impl ContainerFor for bool {
    type P = PropertyBits; // specialize to optimize for space
}

impl Foo {
    fn add<T>(&mut self, name: &str) {
        self.add_trait_obj(name, Box::new(<T as ContainerFor>::P::new())));
    }
    fn get<T>(&mut self, name: &str) -> Option<&<T as ContainerFor>::P> {
        self.get_trait_obj(name).and_then(|prop| prop.downcast::<_>());
    }
}

Terima kasih @aturon !
Pada dasarnya saya melakukan pekerjaan dengan menambahkan atribut "defaultness" baru ke ast::ItemKind::Impl struct (dan kemudian menggunakan atribut baru bersama dengan atribut "defaultness" item impl) tetapi ada juga yang cepat dan mudah
kemungkinan yang terdiri dari pengaturan default ke semua item impl dari default impl selama parsing.
Bagi saya ini bukan solusi "lengkap" karena kami kehilangan informasi bahwa "default" terkait dengan impl dan bukan dengan setiap item impl,
Selain itu jika ada rencana untuk memperkenalkan partial impl solusi pertama sudah menyediakan atribut yang dapat digunakan untuk menyimpan default dan partial . Tetapi hanya untuk memastikan dan
tidak membuang-buang waktu, apa yang kamu pikirkan?

@giannicic @aturon bolehkah saya mengusulkan agar kita membuat terbitan khusus untuk membahas default impl ?

Apakah aturan kisi mengizinkan saya, mengingat:

trait Foo {}

trait A {}
trait B {}
trait C {}
// ...

tambahkan implementasi Foo untuk subset tipe yang menerapkan beberapa kombinasi dari A , B , C , ...:

impl Foo for T where T: A { ... }
impl Foo for T where T: B { ... }
impl Foo for T where T: A + B { ... }
impl Foo for T where T: B + C { ... }
// ...

dan izinkan saya untuk "melarang" beberapa kombinasi, misalnya, A + C tidak boleh terjadi:

impl Foo for T where T: A + C = delete;

?

Konteks: Saya menginginkan ini ketika menerapkan sifat ApproxEqual(Shape, Shape) untuk berbagai jenis bentuk (titik, kubus, poligon, ...) di mana ini semua adalah sifat. Saya harus mengatasinya dengan memfaktorkan ulang ini menjadi sifat yang berbeda, misalnya, ApproxEqualPoint(Point, Point) , untuk menghindari implementasi yang bertentangan.

@bayu_joo

dan izinkan saya untuk "melarang" beberapa kombinasi, misalnya, A + C tidak boleh terjadi:

Tidak, ini bukan sesuatu yang diizinkan oleh aturan kisi. Itu lebih merupakan domain dari "penalaran negatif" dalam beberapa bentuk atau jenis.

Konteks: Saya menginginkan ini ketika menerapkan sifat ApproxEqual (Bentuk, Bentuk) untuk berbagai jenis bentuk (titik, kubus, poligon, ...) di mana ini semua adalah sifat. Saya harus mengatasinya dengan melakukan refactoring ini menjadi sifat yang berbeda, misalnya, ApproxEqualPoint (Point, Point), untuk menghindari implementasi yang bertentangan.

Jadi @withoutboats telah mempromosikan gagasan "grup pengecualian", di mana Anda dapat menyatakan bahwa sekumpulan sifat tertentu saling eksklusif (yaitu, Anda dapat menerapkan paling banyak salah satunya). Saya membayangkan ini sebagai semacam enum (yaitu, sifat-sifat semuanya dideklarasikan bersama). Saya menyukai gagasan ini, terutama karena (menurut saya!) Ini membantu menghindari beberapa aspek yang lebih merusak dari penalaran negatif. Tetapi saya merasa perlu lebih banyak pemikiran di bagian depan ini - dan juga artikel bagus yang mencoba merangkum semua "data" yang beredar tentang cara berpikir tentang penalaran negatif. Mungkin sekarang setelah saya (kebanyakan) menyelesaikan HKT dan seri spesialisasi saya, saya dapat memikirkannya ...

@nikomatakis :

Jadi @withoutboats telah mempromosikan gagasan "grup pengecualian", di mana Anda dapat menyatakan bahwa sekumpulan sifat tertentu saling eksklusif (yaitu, Anda dapat menerapkan paling banyak salah satunya). Saya membayangkan ini sebagai semacam enum (yaitu, sifat-sifat semuanya dideklarasikan bersama). Saya menyukai gagasan ini, terutama karena (menurut saya!) Ini membantu menghindari beberapa aspek yang lebih merusak dari penalaran negatif. Tetapi saya merasa perlu lebih banyak pemikiran di bagian depan ini - dan juga artikel bagus yang mencoba merangkum semua "data" yang beredar tentang cara berpikir tentang penalaran negatif. Mungkin sekarang setelah saya (kebanyakan) menyelesaikan HKT dan seri spesialisasi saya, saya dapat memikirkannya ...

Saya memikirkan tentang grup pengecualian saat menulis ini (Anda menyebutkannya di forum beberapa hari yang lalu), tetapi menurut saya mereka tidak dapat berfungsi karena dalam contoh khusus ini tidak semua penerapan sifat bersifat eksklusif. Contoh paling sepele adalah Point dan Float sifat: Float _can_ menjadi 1D point, jadi ApproxEqualPoint(Point, Point) dan ApproxEqualFloat(Float, Float) tidak bisa eksklusif. Ada contoh lain seperti Square dan Polygon , atau Box | Cube dan AABB (kotak pembatas selaras sumbu) di mana "hierarki sifat" sebenarnya membutuhkan pembatas yang lebih kompleks.

Tidak, ini bukan sesuatu yang diizinkan oleh aturan kisi. Itu lebih merupakan domain dari "penalaran negatif" dalam beberapa bentuk atau jenis.

Saya setidaknya bisa menerapkan kasus tertentu dan memasukkan unimplemented!() di dalamnya. Itu sudah cukup, tapi jelas saya lebih suka jika kompiler akan secara statis menangkap kasus-kasus di mana saya memanggil fungsi dengan unimplemented!() di dalamnya (dan pada titik ini, kita lagi-lagi berada di tanah penalaran negatif) .

Spesialisasi kisi menangis :

Gagasan tentang "kelompok pengecualian" sebenarnya hanyalah batas supertrait negatif. Satu hal yang belum kita telaah terlalu menyeluruh adalah gagasan spesialisasi polaritas terbalik - memungkinkan Anda menulis impl khusus yang polaritas terbalik ke implnya yang kurang terspesialisasi. Misalnya, dalam hal ini Anda hanya akan menulis:

impl<T> !Foo for T where T: A + C { }

Saya tidak sepenuhnya yakin apa implikasi dari mengizinkan itu. Saya pikir ini terkait dengan masalah yang sudah disorot Niko tentang bagaimana spesialisasi adalah semacam kode yang digabungkan kembali dengan polimorfisme sekarang.

Dengan semua diskusi tentang penalaran negatif dan implikasi negatif ini, saya merasa terdorong untuk mengemukakan kembali gagasan lama Haskell tentang "rantai contoh" ( kertas , kertas , pelacak masalah GHC , Rust pre-RFC ), sebagai sumber inspirasi potensial jika tidak ada lain.

Pada dasarnya idenya adalah bahwa di mana pun Anda dapat menulis trait impl, Anda juga dapat menulis sejumlah "else if clauses" yang menetapkan impl yang berbeda yang harus diterapkan jika yang sebelumnya (s) tidak, dengan "klausa lain" opsional akhir yang menetapkan impl negatif (yaitu, jika tidak ada klausa untuk Trait berlaku, maka !Trait berlaku).

@tokopedia

Gagasan tentang "kelompok pengecualian" sebenarnya hanyalah batas supertrait negatif.

Saya pikir itu akan cukup untuk kasus penggunaan saya.

Saya pikir ini terkait dengan masalah yang sudah disorot Niko tentang bagaimana spesialisasi adalah semacam kode yang digabungkan kembali dengan polimorfisme sekarang.

Saya tidak tahu apakah ini bisa diurai. Saya ingin memiliki:

  • polimorfisme: ciri tunggal yang mengabstraksi implementasi berbeda dari suatu operasi untuk banyak jenis berbeda,
  • penggunaan kembali kode: alih-alih menerapkan operasi untuk setiap jenis, saya ingin menerapkannya untuk kelompok jenis yang menerapkan beberapa sifat,
  • kinerja: dapat mengganti implementasi yang sudah ada untuk tipe tertentu atau subset tipe yang memiliki sekumpulan batasan yang lebih spesifik daripada implementasi yang sudah ada,
  • produktivitas: dapat menulis dan menguji program saya secara bertahap, daripada harus menambahkan banyak impl s untuk dikompilasi.

Mencakup semua kasus itu sulit, tetapi jika kompiler memaksa saya untuk mencakup semua kasus:

trait Foo {}
trait A {}
trait B {}

impl<T> Foo for T where T: A { ... }
impl<T> Foo for T where T: B { ... }
// impl<T> Foo for T where T: A + B { ... }  //< compiler: need to add this impl!

dan juga memberi saya implikasi negatif:

impl<T> !Foo for T where T: A + B { }
impl<T> !Foo for T where T: _ { } // _ => all cases not explicitly covered yet

Saya akan dapat menambahkan impls secara bertahap saat saya membutuhkannya dan juga mendapatkan kesalahan kompiler yang bagus ketika saya mencoba menggunakan sifat dengan tipe yang tidak memiliki impl.

Saya tidak sepenuhnya yakin apa implikasi dari mengizinkan itu.

Niko menyebutkan bahwa ada masalah dengan penalaran negatif. FWIW satu-satunya alasan negatif yang digunakan dalam contoh di atas adalah untuk menyatakan bahwa pengguna mengetahui bahwa implan untuk kasus tertentu diperlukan, tetapi secara eksplisit memutuskan untuk tidak menyediakan implementasi untuk itu.

Saya baru saja menekan # 33017 dan belum melihatnya ditautkan di sini. Itu ditandai sebagai lubang yang sehat jadi akan bagus untuk melacak di sini.

Untuk https://github.com/dtolnay/quote/issues/7 saya memerlukan sesuatu yang mirip dengan contoh ini dari RFC yang belum berfungsi. cc @tomaka @Aatch @rphmeier yang berkomentar tentang ini sebelumnya.

trait Example {
    type Output;
    fn generate(self) -> Self::Output;
}

impl<T> Example for T {
    default type Output = Box<T>;
    default fn generate(self) -> Box<T> { Box::new(self) }
}

impl Example for bool {
    type Output = bool;
    fn generate(self) -> bool { self }
}

Saya menemukan solusi berikut yang memberikan cara untuk mengungkapkan hal yang sama.

#![feature(specialization)]

use std::fmt::{self, Debug};

///////////////////////////////////////////////////////////////////////////////

trait Example: Output {
    fn generate(self) -> Self::Output;
}

/// In its own trait for reasons, presumably.
trait Output {
    type Output: Debug + Valid<Self>;
}

fn main() {
    // true
    println!("{:?}", Example::generate(true));

    // box("s")
    println!("{:?}", Example::generate("s"));
}

///////////////////////////////////////////////////////////////////////////////

/// Instead of `Box<T>` just so the "{:?}" in main() clearly shows the type.
struct MyBox<T: ?Sized>(Box<T>);

impl<T: ?Sized> Debug for MyBox<T>
    where T: Debug
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "box({:?})", self.0)
    }
}

///////////////////////////////////////////////////////////////////////////////

/// Return type of the impl containing `default fn`.
type DefaultOutput<T> = MyBox<T>;

impl Output for bool {
    type Output = bool;
}

impl<T> Example for T where T: Pass {
    default fn generate(self) -> Self::Output {
        T::pass({
            // This is the impl you wish you could write
            MyBox(Box::new(self))
        })
    }
}

impl Example for bool {
    fn generate(self) -> Self::Output {
        self
    }
}

///////////////////////////////////////////////////////////////////////////////
// Magic? Soundness exploit? Who knows?

impl<T: ?Sized> Output for T where T: Debug {
    default type Output = DefaultOutput<T>;
}

trait Valid<T: ?Sized> {
    fn valid(DefaultOutput<T>) -> Self;
}

impl<T: ?Sized> Valid<T> for DefaultOutput<T> {
    fn valid(ret: DefaultOutput<T>) -> Self {
        ret
    }
}

impl<T> Valid<T> for T {
    fn valid(_: DefaultOutput<T>) -> Self {
        unreachable!()
    }
}

trait Pass: Debug {
    fn pass(DefaultOutput<Self>) -> <Self as Output>::Output;
}

impl<T: ?Sized> Pass for T where T: Debug, <T as Output>::Output: Valid<T> {
    fn pass(ret: DefaultOutput<T>) -> <T as Output>::Output {
        <T as Output>::Output::valid(ret)
    }
}

Saya masih mengerjakan https://github.com/dtolnay/quote/issues/7 dan membutuhkan pola berlian. Inilah solusi saya. cc @zitsen yang menanyakan hal ini sebelumnya dan @aturon dan @rphmeier yang menanggapi.

#![feature(specialization)]

/// Can't have these impls directly:
///
///  - impl<T> Trait for T
///  - impl<T> Trait for T where T: Clone
///  - impl<T> Trait for T where T: Default
///  - impl<T> Trait for T where T: Clone + Default
trait Trait {
    fn print(&self);
}

fn main() {
    struct A;
    A.print(); // "neither"

    #[derive(Clone)]
    struct B;
    B.print(); // "clone"

    #[derive(Default)]
    struct C;
    C.print(); // "default"

    #[derive(Clone, Default)]
    struct D;
    D.print(); // "clone + default"
}

trait IfClone: Clone { fn if_clone(&self); }
trait IfNotClone { fn if_not_clone(&self); }

impl<T> Trait for T {
    default fn print(&self) {
        self.if_not_clone();
    }
}

impl<T> Trait for T where T: Clone {
    fn print(&self) {
        self.if_clone();
    }
}

impl<T> IfClone for T where T: Clone {
    default fn if_clone(&self) {
        self.clone();
        println!("clone");
    }
}

impl<T> IfClone for T where T: Clone + Default {
    fn if_clone(&self) {
        self.clone();
        Self::default();
        println!("clone + default");
    }
}

impl<T> IfNotClone for T {
    default fn if_not_clone(&self) {
        println!("neither");
    }
}

impl<T> IfNotClone for T where T: Default {
    fn if_not_clone(&self) {
        Self::default();
        println!("default");
    }
}

Serang bug (atau setidaknya perilaku tak terduga dari sudut pandang saya) dengan spesialisasi dan jenis inferensi: # 38167

Kedua implik ini diharapkan valid dengan spesialisasi, bukan? Sepertinya tidak berhasil mengambilnya.

impl<T, ST, DB> ToSql<Nullable<ST>, DB> for T where
    T: ToSql<ST, DB>,
    DB: Backend + HasSqlType<ST>,
    ST: NotNull,
{
    ...
}

impl<T, ST, DB> ToSql<Nullable<ST>, DB> for Option<T> where
    T: ToSql<ST, DB>,
    DB: Backend + HasSqlType<ST>,
    ST: NotNull,
{
    ...
}

Saya mengajukan https://github.com/rust-lang/rust/issues/38516 untuk beberapa perilaku tidak terduga yang saya temui saat bekerja membangun spesialisasi ke Serde. Mirip dengan https://github.com/rust-lang/rust/issues/38167 , ini adalah kasus di mana program dikompilasi tanpa impl khusus dan ketika ditambahkan ada kesalahan tipe. cc @bluss yang prihatin tentang situasi ini sebelumnya.

Bagaimana jika kita mengizinkan spesialisasi tanpa kata kunci default dalam satu peti, serupa dengan cara kita mengizinkan penalaran negatif dalam satu peti?

Pembenaran utama saya adalah ini: "pola iterator dan vektor." Terkadang, pengguna ingin mengimplementasikan sesuatu untuk semua iterator dan untuk vektor:

impl<I> Foo for I where I: Iterator<Item = u32> { ... }
impl Foo for Vec<u32> { ... }

(Ini relevan dengan situasi lain selain iterator dan vektor, tentu saja, ini hanya satu contoh.)

Hari ini ini tidak terkompilasi, dan ada tsuris dan kertakan gigi. Spesialisasi memecahkan masalah ini:

default impl<I> Foo for I where I: Iterator<Item = u32> { ... }
impl Foo for Vec<u32> { ... }

Tetapi dalam memecahkan masalah ini, Anda telah menambahkan kontrak publik ke peti Anda: dimungkinkan untuk mengabaikan impl iterator Foo . Mungkin kami tidak ingin memaksa Anda melakukan itu - karenanya, spesialisasi lokal tanpa default .


Pertanyaan saya kira adalah, apa sebenarnya peran default . Membutuhkan default , menurut saya, awalnya adalah isyarat ke arah kode yang eksplisit dan mendokumentasikan diri. Sama seperti kode Rust tidak dapat diubah secara default, pribadi secara default, aman secara default, itu juga harus final secara default. Namun, karena "non-finality" adalah properti global, saya tidak dapat mengkhususkan item kecuali saya mengizinkan Anda mengkhususkan item.

Membutuhkan default , menurut saya, awalnya adalah isyarat ke arah kode yang eksplisit dan mendokumentasikan diri. Namun [..] Saya tidak bisa mengkhususkan item kecuali saya membiarkan Anda mengkhususkan item.

Apakah itu sangat buruk? Jika Anda ingin mengkhususkan diri pada implan, mungkin orang lain juga ingin melakukannya.

Saya khawatir karena hanya memikirkan RFC ini sudah memberi saya kilas balik PTSD untuk bekerja dalam basis kode C ++ yang menggunakan jumlah yang tidak senonoh dari kelebihan beban dan warisan dan tidak tahu apa yang terjadi dalam baris kode mana pun yang memiliki panggilan metode di dalamnya. Saya sangat menghargai upaya yang telah dilakukan @aturon untuk membuat spesialisasi eksplisit dan mendokumentasikan diri.

Apakah itu sangat buruk? Jika Anda ingin mengkhususkan diri pada implan, mungkin orang lain juga ingin melakukannya.

Jika orang lain hanya "mungkin" ingin mengkhususkannya juga, dan jika ada kasus bagus di mana kita tidak ingin mereka melakukannya, kita tidak boleh membuatnya tidak mungkin untuk menjelaskannya. (agak mirip dengan enkapsulasi: Anda ingin mengakses beberapa data dan mungkin beberapa orang lain juga menginginkannya - jadi Anda secara eksplisit menandai _data ini_ publik, alih-alih menyetel default semua data menjadi publik.)

Saya khawatir karena hanya memikirkan RFC ini sudah memberi saya kilas balik PTSD ...

Tetapi bagaimana pelarangan spesifikasi ini mencegah hal-hal ini terjadi?

jika ada kasus bagus di mana kita tidak menginginkannya, kita seharusnya tidak membuatnya tidak mungkin untuk menentukan ini.

Memberi pengguna kekuatan kapan pun mereka mungkin memiliki alasan penggunaan yang baik untuk itu bukanlah ide yang baik. Tidak jika itu juga memungkinkan pengguna untuk menulis kode yang membingungkan.

Tetapi bagaimana pelarangan spesifikasi ini mencegah hal-hal ini terjadi?

Katakanlah Anda melihat foo.bar() dan Anda ingin melihat apa yang dilakukan bar() . Saat ini, jika Anda menemukan metode yang diterapkan pada jenis pencocokan dan tidak ditandai default Anda tahu bahwa itu adalah definisi metode yang Anda cari. Dengan proposal @withoutboats 'ini tidak lagi benar - sebaliknya Anda tidak akan pernah tahu pasti apakah Anda benar-benar melihat kode yang sedang dieksekusi.

sebaliknya, Anda tidak akan pernah tahu pasti apakah Anda benar-benar melihat kode yang sedang dieksekusi.

Ini adalah efek yang dibesar-besarkan dengan mengizinkan spesialisasi impls non-default untuk tipe lokal. Jika Anda melihat impl konkret, Anda tahu bahwa Anda melihat impl yang benar. Dan Anda memiliki akses ke seluruh sumber peti ini; Anda dapat menentukan apakah impl ini khusus atau tidak secara signifikan lebih cepat daripada "tidak pernah".

Sementara itu, bahkan dengan default , masalah tetap ada ketika impl belum diselesaikan. Jika impl yang benar sebenarnya adalah default impl, Anda berada dalam situasi yang sama mengalami kesulitan karena tidak yakin apakah ini adalah impl yang benar. Dan tentu saja jika spesialisasi digunakan, ini akan menjadi kasus yang cukup umum (misalnya, ini adalah kasus hari ini untuk hampir setiap impl ToString ).

Sebenarnya saya pikir ini adalah masalah yang agak serius, tetapi saya tidak yakin bahwa default menyelesaikannya. Yang kami butuhkan adalah alat navigasi kode yang lebih baik. Saat ini rustdoc membuat pendekatan 'upaya terbaik' yang sangat banyak ketika berhubungan dengan sifat impls - tidak tertaut ke sumbernya dan bahkan tidak mencantumkan impls yang disediakan oleh blanket impls.

Saya tidak mengatakan bahwa perubahan ini adalah slamdunk dengan cara apa pun, tetapi saya pikir nilainya adalah pertimbangan yang lebih bernuansa.

Memberi pengguna kekuatan kapan pun mereka mungkin memiliki alasan penggunaan yang baik untuk itu bukanlah ide yang baik. Tidak jika itu juga memungkinkan pengguna untuk menulis kode yang membingungkan.

Tepat sekali, saya sangat setuju. Saya rasa saya sedang berbicara tentang "pengguna" yang berbeda di sini, yaitu pengguna peti yang Anda tulis. Anda tidak ingin mereka secara bebas mengkhususkan sifat-sifat di dalam kandang Anda (mungkin mempengaruhi perilaku kandang Anda dengan cara yang tidak benar). Di sisi lain, kami akan memberikan lebih banyak kekuatan pada "pengguna" yang Anda bicarakan, yaitu pembuat peti, tetapi bahkan tanpa proposal @withoutboats , Anda harus menggunakan "default" dan mengalami masalah yang sama .

Saya pikir default membantu dalam arti bahwa jika Anda ingin menyederhanakan membaca kode maka Anda dapat meminta agar tidak ada yang menggunakan default atau membuat aturan dokumentasi yang ketat untuk menggunakannya. Pada titik itu, Anda hanya perlu khawatir tentang default s dari std , yang mungkin akan lebih dipahami oleh orang-orang.

Saya ingat gagasan bahwa aturan dokumentasi dapat dikenakan pada penggunaan spesialisasi yang berkontribusi untuk mendapatkan persetujuan RFC spesialisasi.

@withoutboats apakah saya benar membaca motivasi Anda untuk melonggarkan default karena Anda menginginkan bentuk terbatas default yang berarti "dapat diganti, tetapi hanya dalam peti ini" (yaitu, pub(crate) tapi untuk default )? Namun, untuk menyederhanakan, Anda mengusulkan untuk mengubah semantik menghilangkan default , daripada menambahkan kelulusan default -ness?

Benar. Melakukan sesuatu seperti default(crate) sepertinya berlebihan.

A priori, saya membayangkan orang bisa mensimulasikan itu melalui apa ekspor peti, bukan? Adakah situasi di mana Anda tidak bisa begitu saja memperkenalkan sifat pembantu pribadi dengan metode default dan memanggilnya dari impl s akhir Anda sendiri? Anda ingin pengguna menggunakan default s Anda tetapi tidak menyediakannya sendiri?

Benar. Melakukan sesuatu seperti default (peti) sepertinya berlebihan.

Saya tidak setuju. Saya sangat menginginkan bentuk default terbatas. Aku bermaksud untuk mengusulkannya. Motivasi saya adalah terkadang intersection mengimplikasikan dll akan memaksa Anda untuk menambahkan default, tetapi itu tidak berarti Anda ingin mengizinkan peti sewenang-wenang untuk mengubah perilaku Anda. Maaf, ada rapat, saya bisa coba uraikan dengan contoh sebentar lagi.

@nikomatsakis Saya memiliki motivasi yang sama, yang saya usulkan adalah kami hanya menghapus persyaratan default untuk berspesialisasi dalam peti yang sama, bukan menambahkan lebih banyak tuas. :-)

Jika secara kebetulan default yang tidak diekspor ini mungkin adalah penggunaan yang lebih umum, maka fitur #[default_export] akan lebih mudah diingat dengan analogi dengan #[macro_export] . Opsi perantara mungkin mengizinkan fitur ekspor ini untuk baris pub use atau pub mod .

Menggunakan kata kunci pub akan lebih baik, karena Makro 2.0 akan mendukung makro sebagai item normal dan menggunakan pub daripada #[macro_use] . Menggunakan pub untuk menunjukkan visibilitas di seluruh papan akan menjadi kemenangan besar untuk konsistensinya.

@withoutboats terlepas, saya pikir terkadang Anda ingin mengkhususkan diri secara lokal tetapi tidak harus membuka pintu untuk semua

Menggunakan kata kunci pub akan lebih baik

Memiliki pub default fn berarti "mengekspor default fn secara publik" sebagai lawan dari mempengaruhi visibilitas fungsi itu sendiri akan sangat membingungkan bagi pendatang baru.

@jimmycuadra apakah itu yang Anda maksud dengan kata kunci pub ? Saya setuju dengan @sgrif yang tampaknya lebih membingungkan, dan jika kami akan mengizinkan Anda untuk mencakup default secara eksplisit, sintaks yang sama yang kami putuskan untuk visibilitas cakupan sepertinya jalur yang benar.

Mungkin bukan pub default fn tepatnya, karena itu ambigu, seperti yang Anda berdua sebutkan. Saya baru saja mengatakan bahwa ada manfaat memiliki pub secara universal berarti "mengekspos sesuatu yang bersifat pribadi ke luar." Mungkin ada beberapa formulasi sintaks yang melibatkan pub yang akan berbeda secara visual agar tidak bingung dengan membuat fungsi itu sendiri menjadi publik.

Meskipun ini sedikit sintaks, saya tidak akan menentang default(foo) bekerja seperti pub(foo) - simetri antara keduanya sedikit melebihi fiddliness sintaks untuk saya.

Peringatan Bikeshed: apakah kita sudah mempertimbangkan untuk menyebutnya overridable daripada default ? Ini lebih deskriptif secara harfiah, dan overridable(foo) terbaca lebih baik bagi saya daripada default(foo) - yang terakhir menyarankan "ini adalah default dalam lingkup foo , tetapi sesuatu yang lain mungkin default di tempat lain ", sedangkan yang pertama mengatakan" ini dapat diganti dalam lingkup foo ", yang benar.

Saya pikir dua pertanyaan pertama sebenarnya adalah: Apakah mengekspor atau tidak mengekspor default ness secara signifikan lebih umum? Sebaiknya tidak mengekspor default ness menjadi perilaku default?

Kasus Ya: Anda dapat memaksimalkan kemiripan dengan ekspor di tempat lain dengan perintah seperti pub mod mymodule default; dan pub use mymodule::MyTrait default; , atau mungkin dengan overridable . Jika perlu, Anda dapat mengekspor default ness hanya untuk beberapa metode dengan pub use MyModule::MyTrait::{methoda,methodb} default;

Tidak ada kasus: Anda perlu mengungkapkan privasi, bukan publisitas, yang sangat berbeda dari apa pun di Rust, jadi sekarang default(crate) menjadi cara normal untuk mengontrol ekspor ini.

Juga, jika mengekspor dan tidak mengekspor default ness sama-sama umum, maka kalian mungkin dapat memilih secara sewenang-wenang untuk berada dalam kasus ya atau tidak, jadi sekali lagi hanya memilih pub use MyModule::MyTrait::{methoda,methodb} default; berfungsi dengan baik.

Semua notasi ini terlihat kompatibel. Pilihan lain mungkin beberapa impl yang menutup default s, tapi kedengarannya rumit dan aneh.

@burdges Apakah Anda memiliki label "ya case" dan "no case" di belakang sana, atau apakah saya salah paham dengan apa yang Anda katakan?

Yup, ups! Tetap!

Kami memiliki impl<T> Borrow<T> for T where T: ?Sized sehingga Borrow<T> terikat dapat memperlakukan nilai yang dimiliki seolah-olah mereka dipinjam.

Saya kira kita dapat menggunakan spesialisasi untuk mengoptimalkan panggilan jauh ke clone dari Borrow<T> , ya?

pub trait CloneOrTake<T> {
    fn clone_or_take(self) -> T;
}

impl<B,T> CloneOrTake<T> for B where B: Borrow<T>, T: Clone {
    #[inline]
    default fn clone_or_take(b: B) -> T { b.clone() }
}
impl<T> CloneOrTake<T> for T {
    #[inline]
    fn clone_or_take(b: T) -> T { b };
}

Saya pikir ini mungkin membuat Borrow<T> dapat digunakan dalam lebih banyak situasi. Saya menjatuhkan T: ?Sized terikat karena seseorang mungkin membutuhkan Sized ketika mengembalikan T .

Mungkin pendekatan lain

pub trait ToOwnedFinal : ToOwned {
    fn to_owned_final(self) -> Self::Owned;
}

impl<B> ToOwnedFinal for B where B: ToOwned {
    #[inline]
    default fn to_owned_final(b: B) -> Self::Owned { b.to_owned() }
}
impl<T> ToOwnedFinal for T {
    #[inline]
    fn to_owned_final(b: T) -> T { b };
}

Kami telah membuat beberapa penemuan yang mungkin mengganggu hari ini, Anda dapat membaca log IRC di sini: https://botbot.me/mozilla/rust-lang/

Saya tidak 100% yakin tentang semua kesimpulan yang kami capai, terutama karena komentar Niko setelah fakta tampak menggembirakan. Untuk sesaat, hal itu tampak agak apokaliptik bagi saya.

Satu hal yang saya rasa cukup yakin adalah bahwa meminta default tidak dapat dibuat kompatibel dengan jaminan bahwa menambahkan default impls baru selalu kompatibel ke belakang. Berikut peragaannya:

peti parent v 1.0.0

trait A { }
trait B { }
trait C {
    fn foo(&self);
}

impl<T> C for T where T: B {
    // No default, not specializable!
    fn foo(&self) { panic!() }
}

peti client (tergantung pada parent )

extern crate parent;

struct Local;

impl parent::A for Local { }
impl parent::C for Local {
    fn foo(&self) { }
}

Lokal mengimplementasikan A dan C tetapi tidak B . Jika local menerapkan B , implnya C akan bertentangan dengan implan selimut yang tidak dapat dikhususkan dari C for T where T: B .

peti parent v 1.1.0

// Same code as before, but add:
default impl<T> B for T where T: A { }

Im ini telah ditambahkan, dan merupakan impl yang sepenuhnya dapat dikhususkan, jadi kami telah mengatakan ini adalah perubahan non-breaking. Namun , ini menciptakan implikasi transitif - kami sudah memiliki "semua B impl C (tidak dapat dikhususkan)", dengan menambahkan "semua A impl B (dapat dikhususkan)," kami secara implisit telah menambahkan pernyataan "semua A impl C (tidak dapat dikhususkan) ". Sekarang peti anak tidak dapat ditingkatkan.


Mungkin saja gagasan untuk menjamin bahwa menambahkan impls yang dapat dikhususkan bukanlah perubahan yang benar-benar keluar dari jendela, karena Aaron menunjukkan (seperti yang Anda lihat di log yang ditautkan di atas) bahwa Anda dapat menulis impls yang membuat jaminan yang setara mengenai defaultness . Namun, komentar Niko kemudian menunjukkan bahwa implisit semacam itu mungkin dilarang (atau setidaknya dilarang) oleh aturan yatim piatu.

Jadi yang pasti bagi saya jika 'impls adalah non-breaking' jaminan adalah diselamatkan, tetapi yakin bahwa itu tidak kompatibel dengan kontrol eksplisit atas finalitas impl.

Apakah ada rencana untuk mengizinkan ini?

struct Foo;

trait Bar {
    fn bar<T: Read>(stream: &T);
}

impl Bar for Foo {
    fn bar<T: Read>(stream: &T) {
        let stream = BufReader::new(stream);

        // Work with stream
    }

    fn bar<T: BufRead>(stream: &T) {
        // Work with stream
    }
}

Jadi pada dasarnya spesialisasi untuk fungsi templat yang memiliki parameter tipe dengan batasan pada A dimana versi khusus memiliki batasan pada B (yang membutuhkan A ).

@torkleyy saat ini tidak tetapi Anda dapat melakukannya secara diam-diam dengan membuat sifat yang diimplementasikan untuk T: Read dan T: BufRead dan berisi bagian-bagian kode yang ingin Anda spesialisasi dalam impls dari sifat itu. Itu bahkan tidak perlu terlihat di API publik.

Mengenai masalah kompatibilitas mundur, saya pikir berkat aturan yatim piatu kita bisa lolos dengan aturan ini:

_An impl kompatibel ke belakang untuk ditambahkan kecuali : _

  • _Ciri yang diimplikasikan adalah sifat otomatis._
  • _Penerima adalah parameter tipe, dan setiap sifat dalam impl sebelumnya ada._

Artinya, saya pikir dalam semua contoh yang bermasalah, impl tambahan adalah impl selimut. Kami ingin mengatakan bahwa impls blanket default sepenuhnya juga baik-baik saja, tetapi saya pikir kami hanya perlu mengatakan bahwa menambahkan impls blanket yang ada dapat menjadi perubahan yang merusak.

Pertanyaannya adalah jaminan apa yang ingin kami berikan dalam menghadapi hal itu - misalnya saya pikir itu akan menjadi properti yang sangat bagus jika setidaknya selimut impl hanya dapat menjadi perubahan yang melanggar berdasarkan kode di peti Anda, sehingga Anda dapat meninjau peti Anda dan tahu dengan pasti apakah Anda perlu menaikkan versi mayor atau tidak.

@tokopedia

Mengenai masalah kompatibilitas mundur, saya pikir berkat aturan yatim piatu kita bisa lolos dengan aturan ini:

_An impl kompatibel ke belakang untuk ditambahkan kecuali : _

  • _Ciri yang diimplikasikan adalah sifat otomatis._
  • _Penerima adalah parameter tipe, dan setiap sifat dalam impl sebelumnya ada._

Artinya, saya pikir dalam semua contoh yang bermasalah, impl tambahan adalah impl selimut. Kami ingin mengatakan bahwa impls blanket default sepenuhnya juga baik-baik saja, tetapi saya pikir kami hanya perlu mengatakan bahwa menambahkan impls blanket yang ada dapat menjadi perubahan yang merusak.

Seminggu dan banyak diskusi kemudian, sayangnya hal ini tidak terjadi .

Hasil yang kami peroleh adalah: cryo_cat_face :, tapi menurut saya apa yang saya tulis di sana sama dengan kesimpulan Anda. Menambahkan impls selimut adalah perubahan yang menghancurkan, apa pun yang terjadi. Tapi hanya blanket impls (dan auto trait impls); Sejauh yang saya tahu, kami belum menemukan kasus di mana impl non-blanket dapat memecah kode hilir (dan itu akan sangat buruk).

Saya pernah berpikir pada satu titik bahwa kami mungkin dapat melonggarkan aturan yatim piatu sehingga Anda dapat menerapkan sifat untuk tipe seperti Vec<MyType> , tetapi jika kami melakukannya, situasi ini akan berjalan dengan cara yang persis sama di sana:

//crate A

trait Foo { }

// new impl
// impl<T> Foo for Vec<T> { }
// crate B
extern crate A;

use A::Foo;

trait Bar {
    type Assoc;
}

// Sadly, this impl is not an orphan
impl<T> Bar for Vec<T> where Vec<T>: Foo {
    type Assoc = ();
}
// crate C

struct Baz;

// Therefore, this impl must remain an orphan
impl Bar for Vec<Baz> {
    type Assoc = bool;
}

@withoutboats Ah, saya memahami daftar dua butir Anda sebagai atau lebih daripada dan , yang tampaknya apa yang Anda maksud?

@aturon Ya, maksud saya 'atau' - itu adalah dua kasus di mana itu adalah perubahan yang menghancurkan. Setiap impl sifat otomatis, betapapun konkretnya, adalah perubahan besar karena cara kami mengizinkan penalaran negatif tentangnya untuk menyebar: https://is.gd/k4Xtlp

Artinya, kecuali jika berisi nama baru. AFAIK sebuah impl yang berisi nama baru tidak pernah rusak.

@withoutboats Saya ingin tahu apakah kita dapat / harus membatasi orang yang mengandalkan logika negatif seputar sifat otomatis. Artinya, jika kami mengatakan bahwa menambahkan impls baru dari sifat otomatis adalah perubahan yang melanggar hukum, kami mungkin akan memperingatkan tentang impls yang dapat dirusak oleh peti upstream yang menambahkan Send . Ini akan bekerja paling baik jika kita memiliki:

  1. spesialisasi stabil, seseorang dapat mengatasi peringatan dengan menambahkan default di tempat-tempat strategis (sebagian besar waktu);
  2. beberapa bentuk implik negatif eksplisit, sehingga tipe seperti Rc dapat menyatakan niat mereka untuk tidak pernah menjadi Send - tapi kemudian kita memiliki itu untuk sifat otomatis, jadi kita bisa mempertimbangkannya.

Saya tidak tahu, saya pikir itu tergantung apakah ada motivasi yang kuat atau tidak. Tampaknya sangat tidak mungkin Anda menyadari bahwa suatu tipe dapat memiliki unsafe impl Send/Sync setelah Anda merilisnya; Saya pikir sebagian besar waktu itu akan aman, Anda akan menulis sebuah tipe dengan pengetahuan sebelumnya bahwa itu akan aman (karena itulah inti dari tipe tersebut).

Saya menambahkan unsafe impl Send/Sync setelah fakta sepanjang waktu. Kadang-kadang karena saya membuatnya aman untuk utas, kadang karena saya menyadari C API yang saya antarmuka baik-baik saja untuk dibagikan di utas, dan terkadang itu hanya karena apakah sesuatu harus Send / Sync isn Bukan apa yang saya pikirkan ketika saya memperkenalkan suatu tipe.

Saya menambahkannya setelah fakta juga saat mengikat C API - seringkali karena seseorang secara eksplisit meminta batasan itu jadi saya kemudian memeriksa dan memeriksa apa yang dijamin oleh perpustakaan yang mendasarinya.

Satu hal yang tidak saya sukai tentang cara kerja sifat terkait yang terspesialisasi saat ini, pola ini tidak berfungsi:

trait Buffer: Read {
    type Buffered: BufRead;
    fn buffer(self) -> impl BufRead;
}

impl<T: Read> Buffer for T {
    default type Buffered = BufReader<T>;
    default fn buffer(self) -> BufReader<T> {
        BufReader::new(self)
    }
}

impl<T: BufRead> Buffer for T {
    type Buffered = Self;
    fn buffer(self) -> T {
        self
    }
}

Ini karena sistem saat ini mengharuskan impl ini valid:

impl Buffer for SomeRead {
    type Buffered = SomeBufRead;
    // no overriding of fn buffer, it no longer returns Self::Buffered
}

impl Trait dalam ciri-ciri akan melepaskan banyak keinginan untuk pola semacam ini, tetapi saya bertanya-tanya apakah tidak ada solusi yang lebih baik di mana impl umum valid tetapi spesialisasi itu tidak berfungsi karena memperkenalkan kesalahan jenis ?

@withoutboats Ya, ini adalah salah satu pertanyaan utama yang belum terselesaikan tentang desain (yang lupa saya

@aturon Apakah solusi saat ini yang paling konservatif (maju kompatibel dengan apa pun yang ingin kita lakukan) atau apakah itu keputusan yang harus kita buat sebelum stabil?

Saya pribadi berpikir satu-satunya solusi nyata untuk masalah ini yang dimunculkan @withoutboats adalah mengizinkan item untuk "dikelompokkan" bersama ketika Anda menentukan tag default . Ini semacam solusi yang lebih baik-lebih-lebih baik, tetapi saya merasa varian yang lebih buruk-lebih-lebih baik (menimpa cara apa pun yang menimpa semua) agak lebih buruk. (Tapi sebenarnya @withoutboats cara Anda menulis kode ini membingungkan. Saya pikir sebagai ganti menggunakan impl BufRead sebagai jenis pengembalian Buffer , maksud Anda Self::BufReader , bukan?)

Dalam kasus tersebut, berikut ini akan diizinkan:

trait Buffer: Read {
    type Buffered: BufRead;
    fn buffer(self) -> impl BufRead;
}

impl<T: Read> Buffer for T {
    default {
        type Buffered = BufReader<T>;
        fn buffer(self) -> BufReader<T> {
            BufReader::new(self)
        }
    }
}

impl<T: BufRead> Buffer for T {
    type Buffered = Self;
    fn buffer(self) -> T {
        self
    }
}

Tapi mungkin kita bisa menyimpulkan pengelompokan ini? Saya tidak terlalu memikirkannya, tetapi tampaknya fakta bahwa item default "terjerat" terlihat dari definisi sifat.

Tapi sebenarnya @withoutboats cara Anda menulis kode ini membingungkan. Saya pikir sebagai ganti menggunakan impl BufRead sebagai jenis kembalian Buffer, yang Anda maksud adalah Self :: BufReader, bukan?

Ya, saya telah memodifikasi solusi menjadi impl Trait berbasis & kemudian beralih kembali tetapi melewatkan tipe pengembalian dalam sifat tersebut.

Mungkin sesuatu seperti sistem tipe bahasa ini mungkin juga menarik, karena sepertinya mirip dengan Rusts, tetapi dengan beberapa fitur, itu dapat menyelesaikan masalah saat ini.
( A <: B akan di Rust menjadi true ketika A adalah sebuah struct dan mengimplementasikan ciri B , atau bila A adalah ciri, dan implementasi umum untuk objek dari sifat ini ada, saya pikir)

Tampaknya ada masalah dengan ciri Display untuk spesialisasi.
Misalnya, contoh ini tidak dapat dikompilasi:

use std::fmt::Display;

pub trait Print {
    fn print(&self);
}

impl<T: Display> Print for T {
    default fn print(&self) {
        println!("Value: {}", self);
    }
}

impl Print for () {
    fn print(&self) {
        println!("No value");
    }
}

fn main() {
    "Hello, world!".print();
    ().print();
}

dengan kesalahan berikut:

error[E0119]: conflicting implementations of trait `Print` for type `()`:
  --> src/main.rs:41:1
   |
35 |   impl<T: Display> Print for T {
   |  _- starting here...
36 | |     default fn print(&self) {
37 | |         println!("Value: {}", self);
38 | |     }
39 | | }
   | |_- ...ending here: first implementation here
40 | 
41 |   impl Print for () {
   |  _^ starting here...
42 | |     fn print(&self) {
43 | |         println!("No value");
44 | |     }
45 | | }
   | |_^ ...ending here: conflicting implementation for `()`

sementara ini mengkompilasi:

pub trait Print {
    fn print(&self);
}

impl<T: Default> Print for T {
    default fn print(&self) {
    }
}

impl Print for () {
    fn print(&self) {
        println!("No value");
    }
}

fn main() {
    "Hello, world!".print();
    ().print();
}

Terima kasih telah memperbaiki masalah ini.

@antoyo apakah Anda yakin itu karena Display spesial, atau mungkinkah karena Display tidak diimplementasikan untuk tupel sedangkan Default adalah?

@magetanbanget
Saya tidak tahu apakah ini tentang Display , tetapi yang berikut ini bekerja dengan sifat Custom tidak diterapkan untuk tupel:

pub trait Custom { }

impl<'a> Custom for &'a str { }

pub trait Print {
    fn print(&self);
}

impl<T: Custom> Print for T {
    default fn print(&self) {
    }
}

impl Print for () {
    fn print(&self) {
        println!("No value");
    }
}

fn main() {
    "Hello, world!".print();
    ().print();
}

Ngomong-ngomong, inilah hal nyata yang ingin saya capai dengan spesialisasi:

pub trait Emit<C, R> {
    fn emit(callback: C, value: Self) -> R;
}

impl<C: Fn(Self) -> R, R, T> Emit<C, R> for T {
    default fn emit(callback: C, value: Self) -> R {
        callback(value)
    }
}

impl<C> Emit<C, C> for () {
    fn emit(callback: C, _value: Self) -> C {
        callback
    }
}

Saya ingin memanggil fungsi secara default, atau mengembalikan nilai jika parameternya adalah unit.
Saya mendapatkan kesalahan yang sama tentang implementasi yang bertentangan.
Mungkinkah (atau akankah ini mungkin) untuk melakukannya dengan spesialisasi?
Jika tidak, apa alternatifnya?

Sunting: Saya pikir saya sudah tahu mengapa itu tidak dapat dikompilasi:
T di for T lebih umum daripada () di for () jadi impl tidak dapat menjadi spesialisasi.
Dan C lebih umum daripada C: Fn(Self) -> R jadi impl tidak bisa menjadi spesialisasi.
Tolong beritahu saya jika saya salah.
Tapi saya masih tidak mengerti mengapa itu tidak berfungsi dengan contoh pertama dengan Display .

Ini adalah perilaku yang benar saat ini.

Dalam contoh Custom , implik tersebut tidak tumpang tindih karena alasan negatif lokal khusus. Karena sifatnya berasal dari peti ini, kita dapat menyimpulkan bahwa () , yang tidak memiliki implikasi Custom , tidak tumpang tindih dengan T: Custom . Tidak perlu spesialisasi.

Namun, kami tidak melakukan penalaran negatif ini untuk sifat-sifat yang bukan dari kandang Anda. Pustaka standar dapat menambahkan Display for () di rilis berikutnya, dan kami tidak ingin itu menjadi perubahan yang mengganggu. Kami ingin perpustakaan memiliki kebebasan untuk melakukan perubahan semacam itu. Jadi meskipun () tidak mengimplikasikan Display, kita tidak dapat menggunakan informasi tersebut dalam pemeriksaan overlap.

Tetapi juga, karena () tidak mengimplikasikan Tampilan, ini tidak lebih spesifik dari T: Display . Inilah sebabnya mengapa spesialisasi tidak berfungsi, sedangkan dalam kasus Default, (): Default , oleh karena itu impl lebih spesifik daripada T: Default .

Impls seperti ini adalah semacam 'limbo' di mana kita tidak bisa menganggapnya tumpang tindih atau tidak. Kami mencoba mencari cara berprinsip untuk membuat ini berfungsi, tetapi ini bukan implementasi spesialisasi pertama, ini adalah ekstensi yang kompatibel dengan versi sebelumnya untuk fitur itu yang akan datang nanti.

Saya mengajukan # 40582 untuk melacak masalah kesehatan yang berhubungan dengan seumur hidup.

Saya mengalami masalah saat mencoba menggunakan spesialisasi, menurut saya tidak sama dengan yang dimiliki @antoyo , saya telah mengajukannya sebagai masalah terpisah # 41140, saya dapat membawa contoh kode dari situ ke sini jika perlu

@ afonso360 Tidak, masalah terpisah tidak masalah.

Sebagai poin umum: pada poin ini, pekerjaan spesialisasi lebih lanjut diblokir pada pekerjaan di Chalk , yang akan memungkinkan kita untuk mengatasi masalah kesehatan dan juga mungkin untuk menjernihkan ICE yang sedang dilanda hari ini.

Bisakah seseorang mengklarifikasi apakah ini bug, atau sesuatu yang sengaja dilarang? https://is.gd/pBvefi

@sgrif Saya yakin masalah di sini hanyalah bahwa proyeksi jenis terkait default tidak diizinkan. Diagnostik bisa lebih baik: https://github.com/rust-lang/rust/issues/33481

Bisakah Anda menjelaskan mengapa hal itu diharapkan untuk tidak diizinkan? Kami tahu bahwa impl yang lebih spesifik tidak dapat ditambahkan, karena itu akan melanggar aturan yatim piatu.

Komentar ini menunjukkan bahwa perlu untuk beberapa kasus agar memerlukan kesesuaian (meskipun saya tidak tahu mengapa) dan di kasus lain untuk memaksa konsumen antarmuka untuk memperlakukannya sebagai tipe abstrak: https://github.com/rust- lang / rust / blob / e5e664f / src / librustc / traits / project.rs # L41

Apakah ada yang pernah bisa melihat https://github.com/rust-lang/rust/issues/31844#issuecomment -266221638? Implikasi tersebut harus valid dengan spesialisasi sejauh yang saya tahu. Saya yakin ada bug yang mencegah mereka.

@sgrif Saya yakin masalah dengan kode Anda mungkin mirip dengan masalah di https://github.com/rust-lang/rust/issues/31844#issuecomment -284235369 yang dijelaskan @withoutboats di https://github.com / rust-lang / rust / issues / 31844 # issue -284268302. Karena itu, berdasarkan komentar @withoutboats , nampaknya penalaran lokal saat ini memungkinkan contoh Anda untuk dikompilasi, tetapi mungkin saya salah tentang apa yang diharapkan berhasil.

Selain itu, saya mencoba menerapkan yang berikut ini, tidak berhasil:

trait Optional<T> {
    fn into_option(self) -> Option<T>;
}

impl<R, T: Into<R>> Optional<R> for T {
    default fn into_option(self) -> Option<R> {
        Some(self.into())
    }
}

impl<R> Optional<R> for Option<R> {
    fn into_option(self) -> Option<R> {
        self
    }
}

Saya secara intuitif mengharapkan Option<R> lebih spesifik daripada <R, T: Into<R>> T , tetapi tentu saja, tidak ada yang mencegah impl<R> Into<R> for Option<R> di masa mendatang.

Saya tidak yakin mengapa hal ini tidak diizinkan. Bahkan jika impl<R> Into<R> for Option<R> ditambahkan di masa mendatang, saya masih berharap Rust untuk memilih implementasi non- default , sejauh yang saya bisa lihat, mengizinkan kode ini tidak berimplikasi pada penerusan- kesesuaian.

Secara keseluruhan, saya menemukan spesialisasi sangat menyebalkan untuk dikerjakan. Hampir semua hal yang saya harapkan tidak berhasil. Satu-satunya kasus di mana saya berhasil dengan spesialisasi adalah kasus yang sangat sederhana, seperti memiliki dua impl s yang mencakup T where T: A dan T where T: A + B . Saya kesulitan mendapatkan hal-hal lain untuk berfungsi, dan pesan kesalahan tidak menunjukkan mengapa upaya untuk mengkhususkan tidak berhasil. Tentu saja, masih ada jalan di depan, jadi saya tidak mengharapkan pesan kesalahan yang sangat membantu. Tetapi tampaknya ada beberapa kasus di mana saya benar-benar mengharapkan sesuatu untuk bekerja (seperti di atas) tetapi ternyata tidak, dan saat ini cukup sulit bagi saya untuk memastikan apakah itu karena saya salah paham tentang apa yang diperbolehkan (dan yang lebih penting, mengapa), jika ada yang salah, atau jika sesuatu belum diterapkan. Tinjauan yang bagus tentang apa yang terjadi dengan fitur ini sebagaimana adanya akan sangat membantu.

Saya tidak yakin ini di tempat yang benar, tetapi kami mengalami masalah di forum pengguna yang ingin saya sebutkan di sini.

Kode berikut (yang diadaptasi dari RFC di sini ) tidak dapat dikompilasi setiap malam:

#![feature(specialization)]

trait Example {
    type Output;
    fn generate(self) -> Self::Output;
}

default impl<T> Example for T {
    type Output = Box<T>;
    fn generate(self) -> Self::Output { Box::new(self) }
}

impl Example for bool {
    type Output = bool;
    fn generate(self) -> Self::Output { self }
}

Ini tidak benar-benar tampak seperti kesalahan tetapi lebih seperti masalah kegunaan - jika hipotesis impl hanya mengkhususkan jenis terkait dalam contoh di atas, defaulti impl dari generate wouldn cek ketik.

Tautkan ke utas di sini

@ burns47 ada solusi yang membingungkan namun berguna di sini: https://github.com/rust-lang/rust/issues/31844#issuecomment -263175793.

@dtolnay Kurang memuaskan - bagaimana jika kita mengkhususkan diri pada sifat yang tidak kita miliki (dan tidak dapat dimodifikasi)? Kita tidak perlu menulis ulang / refactor definisi sifat untuk melakukan IMO ini.

Adakah yang bisa berkomentar tentang apakah kode dalam masalah berikut ini sengaja ditolak? https://github.com/rust-lang/rust/issues/45542

Apakah spesialisasi memungkinkan penambahan sesuatu seperti berikut ini ke libcore?

impl<T: Ord> Eq for T {}

impl<T: Ord> PartialEq for T {
    default fn eq(&self, other: &Self) -> bool {
        self.cmp(other) == Ordering::Equal
    }
}

impl<T: Ord> PartialOrd for T {
    default fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

Dengan cara ini Anda dapat menerapkan Ord untuk jenis ubahsuaian Anda dan memiliki Eq , PartialEq , dan PartialOrd diterapkan secara otomatis.

Perhatikan bahwa menerapkan Ord dan secara bersamaan memperoleh PartialEq atau PartialOrd berbahaya dan dapat menyebabkan bug yang sangat halus! Dengan impls default ini, Anda tidak akan tergoda untuk mendapatkan sifat-sifat tersebut, jadi masalahnya akan sedikit berkurang.


Atau, kami memodifikasi derivasi untuk memanfaatkan spesialisasi. Misalnya, menulis #[derive(PartialOrd)] atas struct Foo(String) dapat menghasilkan kode berikut:

impl PartialOrd for Foo {
    default fn partial_cmp(&self, other: &Foo) -> Option<Ordering> {
        self.0.partial_cmp(&other.0)
    }
}

impl PartialOrd for Foo where Foo: Ord {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

Dengan cara ini impl default digunakan jika Ord tidak diimplementasikan. Tetapi jika ya, maka PartialOrd bergantung pada Ord . Sayangnya, ini tidak dapat dikompilasi: error[E0119]: conflicting implementations of trait `std::cmp::PartialOrd` for type `Foo`

@stjepang tentunya saya berharap selimut seperti itu bisa ditambahkan - impl<T:Copy> Clone for T juga.

kupikir

impl<T: Ord> PartialEq for T

seharusnya

impl<T, U> PartialEq<U> for T where T : PartialOrd<U>

karena PartialOrd membutuhkan PartialEq dan dapat menyediakannya juga.

Saat ini, seseorang tidak dapat benar-benar menggunakan jenis terkait untuk membatasi spesialisasi, baik karena keduanya tidak dapat dibiarkan tidak ditentukan dan karena memicu rekursi yang tidak diperlukan . Lihat https://github.com/dhardy/rand/issues/18#issuecomment -358147645

Akhirnya, saya ingin sekali melihat apa yang saya sebut grup spesialisasi dengan sintaks yang diusulkan oleh @nikomatsakis di sini https://github.com/rust-lang/rust/issues/31844#issuecomment -249355377 dan secara mandiri oleh saya. Saya ingin menulis RFC pada proposal itu nanti ketika kita mendekati stabilisasi spesialisasi.

Seandainya tidak ada yang melihatnya, posting blog ini mencakup proposal untuk membuat spesialisasi terdengar dalam menghadapi pengiriman berbasis seumur hidup.

Karena penutupan salinan sudah distabilkan di Beta, developer memiliki lebih banyak motivasi untuk menstabilkan spesialisasi sekarang. Alasannya adalah bahwa Fn dan FnOnce + Clone mewakili dua set penutupan yang tumpang tindih, dan dalam banyak kasus kita perlu menerapkan sifat untuk keduanya.

Cari tahu saja bahwa kata-kata dari rfc 2132 tampaknya menyiratkan bahwa hanya ada 5 jenis penutupan:

  • FnOnce (penutupan move dengan semua variabel yang ditangkap bukan Copy atau Clone )
  • FnOnce + Clone (penutupan move dengan semua variabel yang ditangkap menjadi Clone )
  • FnOnce + Copy + Clone (penutupan move dengan semua variabel yang ditangkap menjadi Copy dan Clone )
  • FnMut + FnOnce (penutupan non- move dengan variabel hasil mutasi)
  • Fn + FnMut + FnOnce + Copy + Clone (penutupan non- move tanpa variabel yang diambil mutasi)

Jadi jika spesifikasi tidak tersedia dalam waktu dekat, mungkin kita harus memperbarui definisi kita dari Fn sifat sehingga Fn tidak tumpang tindih dengan FnOnce + Clone ?

Saya memahami bahwa seseorang mungkin telah menerapkan jenis tertentu yaitu Fn tanpa Copy/Clone , tetapi apakah ini harus dihentikan? Saya pikir selalu ada cara yang lebih baik untuk melakukan hal yang sama.

Apakah yang berikut ini seharusnya diizinkan oleh spesialisasi (perhatikan tidak adanya default ) atau apakah ini bug?

#![feature(specialization)]
mod ab {
    pub trait A {
        fn foo_a(&self) { println!("a"); }
    }

    pub trait B {
        fn foo_b(&self) { println!("b"); }
    }

    impl<T: A> B for T {
        fn foo_b(&self) { println!("ab"); }
    }

    impl<T: B> A for T {
        fn foo_a(&self) { println!("ba"); }
    }
}

use ab::B;

struct Foo;

impl B for Foo {}

fn main() {
    Foo.foo_b();
}

tanpa spesialisasi, ini gagal untuk membangun dengan:

error[E0119]: conflicting implementations of trait `ab::B` for type `Foo`:
  --> src/main.rs:24:1
   |
11 |     impl<T: A> B for T {
   |     ------------------ first implementation here
...
24 | impl B for Foo {}
   | ^^^^^^^^^^^^^^ conflicting implementation for `Foo`

@glandium apa yang sebenarnya terjadi di sana? Contoh yang bagus, ini tautan taman bermainnya: https://play.rust-lang.org/?gist=fc7cf5145222c432e2 E5E5E5de1b0a425cd&version=nightly&mode=

Apakah itu? tidak ada impl kosong dalam contoh saya.

@bayu_joo

 impl B for Foo {}

@MoSal tapi itu berarti "tidak kosong" karena B menambahkan metode dengan implementasi default.

@gnzlbg Menurut definisi, kosong. Tidak ada apa pun di antara kawat gigi.


#![feature(specialization)]

use std::borrow::Borrow;

#[derive(Debug)]
struct Bla {
    bla: Vec<Option<i32>>
}

// Why is this a conflict ?
impl From<i32> for Bla {
    fn from(i: i32) -> Self {
        Bla { bla: vec![Some(i)] }
    }
}

impl<B: Borrow<[i32]>> From<B> for Bla {
    default fn from(b: B) -> Self {
        Bla { bla: b.borrow().iter().map(|&i| Some(i)).collect() }
    }
}

fn main() {
    let b : Bla = [1, 2, 3].into();
    println!("{:?}", b);
}

error[E0119]: conflicting implementations of trait `std::convert::From<i32>` for type `Bla`:
  --> src/main.rs:17:1
   |
11 | impl From<i32> for Bla {
   | ---------------------- first implementation here
...
17 | impl<B: Borrow<[i32]>> From<B> for Bla {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Bla`
   |
   = note: upstream crates may add new impl of trait `std::borrow::Borrow<[i32]>` for type `i32` in future versions

Bukankah spesialisasi mencegah kemungkinan konflik di masa depan?

Ya ampun, ini adalah fitur yang bergerak lambat! Tidak ada kemajuan dalam lebih dari dua tahun, tampaknya (tentunya menurut postingan aslinya). Apakah tim lang meninggalkan ini?

@alexreg lihat http://aturon.github.io/2018/04/05/sound-specialization/ untuk perkembangan terbaru.

@alexreg Ternyata kesehatan adalah _hard_. Saya yakin ada beberapa pekerjaan tentang gagasan "selalu berlaku" yang sedang terjadi, jadi ada kemajuan. Lihat https://github.com/rust-lang/rust/pull/49624. Juga, saya percaya bahwa kelompok kerja kapur juga sedang bekerja untuk mengimplementasikan ide "impls selalu berlaku", tetapi saya tidak tahu seberapa jauh hal itu telah terjadi.

Setelah sedikit pertengkaran, tampaknya mungkin untuk secara efektif mengimplementasikan implikasi persimpangan melalui peretasan menggunakan specialization dan overlapping_marker_traits .

https://play.rust-lang.org/?gist=cb7244f41c040db41fc447d491031263&version=nightly&mode=debug

Saya mencoba menulis fungsi khusus rekursif untuk mengimplementasikan yang setara dengan kode C ++ ini:


Kode C ++

#include <cassert>
#include <vector>

template<typename T>
size_t count(T elem)
{
    return 1;
}

template<typename T>
size_t count(std::vector<T> vec)
{
    size_t n = 0;
    for (auto elem : vec)
    {
        n += count(elem);
    }
    return n;
}

int main()
{
    auto v1 = std::vector{1, 2, 3};
    assert(count(v1) == 3);

    auto v2 = std::vector{ std::vector{1, 2, 3}, std::vector{4, 5, 6} };
    assert(count(v2) == 6);

    return 0;
}


Saya mencoba ini:


Kode karat

#![feature(specialization)]

trait Count {
    fn count(self) -> usize;
}

default impl<T> Count for T {
    fn count(self) -> usize {
        1
    }
}

impl<T> Count for T
where
    T: IntoIterator,
    T::Item: Count,
{
    fn count(self) -> usize {
        let i = self.into_iter();

        i.map(|x| x.count()).sum()
    }
}

fn main() {
    let v = vec![1, 2, 3];
    assert_eq!(v.count(), 3);

    let v = vec![
        vec![1, 2, 3],
        vec![4, 5, 6],
    ];
    assert_eq!(v.count(), 6);
}


Tapi saya mendapatkan:

overflow evaluating the requirement `{integer}: Count`

Saya tidak berpikir bahwa ini harus terjadi karena impl<T> Count for T where T::Item: Count seharusnya tidak meluap.

EDIT: maaf, saya baru saja melihat ini sudah disebutkan

@Boiethios Usecase Anda berfungsi jika Anda default di fn dan bukan di impl:

#![feature(specialization)]

trait Count {
    fn count(self) -> usize;
}

impl<T> Count for T {
    default fn count(self) -> usize {
        1
    }
}

impl<T> Count for T
where
    T: IntoIterator,
    T::Item: Count,
{
    fn count(self) -> usize {
        let i = self.into_iter();

        i.map(|x| x.count()).sum()
    }
}

fn main() {
    let v = vec![1, 2, 3];
    assert_eq!(v.count(), 3);

    let v = vec![vec![1, 2, 3], vec![4, 5, 6]];
    assert_eq!(v.count(), 6);
}

Apakah lubang kesehatan masih belum diperbaiki?

@alexreg Saya rasa tidak. Lihat http://smallcultfollowing.com/babysteps/blog/2018/02/09/maximally-minimal-specialization-always-applicable-impls/

Dugaan saya adalah semua orang saat ini fokus pada edisi ...

Oke terima kasih ... sepertinya masalah ini berlarut-larut selamanya, tapi cukup adil. Itu sulit, saya tahu. Dan sayangnya sekarang perhatian diarahkan ke tempat lain.

Dapatkah seseorang secara lebih konkret menjelaskan alasan di balik tidak mengizinkan proyeksi untuk tipe terkait default dalam kasus monomorfik penuh? Saya memiliki kasus penggunaan di mana saya ingin fungsionalitas itu (khususnya, secara semantik tidak benar untuk sifat yang pernah dipanggil dengan jenis yang tidak sepenuhnya monomorfik), dan jika tidak ada masalah kesehatan, saya tidak sepenuhnya mengerti mengapa itu tidak diizinkan.

@pythonesque Ada beberapa diskusi di https://github.com/rust-lang/rust/pull/42411

Ah, saya mengerti jika ternyata proyeksi berinteraksi buruk dengan spesialisasi secara umum. . Dan memang benar bahwa yang saya inginkan adalah rasa "penalaran negatif" (meskipun sifat-sifat tertutup tidak akan cukup).

Sayangnya, saya tidak yakin apakah benar-benar ada cara untuk melakukan apa yang saya inginkan tanpa fitur seperti itu: Saya ingin memiliki jenis terkait yang menghasilkan "True" ketika dua jenis yang diteruskan yang mengimplementasikan sifat tertentu secara sintaksis sama, dan "False" jika tidak (dengan kasus "False" memicu penelusuran sifat yang lebih mahal yang dapat memutuskan apakah keduanya sama "secara semantik"). Satu-satunya alternatif nyata tampaknya (bagi saya) adalah selalu melakukan pencarian mahal; yang secara teori bagus, tapi bisa jadi jauh lebih mahal.

(Saya bisa mengatasi ini jika sifat itu dimaksudkan untuk ditutup, dengan hanya menghitung setiap pasangan konstruktor yang mungkin di posisi kepala dan membuatnya mengeluarkan True atau False; tetapi itu dimaksudkan untuk terbuka untuk ekstensi di luar repositori, sehingga bisa mungkin tidak berfungsi, terutama karena implementasi di dua repositori pengguna yang berbeda belum tentu saling mengetahui).

Bagaimanapun, mungkin ini hanya indikasi bahwa apa yang ingin saya lakukan tidak sesuai untuk sistem sifat dan saya harus beralih ke beberapa mekanisme lain, seperti makro: P

Dan memang benar bahwa yang saya inginkan adalah rasa "penalaran negatif" (meskipun sifat-sifat tertutup tidak akan cukup).

Sebuah alternatif untuk penalaran negatif mensyaratkan bahwa suatu tipe hanya mengimplementasikan satu sifat dari satu set sifat tertutup, sehingga implementasi dengan sifat lain dalam himpunan tidak dapat tumpang tindih (misalnya T mengimplementasikan salah satu dari { Float | Int | Bool | Ptr } ).

Bahkan jika ada cara untuk memaksakan itu di Rust (yang tidak ada, AFAIK?), Saya rasa itu tidak akan menyelesaikan masalah saya. Saya ingin pengguna di peti yang berbeda dapat menerapkan sejumlah konstanta baru yang sewenang-wenang, yang harus dibandingkan hanya dengan dirinya sendiri dan tidak sama dengan setiap konstanta yang ditentukan lainnya, termasuk yang tidak diketahui pada waktu definisi peti. Saya tidak melihat bagaimana kumpulan sifat tertentu (atau bahkan kumpulan sifat) dapat mencapai tujuan itu dengan sendirinya: ini adalah masalah yang pada dasarnya tidak dapat diselesaikan tanpa melihat langsung jenisnya. Alasan mengapa hal ini dapat diterapkan dengan proyeksi default adalah bahwa Anda dapat menetapkan semuanya secara default ke "jangan bandingkan sama" dan kemudian menerapkan persamaan konstanta baru Anda ke dirinya sendiri di kotak apa pun yang Anda tentukan konstanta, yang tidak akan bertabrakan dengan yatim piatu aturan karena semua jenis dalam implementasi sifat berada di peti yang sama. Jika saya menginginkan hampir semua aturan seperti itu kecuali kesetaraan, ini pun tidak akan berhasil, tetapi kesetaraan sudah cukup baik untuk saya :)

Pada malam hari ini, ini berfungsi:

trait Foo {}
trait Bar {}

impl<T: Bar> Foo for T {}
impl Foo for () {}

tetapi bahkan dengan spesialisasi, dan menggunakan nightly, ini tidak:

#![feature(specialization)]

trait Foo<F> {}
trait Bar<F> {}

default impl<F, T: Bar<F>> Foo<F> for T {}
impl<F> Foo<F> for () {}

Apakah ini memiliki alasan atau bug?

@rmanoka Bukankah ini hanya aturan yatim piatu biasa? Dalam kasus pertama, tidak ada peti hilir yang dapat impl Bar for () sehingga kompilator mengizinkannya, tetapi pada contoh kedua, peti hilir dapat impl Bar<CustomType> for () yang akan bertentangan dengan impl default Anda.

@Boscop Dalam skenario itu, impl default tetap harus diganti oleh non-default di bawah ini. Misalnya, jika saya memiliki: impl Bar<bool> for () {} ditambahkan sebelum impls lainnya, maka saya akan berharap itu berfungsi (sesuai RFC / harapan). Benar bukan?

Menggali lebih dalam di sepanjang garis kontra-contoh yang Anda sebutkan, saya menyadari (atau percaya) bahwa contoh tersebut memenuhi tes "selalu berlaku" , dan mungkin sedang dikerjakan.

Masalah ini mungkin bergantung pada # 45814.

Apakah ada rencana untuk mendukung batasan sifat pada default yang tidak ada dalam spesialisasi?

Sebagai contoh, ini akan sangat berguna, sehingga Anda dapat dengan mudah menyusun penanganan berbagai jenis dengan membuat Struct generik dengan Inner arbitrary untuk fungsionalitas yang tidak boleh dibagikan.

#![feature(specialization)]
trait Handler<M> {
    fn handle(&self, m:M);
}

struct Inner;
impl Handler<f64> for Inner {
    fn handle(&self, m : f64) {
        println!("inner got an f64={}", m);
    }
}

struct Struct<T>(T);
impl<T:Handler<M>, M:std::fmt::Debug> Handler<M> for Struct<T> {
    default fn handle(&self, m : M) {
        println!("got something else: {:?}", m);
        self.0.handle(m)
    }
}
impl<T> Handler<String> for Struct<T> {
    fn handle(&self, m : String) {
        println!("got a string={}", m);
    }
}
impl<T> Handler<u32> for Struct<T> {
    fn handle(&self, m : u32) {
        println!("got a u32={}", m);
    }
}

fn main() {
    let s = Struct(Inner);
    s.handle("hello".to_string());
    s.handle(5.0 as f64);
    s.handle(5 as u32);
}

Selanjutnya, dalam contoh di atas, sesuatu yang aneh yang saya alami - setelah menghapus sifat terikat pada default Handler impl (dan juga self.0.handle (m)) kode dikompilasi tanpa masalah. Namun, ketika Anda menghapus implementasi untuk u32, tampaknya merusak deduksi sifat lainnya:

#![feature(specialization)]
trait Handler<M> {
    fn handle(&self, m:M);
}

struct Struct<T>(T);
impl<T, M:std::fmt::Debug> Handler<M> for Struct<T> {
    default fn handle(&self, m : M) {
        println!("got something else: {:?}", m);
    }
}
impl<T> Handler<String> for Struct<T> {
    fn handle(&self, m : String) {
        println!("got a string={}", m);
    }
}
// impl<T> Handler<u32> for Struct<T> {
//     fn handle(&self, m : u32) {
//         println!("got a u32={}", m);
//     }
// }
fn main() {
    let s = Struct(());
    s.handle("hello".to_string());
    s.handle(5.0 as f64);
}

Meskipun tidak ada kode yang memanggil penangan untuk u32, spesialisasi yang tidak ada menyebabkan kode tidak dapat dikompilasi.

Sunting: ini tampaknya sama dengan masalah kedua ("Namun, ketika Anda menghapus implementasi untuk u32, tampaknya merusak deduksi sifat lainnya") yang disebutkan oleh Gladdy pada satu posting kembali.

Dengan rustc 1.35.0-nightly (3de010678 2019-04-11), kode berikut memberikan kesalahan:

#![feature(specialization)]
trait MyTrait<T> {
    fn print(&self, parameter: T);
}

struct Message;

impl<T> MyTrait<T> for Message {
    default fn print(&self, parameter: T) {}
}

impl MyTrait<u8> for Message {
    fn print(&self, parameter: u8) {}
}

fn main() {
    let message = Message;
    message.print(1_u16);
}

kesalahan:

error[E0308]: mismatched types
  --> src/main.rs:20:19
   |
18 |     message.print(1_u16);
   |                   ^^^^^ expected u8, found u16

Namun, kode mengkompilasi dan berfungsi ketika saya menghilangkan blok impl MyTrait<u8> :

#![feature(specialization)]
trait MyTrait<T> {
    fn print(&self, parameter: T);
}

struct Message;

impl<T> MyTrait<T> for Message {
    default fn print(&self, parameter: T) {}
}

/*
impl MyTrait<u8> for Message {
    fn print(&self, parameter: u8) {}
}
*/

fn main() {
    let message = Message;
    message.print(1_u16);
}

Apakah ini memang sengaja, apakah ini karena implementasinya tidak lengkap, atau apakah ini bug?

Juga, saya ingin tahu apakah kasus penggunaan untuk spesialisasi ini (menerapkan sifat dengan parameter jenis yang tumpang tindih untuk satu jenis beton sebagai lawan dari penerapan sifat yang sama untuk jenis yang tumpang tindih) akan didukung. Membaca bagian "Menentukan aturan prioritas" di RFC 1210, saya pikir itu akan didukung, tetapi RFC tidak memberikan contoh seperti itu dan saya tidak tahu apakah kami masih mengikuti RFC ini secara ketat.

Laporkan keanehan:

trait MyTrait {}
impl<E: std::error::Error> MyTrait for E {}

struct Foo {}
impl MyTrait for Foo {}  // OK

// But this one is conflicting with error message:
//
//   "... note: upstream crates may add new impl of trait `std::error::Error` for type
//    std::boxed::Box<(dyn std::error::Error + 'static)>` in future versions"
//
// impl MyTrait for Box<dyn std::error::Error> {}

Mengapa Box<dyn std::error::Error> aneh (hindari penggunaan kata "khusus") dalam kasus ini? Bahkan jika ini mengimplikasikan std::error::Error di masa depan, impl MyTrait for Box<dyn std::error::Error> masih merupakan spesialisasi yang valid dari impl<E: std::error::Error> MyTrait for E , bukan?

masih merupakan spesialisasi yang valid

Dalam kasus Anda impl<E: std::error::Error> MyTrait for E tidak dapat dikhususkan, karena tidak memiliki metode default .

@ bjorn3 Sepertinya ini akan bekerja, tetapi tidak meskipun Anda menambahkan metode tiruan

di peti bar

pub trait Bar {}
impl<B: Bar> Bar for Box<B> {}

Di peti foo

#![feature(specialization)]

use bar::*;

trait Trait {
    fn func(&self) {}
}

impl<E: Bar> Trait for E {
    default fn func(&self) {}
}

struct Foo;
impl Trait for Foo {}  // OK

impl Trait for Box<dyn Bar> {} // Error error[E0119]: conflicting implementations of trait

Perhatikan bahwa jika Anda mengubah peti bar menjadi

pub trait Bar {}
impl<B: ?Sized + Bar> Bar for Box<B> {}

Kemudian kompilasi peti foo .

@ bjorn3 Sepertinya kita tidak memerlukan metode default untuk mengkhususkannya ( taman bermain ).

@KrishnaSannasi Saya tidak dapat mereproduksi kesalahan "implementasi yang bertentangan" dalam contoh Anda ( taman bermain ).

Pembaruan: Oh, begitu. Sifat Bar harus dari peti hulu agar contoh dapat bekerja.

@updogliu contoh Anda tidak menampilkan spesialisasi karena Foo tidak mengimplementasikan Error .

Apakah saya memprogram terlambat malam ini, atau haruskah ini tidak menyebabkan tumpukan melimpah?

#![feature(specialization)]
use std::fmt::Debug;

trait Print {
    fn print(self);
}

default impl<T> Print for [T; 1] where T: Debug {
    fn print(self) {
        println!("{:?}", self);
    }
}

impl<T> Print for [T; 1] where T: Debug + Clone {
    fn print(self) {
        println!("{:?}", self.clone());
    }
}

fn main() {
    let x = [0u8];
    x.print();
}

Tautan taman bermain

Blok default impl berbutir kasar selalu melakukan hal yang sangat aneh bagi saya, saya sarankan untuk mencoba sintaks spesialisasi halus default fn sebagai gantinya.

EDIT: Setelah memeriksa ulang RFC, ini diharapkan, karena default impl sebenarnya _not_ berarti bahwa semua item di blok impl adalah default ed. Saya menemukan semantik itu mengejutkan untuk sedikitnya.

Tautan taman bermain

@ HadrienG2 Saya sebenarnya selalu menggunakan default fn dalam proyek ini tetapi kali ini saya lupa kata kunci default dan penyusun menyarankan untuk menambahkannya ke impl . Belum pernah melihat masalah rekursi tumpukan sebelumnya dan tidak yakin apakah hal itu diharapkan pada tahap ini. Terima kasih atas sarannya, default fn berfungsi dengan baik.

Melihat RFC asli, ada bagian tentang spesialisasi impl yang melekat. Apakah seseorang memberikan yang saya coba?

Pendekatan yang diusulkan di RFC mungkin tidak berfungsi secara langsung lagi, setidaknya, untuk metode const yang melekat:

// This compiles correctly today:
#![feature(specialization)] 
use std::marker::PhantomData;
struct Foo<T>(PhantomData<T>);
impl<T> Foo<T> {
    default const fn foo() -> Self { Self(PhantomData) }
    // ^^should't default here error?
}
// ----
// Adding this fails:
impl<T: Copy> Foo<T> {
    const fn foo() -> Self { Self(PhantomData) }
}

RFC asli mengusulkan mengangkat metode ke dalam suatu sifat, mengimplementasikannya untuk tipe, dan mengkhususkan impl. Saya kira bahwa untuk metode const fn, impls dari sifat untuk tipe tersebut harus menjadi impls const.

Bagi siapa pun yang menemukan ini dan ingin tahu tentang statusnya - ada beberapa kemajuan konseptual yang signifikan pada tahun 2018:
http://smallcultfollowing.com/babysteps/blog/2018/02/09/maximally-minimal-specialization-always-applicable-impls/
http://aturon.github.io/tech/2018/04/05/sound-specialization/

Baru-baru ini, bulan lalu @nikomatsakis menulis (sebagai contoh, dalam konteks lain; saya tebal) bahwa:

ada satu masalah utama [dalam spesialisasi] yang tidak pernah diselesaikan dengan memuaskan, masalah teknis seputar masa hidup dan sifat [...] Kemudian, [kedua pos yang ditautkan di atas]. Sepertinya ide-ide ini pada dasarnya telah menyelesaikan masalah , tetapi kami telah sibuk sementara itu dan belum punya waktu untuk menindaklanjuti.

Kedengarannya penuh harapan meskipun jelas masih ada pekerjaan yang harus dilakukan.

(Memposting ini karena saya menemukan utas ini beberapa minggu yang lalu dan tidak tahu tentang kemajuan tahun lalu, kemudian baru-baru ini menemukan posting itu secara tidak sengaja. Ada komentar di atas yang menyebutkannya, tetapi GitHub membuatnya semakin sulit untuk melihat yang lain kecuali yang pertama dan beberapa komentar terakhir di utas panjang: cry:. Mungkin berguna jika pembaruan ini masuk ke dalam deskripsi masalah.)

Halo semuanya! Bisakah seseorang memberi tahu saya mengapa kasus penggunaan ini tidak berfungsi? Bug atau perilaku yang diharapkan?

Seperti contoh ini. impl A for i32 tidak apa-apa, tetapi impl A for () tidak dapat dikompilasi dalam 1.39.0-nightly.

#![feature(specialization)]

trait A {
    fn a();
}

default impl <T: ToString> A for T {
    fn a() {}
}

impl A for i32 {
    fn a() {}
}

impl A for () {
    fn a() {}
}

menyusun pesan:

error[E0119]: conflicting implementations of trait `A` for type `()`:
  --> src/lib.rs:16:1
   |
8  | default impl <T: ToString> A for T {
   | ---------------------------------- first implementation here
...
16 | impl A for () {
   | ^^^^^^^^^^^^^ conflicting implementation for `()`
   |
   = note: upstream crates may add new impl of trait `std::fmt::Display` for type `()` in future versions

@Hexilee Taruh default pada metode, bukan impl.

@Krisnaini misalnya 2

@zserik ya, saya tahu. Saya rasa itu belum diterapkan, atau dibatalkan. Bagaimanapun itu tidak berfungsi sekarang.

Ini jelas tidak berfungsi sekarang, tapi saya pikir itu harus berhasil.

Saya menanyakan ini di sini, karena saya belum memperhatikan topik ini muncul di tempat lain - apakah ada rencana untuk default -ify berbagai fungsi pustaka standar, mirip dengan bagaimana kita memiliki const fungsi yang telah dianggap aman untuk melakukannya? Alasan utama saya bertanya adalah bahwa penerapan generik default From dan Into ( impl<T, U: From<T>> Into<U> for T dan impl<T> From<T> for T ) menyulitkan penulisan generik From komprehensif Into implementasi downstream dari core , dan alangkah baiknya jika saya dapat mengganti konversi tersebut di peti saya sendiri.

Bahkan jika kita mengizinkan spesialisasi untuk From / Into itu tidak akan membantu implik umum karena masalah kisi.

@Krishnaannasi Saya tidak percaya itu masalahnya. Misalnya, kode ini seharusnya berfungsi jika From dan Into dapat dikhususkan, tetapi tidak karena tidak:

impl<M: Into<[S; 2]>, S> From<M> for GLVec2<S> {
    fn from(to_array: M) -> GLVec2<S> {
        unimplemented!()
    }
}
impl<M, S> Into<M> for GLVec2<S>
where
    [S; 2]: Into<M>,
{
    fn into(self) -> M {
        unimplemented!()
    }
}

pub struct GLVec2<S> {
    pub x: S,
    pub y: S,
}

Itu berfungsi jika Anda mengonversi From dan Into menjadi sifat khusus yang tidak memiliki implementaitons umum: https://play.rust-lang.org/?version=stable&mode=debug&edition= 2018 & inti = cc126b016ff62643946aebc6bab88c98

@Osspial Nah, jika Anda mencoba dan mensimulasikan menggunakan impl default, Anda akan melihat masalah,

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=e5b9da0eeca05d063e2605135a0b5ead

Saya akan ulangi, mengubah From/Into impl menjadi impl default di perpustakaan standar tidak akan membuat impls umum untuk Into mungkin. (dan itu tidak mempengaruhi implikasi umum dari From )

Hai, ada bug serius dalam implementasi spesialisasi saat ini. Saya menandainya sebagai bug karena meskipun itu adalah keputusan desain yang eksplisit, hal itu mencegah kita menggunakan salah satu fitur spesialisasi yang paling kuat, yaitu kemungkinan pembuatan "tipe buram" (ini bukan nama formal). Pola ini adalah salah satu blok penyusun paling primitif dalam bahasa lain yang menyediakan kelas tipe, seperti Haskell atau Scala.

Pola ini sederhana - kita dapat mendefinisikan struktur seperti WithLabel atau WithID yang menambahkan beberapa bidang dan metode ke struktur yang mendasarinya, jadi misalnya jika kita membuat WithLabel<WithID<MyType>> maka kita akan dapat untuk mendapatkan id , label dan semua bidang / metode MyType juga. Sayangnya, dengan implementasi saat ini, hal tersebut tidak memungkinkan.

Di bawah ini adalah contoh kode yang menunjukkan penggunaan pola ini. Kode yang diberi komentar tidak dapat dikompilasi, sementara itu seharusnya membuat pola ini sangat berguna:

#![feature(specialization)]

use std::ops::Deref;
use std::ops::DerefMut;

// =================
// === WithLabel ===
// =================

struct WithLabel<T>(String, T);

pub trait HasLabel {
    fn label(&self) -> &String;
}

impl<T> HasLabel for WithLabel<T> {
    fn label(&self) -> &String { 
        &self.0
    }
}

// THIS SHOULD COMPILE, BUT GETS REJECTED
// impl<T> HasLabel for T
// where T: Deref, <Self as Deref>::Target : HasLabel {
//     default fn label(&self) -> &String { 
//         self.deref().label() 
//     }
// }

impl<T> Deref for WithLabel<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.1
    }
}

// ==============
// === WithID ===
// ==============

struct WithID<T>(i32, T);

pub trait HasID {
    fn id(&self) -> &i32;
}

impl<T> HasID for WithID<T> {
    fn id(&self) -> &i32 { 
        &self.0
    }
}

// THIS SHOULD COMPILE, BUT GETS REJECTED
// impl<T> HasID for T
// where T: Deref, <Self as Deref>::Target : HasID {
//     default fn id(&self) -> &i32 { 
//         self.deref().id() 
//     }
// }

impl<T> Deref for WithID<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.1
    }
}

// =============
// === Usage ===
// =============

struct A(i32);

type X = WithLabel<WithID<A>>;

fn test<T: HasID + HasLabel> (t: T) {
    println!("{:?}", t.label());
    println!("{:?}", t.id());
}

fn main() {
    let v1 = WithLabel("label1".to_string(), WithID(0, A(1)));
    // test(v1); // THIS IS EXAMPLE USE CASE WHICH DOES NOT COMPILE
}

Untuk membuat baris test(v1) berfungsi, kita perlu menambahkan sifat tersebut secara manual:

impl<T: HasID> HasID for WithLabel<T> {
    fn id(&self) -> &i32 { 
        self.deref().id()
    }
}

Tentu saja, untuk membuatnya lengkap, kita juga perlu membuat sifat ini berimplikasi:

impl<T: HasLabel> HasLabel for WithID<T> {
    fn label(&self) -> &String { 
        self.deref().label()
    }
}

Dan ini SANGAT BURUK . Hanya untuk 2 jenis itu sederhana. Namun, bayangkan kita memiliki 10 definisi jenis buram yang berbeda, yang menambahkan bidang berbeda, seperti WithID , WithLabel , WithCallback , ... sebutkan saja. Dengan perilaku spesialisasi saat ini, kita perlu mendefinisikan ... lebih dari 1000 implementasi sifat yang berbeda! Jika kode yang diberi komentar akan diterima, kita hanya membutuhkan 10 implementasi sifat dan mengimplementasikan setiap tipe baru hanya akan membutuhkan satu implementasi tambahan .

Saya tidak yakin bagaimana kode Anda berkaitan dengan spesialisasi. Argumen Anda (kode awal Anda terkompilasi tetapi baris test(v1); dikomentari tidak dapat dikompilasi tanpa manual impl yang Anda sajikan) masih berlaku jika baris #![feature(specialization)] pertama dihapus.

@qnighy Kode harus dikompilasi setelah menghapus komentar impls HasLabel for T dan HasID for T - mereka menggunakan spesialisasi. Saat ini, mereka ditolak (coba hapus komentar mereka di kode yang saya berikan!). Apakah sekarang masuk akal bagi Anda? 🙂

Mari kita pertimbangkan tiga contoh WithLabel<WithID<A>> , WithID<WithLabel<A>> dan WithLabel<WithLabel<A>> . Kemudian

  • impl pertama mencakup WithLabel<WithID<A>> dan WithLabel<WithLabel<A>> .
  • impl kedua mencakup WithID<WithLabel<A>> dan WithLabel<WithLabel<A>> .

Oleh karena itu, pasangan impls tidak memenuhi klausa berikut dari RFC :

Untuk memastikan spesialisasi koheren, kami akan memastikan bahwa untuk dua implik I dan J yang tumpang tindih, kita memiliki I < J atau J < I . Artinya, yang satu harus benar-benar lebih spesifik dari yang lain.

Dan ini adalah masalah nyata dalam kasus Anda juga karena HasLabel impl dari WithLabel<WithLabel<A>> dapat diartikan dalam dua cara.

Bagaimana kita bisa menutupi kasus ini sudah dibahas di RFC juga, dan kesimpulannya adalah:

Batasan yang dialamatkan oleh aturan kisi cukup sekunder untuk tujuan utama spesialisasi (seperti yang dijelaskan dalam Motivasi), dan karena itu, karena aturan kisi dapat ditambahkan nanti, RFC tetap menggunakan aturan rantai sederhana untuk saat ini.

@qnighy , terima kasih telah memikirkannya.

Dan ini adalah masalah nyata dalam kasus Anda juga karena implan HasLabel WithLabel<WithLabel<A>> dapat ditafsirkan dalam dua cara.

Ini benar jika kita tidak menganggap impl<T> HasLabel for WithLabel<T> lebih terspesialisasi daripada impl<T> HasLabel for T untuk masukan WithLabel<WithLabel<A>> . Bagian dari RFC yang Anda tempel memang mencakup itu, namun, saya percaya bahwa ini adalah batasan yang serius dan saya akan meminta untuk mempertimbangkan kembali dukungan untuk kasus penggunaan ini di rilis pertama ekstensi ini.

Sementara itu, saya sedang bermain dengan negative trait impls karena mereka mungkin benar-benar menyelesaikan poin yang Anda liput. Saya membuat kode yang tidak memiliki masalah yang Anda gambarkan (kecuali saya melewatkan sesuatu), namun, kode itu tetap tidak dapat dikompilasi. Kali ini, saya tidak mengerti dari mana asal kendala yang disebutkan dalam kesalahan, karena penyelesaiannya tidak boleh ambigu.

Hal baiknya adalah bahwa sebenarnya semuanya dikompilasi sekarang (termasuk spesialisasi) tetapi bukan penggunaan test(v1) :

#![feature(specialization)]
#![feature(optin_builtin_traits)]

use std::ops::Deref;
use std::ops::DerefMut;

// =================
// === WithLabel ===
// =================

struct WithLabel<T>(String, T);

auto trait IsNotWithLabel {}
impl<T> !IsNotWithLabel for WithLabel<T> {}

pub trait HasLabel {
    fn label(&self) -> &String;
}

impl<T> HasLabel for WithLabel<T> {
    fn label(&self) -> &String { 
        &self.0
    }
}

impl<T> HasLabel for T
where T: Deref + IsNotWithLabel, <Self as Deref>::Target : HasLabel {
    default fn label(&self) -> &String { 
        self.deref().label() 
    }
}

impl<T> Deref for WithLabel<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.1
    }
}

// ==============
// === WithID ===
// ==============

struct WithID<T>(i32, T);

pub trait HasID {
    fn id(&self) -> &i32;
}

impl<T> HasID for WithID<T> {
    fn id(&self) -> &i32 { 
        &self.0
    }
}

auto trait IsNotWithID {}
impl<T> !IsNotWithID for WithID<T> {}

impl<T> HasID for T
where T: Deref + IsNotWithID, <Self as Deref>::Target : HasID {
    default fn id(&self) -> &i32 { 
        self.deref().id() 
    }
}

impl<T> Deref for WithID<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.1
    }
}

// =============
// === Usage ===
// =============

struct A(i32);

type X = WithLabel<WithID<A>>;

fn test<T: HasID + HasLabel> (t: T) {
    println!("{:?}", t.label());
    println!("{:?}", t.id());
}

fn main() {
    let v1 = WithLabel("label1".to_string(), WithID(0, A(1)));
    test(v1);
}

Sementara itu, Anda dapat mengeksploitasi RFC1268 overlapping_marker_traits untuk memungkinkan tumpang tindih sifat non-penanda, tetapi peretasan ini membutuhkan tiga sifat lagi (satu untuk menelusuri ciri penanda, dua untuk memperoleh kembali data yang terhapus melalui spesialisasi).

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=b66ee0021db73efaaa5d46edfb4f3990

@qnighy Saya telah membuat masalah terpisah tentang bug ini: https://github.com/rust-lang/rust/issues/66041

Oke, saya baru saja menemukan bahwa auto traits tidak akan pernah menjadi solusi di sini, karena (menurut https://doc.rust-lang.org/nightly/unstable-book/language-features/optin-builtin-traits. html) mereka menyebar ke semua bidang dalam sebuah struct:

Sifat otomatis, seperti Kirim atau Sinkronkan di pustaka standar, adalah sifat penanda yang diterapkan secara otomatis untuk setiap jenis, kecuali jika jenis, atau jenis yang dikandungnya, telah secara eksplisit menyisih melalui impl negatif.

EDIT
@qnighy entah bagaimana saya lupa bahwa Anda memberikan tautan ke taman bermain. ❤️ Terima kasih banyak untuk itu. Ini berhasil dan saya kagum dengan betapa kerasnya solusi ini. Luar biasa bahwa kami dapat mengungkapkannya saat ini dan saya berharap kemungkinan ini tidak akan hilang di masa depan!

Dalam situasi seperti itu, overlapping marker traits adalah satu-satunya peretasan yang dapat kita gunakan sekarang, tetapi saya pikir akan lebih baik untuk mengizinkan di masa depan beberapa jenis solusi yang lebih mudah untuk mengekspresikan jenis buram (seperti yang dijelaskan di posting saya sebelumnya: https : //github.com/rust-lang/rust/issues/31844#issuecomment-549023367).

Contoh yang sangat sederhana (penyederhanaan contoh di atas ) yang gagal:

trait Trait<T> {}
impl<T> Trait<T> for T {}
impl<T> Trait<()> for T {}

Saya tidak percaya ini mengenai masalah yang diidentifikasi dengan aturan kisi , namun mungkin pemecah yang terlalu sederhana berpikir demikian?

Tanpa ini, implementasi saat ini tidak berguna untuk tujuan saya. Jika hal di atas diizinkan, maka saya yakin mungkin juga untuk mengimplementasikan From pada jenis pembungkus (meskipun saya tidak yakin tentang Into ).

Untuk semua orang yang belum tahu: ada trik luar biasa yang ditemukan oleh dtolnay yang memungkinkan untuk menggunakan spesialisasi (sangat terbatas) pada karat yang stabil

Saya tidak yakin apakah ini telah ditangani, tetapi ciri-ciri dengan implementasi default untuk metodenya harus didefinisikan ulang supaya mereka dapat ditandai sebagai default . Contoh;

trait Trait {
    fn test(&self) { println!("default implementation"); }
}

impl<T> Trait for T {
    // violates DRY principle
    default fn test(&self) { println!("default implementation"); }
}

Saya mengusulkan sintaks berikut untuk memperbaiki ini (jika perlu diperbaiki):

impl<T> Trait for T {
    // delegates to the already existing default implementation
    default fn test(&self);
}

Pindah ke # 68309

@jazzfool Harap isi ulang ini sebagai masalah (berlaku umum untuk semua orang yang menanyakan pertanyaan serupa di sini) dan cc saya untuk masalah itu.

Apakah ada pendekatan untuk menguji spesialisasi? Misalnya, saat menulis pengujian yang memeriksa kebenaran spesialisasi, Anda harus terlebih dahulu mengetahui apakah spesialisasi yang Anda coba uji benar-benar diterapkan, bukan implementasi default.

@ the8472 maksud Anda menguji kompiler , atau maksud Anda menguji dalam kode Anda sendiri? Anda tentu dapat menulis pengujian unit yang berperilaku berbeda (yaitu, memanggil fn, dan melihat apakah Anda mendapatkan varian khusus). Mungkin Anda mengatakan bahwa kedua varian itu setara, kecuali yang satu seharusnya lebih cepat, dan karenanya Anda tidak yakin bagaimana cara menguji versi mana yang Anda dapatkan? Kalau begitu, saya setuju, saya tidak tahu bagaimana Anda bisa mengujinya sekarang.

Anda bisa saya kira membuat beberapa sifat lain dengan set impls yang sama, tetapi di mana fns berperilaku berbeda, hanya untuk meyakinkan diri sendiri.

Mungkin Anda mengatakan bahwa kedua varian itu setara, kecuali yang satu seharusnya lebih cepat, dan karenanya Anda tidak yakin bagaimana cara menguji versi mana yang Anda dapatkan? Kalau begitu, saya setuju, saya tidak tahu bagaimana Anda bisa mengujinya sekarang.

Anda bisa mengujinya menggunakan makro. Saya agak berkarat dengan Rust saya, tetapi sesuatu seperti ini ...

[#cfg(test)]
static mut SPECIALIZATION_TRIGGERED : bool = false;

[#cfg(test)]
macro_rules! specialization_trigger {
    () =>  { SPECIALIZATION_TRIGGERED = true; };
}

[#cfg(not(test))]
macro_rules! specialization_trigger {
    () => {};
}

Kemudian gunakan specialization_trigger!() dalam impl khusus, dan dalam pengujian gunakan assert!(SPECIALIZATION_TRIGGERED);

[#cfg(test)]
static mut SPECIALIZATION_TRIGGERED : bool = false;
...

Anda akan ingin menggunakan thread_local! { static VAR: Cell<bool> = Cell::new(false); } daripada static mut karena jika tidak, variabel dapat disetel dalam satu thread kasus uji dan keliru membaca dari thread lain. Juga, ingatlah untuk mengatur ulang variabel di awal setiap pengujian, jika tidak, Anda akan mendapatkan true dari pengujian sebelumnya.

Saya punya pertanyaan tentang teks RFC, semoga ini adalah tempat yang bagus untuk bertanya.

Di bagian penggunaan kembali , contoh ini diberikan:

trait Add<Rhs=Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
    fn add_assign(&mut self, rhs: Rhs);
}

// the `default` qualifier here means (1) not all items are implied
// and (2) those that are can be further specialized
default impl<T: Clone, Rhs> Add<Rhs> for T {
    fn add_assign(&mut self, rhs: Rhs) {
        let tmp = self.clone() + rhs;
        *self = tmp;
    }
}

Saya bertanya-tanya bagaimana ini seharusnya memeriksa-ketik, mengingat bahwa tmp memiliki tipe Self::Output dan tidak ada yang diketahui tentang jenis terkait ini. Teks RFC tampaknya tidak menjelaskan hal itu, setidaknya tidak mendekati tempat contoh diberikan.

Adakah mekanisme di sini yang seharusnya / seharusnya membuat itu berhasil?

Mungkinkah default itu dibatasi where T: Add<Output = T> ? Atau apakah itu lingkaran kausalitas?

@RalfJung Saya setuju bahwa sepertinya salah.

Saya memiliki pertanyaan tentang prosedur: seberapa berartinya masalah ini dan seberapa berarti bagi orang-orang yang mencoba fitur ini? Seperti yang saya pahami, implementasi saat ini tidak sehat dan tidak lengkap dan kemungkinan besar akan digantikan sepenuhnya oleh kapur atau sesuatu yang lain. Jika itu benar, haruskah kita membatalkan penerapan fitur ini dan yang lainnya (misalnya GAT) sampai mereka dapat dilakukan ulang dengan benar?

Harap jangan membatalkan penerapan. Rusak, tidak sehat dan tidak lengkap masih memungkinkan eksperimen.

Jika itu benar, haruskah kita membatalkan penerapan fitur ini dan yang lainnya (misalnya GAT) sampai mereka dapat dilakukan ulang dengan benar?

Mohon jangan, PyO3 (pustaka pengikatan Python) saat ini bergantung pada spesialisasi. Lihat https://github.com/PyO3/pyo3/issues/210

Bukankah jumlah yang adil dari std bergantung padanya? Saya pikir saya ingat melihat banyak implementasi internal khusus untuk hal-hal yang berhubungan dengan vektor & string. Bukan berarti itu mencegah de-implementasi, hanya saja itu tidak akan sesederhana menghapus bagian yang relevan dari pemeriksa tipe.

@Lucretiel ya, banyak pengoptimalan yang berguna (terutama di sekitar iterator) bergantung pada spesialisasi, jadi akan menjadi regresi kinerja yang sangat besar untuk mengimplementasikannya.

Misalnya, FusedIterator dan TrustedLen tidak berguna tanpa spesialisasi.

PyO3 (pustaka pengikatan Python) saat ini bergantung pada spesialisasi

Itu menakutkan, karena bagian yang "tidak sehat". Pustaka standar memiliki bug kesehatan yang kritis karena penggunaan spesialisasi yang salah. Seberapa yakin Anda bahwa Anda tidak memiliki bug yang sama? Coba gunakan min_specialization sebagai gantinya, mudah-mudahan tidak terlalu bermasalah.

Mungkin specialization harus mendapatkan peringatan yang mirip dengan const_generics mengatakan "fitur ini tidak lengkap, tidak bagus dan rusak, tidak digunakan dalam produksi ".

banyak pengoptimalan yang berguna (terutama di sekitar iterator) bergantung pada spesialisasi, jadi akan menjadi regresi kinerja yang sangat besar untuk mengimplementasikannya.

Saat ini mereka bergantung pada min_specialization (lihat misalnya https://github.com/rust-lang/rust/pull/71321), yang memiliki lubang kesehatan terbesar yang terpasang.

@nikomat

Saya setuju itu sepertinya salah.

Tahu apa kode yang dimaksud? Saya pertama kali mengira default impl juga dimaksudkan untuk menetapkan type Output = Self; , tetapi sebenarnya itu tidak mungkin dalam RFC yang diusulkan . Jadi mungkin niatnya adalah memiliki ikatan Output = T ?

@RalfJung Adakah kemungkinan min_specialization dapat didokumentasikan? Saya merasa lebih berisiko menggunakan fitur yang benar-benar tidak terdokumentasi pada peti daripada yang diketahui (dan mungkin tidak diketahui) bug soundnessnya. Tidak ada yang bagus, tapi setidaknya yang terakhir bukan hanya internal compiler.

Saya tidak dapat menemukan min_specialization dari masalah pelacakan ini di luar PR # 71321 - dan menurut buku Tidak Stabil, ini adalah masalah pelacakan untuk fitur tersebut.

Saya juga tidak tahu banyak tentang fitur itu, saya baru saja melihat perbaikan kesehatan libstd. Itu diperkenalkan di https://github.com/rust-lang/rust/pull/68970 yang menjelaskan beberapa hal lagi tentangnya.

@ Matthewjasper apakah masuk akal untuk mendokumentasikan ini sedikit lebih banyak dan meminta pengguna nightly feature(specialization) untuk bermigrasi?

Sepertinya setidaknya harus ada peringatan. Sepertinya fitur ini rusak secara mencolok dan berbahaya untuk digunakan dalam kondisi saat ini.

Saya pikir specialization bisa menjadi sinonim untuk min_specialization , tetapi tambahkan fitur unsound_specialization jika perlu untuk proyek yang sudah ada, seperti PyO3 atau apa pun. Ini akan menyelamatkan siapa saja yang hanya menggunakan min_specialization usaha yang cukup besar, tetapi siapa pun yang mendapatkan pesan kesalahan, dan dapat mencari di sini nama baru.

@Ralfian

Tahu apa kode yang dimaksud?

Nah, pada titik tertentu, kami telah mempertimbangkan mode di mana default dapat bergantung satu sama lain. Jadi saya membayangkan pada saat itu hal berikut akan berhasil:

default impl<T: Clone, Rhs> Add<Rhs> for T {
    type Output = T;

    fn add_assign(&mut self, rhs: Rhs) {
        let tmp = self.clone() + rhs;
        *self = tmp;
    }
}

Peringatan adalah bahwa jika Anda menimpa setiap anggota impl , maka Anda harus menimpa mereka semua. Kami kemudian mundur dari ide ini, dan kemudian memulai berbagai iterasi, seperti "grup default" (yang juga akan berfungsi di sini), dan pada akhirnya tidak mengadopsi solusi apa pun karena kami pikir kami dapat melakukannya nanti setelah kami menangani masalah lain, er, menekan (cc # 71420).

Mohon jangan, PyO3 (pustaka pengikatan Python) saat ini bergantung pada spesialisasi. Lihat PyO3 / pyo3 # 210

Pemelihara PyO3 di sini - kami mendukung perpindahan dari spesialisasi sehingga kami dapat menggunakan Rust yang stabil. Apakah min_specialization kemungkinan besar akan distabilkan sebelum spesialisasi lainnya selesai?

Saya pikir ada beberapa diskusi tentang mencoba menstabilkan min_specialization dalam pertemuan desain lang perencanaan edisi 2021 (ada di youtube; maaf, saya di ponsel saya, atau saya akan mencoba mencari tautan). Saya lupa apa yang mereka katakan tentang itu

Saya pikir ada beberapa diskusi tentang mencoba menstabilkan min_specialization dalam pertemuan desain lang perencanaan edisi 2021 (ada di youtube; maaf, saya di ponsel saya, atau saya akan mencoba mencari tautan). Saya lupa apa yang mereka katakan tentang itu

Saya pikir ini adalah tautan YouTube yang benar: https://youtu.be/uDbs_1LXqus
(juga di ponsel saya)

Ya, itu dia. Berikut ini tautan ke diskusi khusus: https://youtu.be/uDbs_1LXqus?t=2073

Saya telah menggunakan #[min_specialization] di perpustakaan eksperimental yang telah saya kembangkan jadi saya pikir saya akan berbagi pengalaman saya. Tujuannya adalah menggunakan spesialisasi dalam bentuk yang paling sederhana: untuk memiliki beberapa kasus sempit dengan implementasi yang lebih cepat daripada kasus umum. Secara khusus, agar algoritme kriptografi dalam kasus umum berjalan dalam waktu yang konstan tetapi kemudian jika semua masukan ditandai Public agar memiliki versi khusus yang berjalan dalam waktu variabel yang lebih cepat (karena jika bersifat publik, kami tidak peduli tentang membocorkan info tentang mereka melalui waktu eksekusi). Selain itu, beberapa algoritme lebih cepat tergantung pada apakah titik kurva eliptik dinormalisasi atau tidak. Untuk membuat ini bekerja, kita mulai

#![feature(rustc_attrs, min_specialization)]

Kemudian jika Anda perlu membuat sifat _specialization predicate_ seperti yang dijelaskan dalam spesialisasi minimal maksimal Anda menandai deklarasi sifat dengan #[rustc_specialization_trait] .

Semua spesialisasi saya dilakukan dalam file ini dan berikut adalah contoh sifat predikat spesialisasi.

Fitur ini berfungsi dan melakukan apa yang saya butuhkan. Ini jelas menggunakan penanda internal rustc dan oleh karena itu rentan pecah tanpa peringatan.

Satu-satunya umpan balik negatif adalah saya merasa kata kunci default masuk akal. Pada dasarnya apa yang dimaksud dengan default sekarang adalah: "impl ini dapat dikhususkan jadi tafsirkan impls yang mencakup subset dari yang satu ini sebagai spesialisasi darinya daripada impl yang bertentangan". Masalahnya adalah ini mengarah ke kode yang tampak sangat aneh:

https://github.com/LLFourn/secp256kfun/blob/6766b60c02c99ca24f816801fe876fed79643c3a/secp256kfun/src/op.rs#L196 -L206

Di sini impl kedua mengkhususkan diri pada yang pertama tetapi juga default . Arti dari default sepertinya hilang. Jika Anda melihat impls lainnya, cukup sulit untuk mengetahui impls mana yang mengkhususkan diri. Selain itu, ketika saya membuat implan yang salah yang tumpang tindih dengan implan yang sudah ada, sering kali sulit untuk mengetahui di mana kesalahan saya.

Menurut saya ini akan lebih sederhana jika semuanya dapat dikhususkan dan ketika Anda mengkhususkan sesuatu, Anda menyatakan dengan tepat yang berarti Anda mengkhususkan. Mengubah contoh di RFC menjadi apa yang ada dalam pikiran saya:

impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
{
    // no need for default
    fn extend(&mut self, iterable: T) {
        ...
    }
}

// We declare explicitly which impl we are specializing repeating all type bounds etc
specialize impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
    // And then we declare explicitly how we are making this impl narrower with ‘when’.
    // i.e. This impl is like the first except replace all occurances of ‘T’ with ‘&'a [A]’
    when<'a> T = &'a [A]
{
    fn extend(&mut self, iterable: &'a [A]) {
        ...
    }
}

Terima kasih untuk umpan baliknya.

Komentar saya di sini , khususnya item 6, memberikan kasus konkret di pustaka standar di mana mungkin diinginkan untuk memiliki spesialisasi yang hanya dapat ditimpa sebagian: IndexSet akan membutuhkan tipe Output karena IndexSet dapat diimplementasikan tanpa Index , tetapi kami mungkin tidak ingin membiarkan kedua tipe tersebut berdampingan dengan tipe Output berbeda. Karena IndexSet dapat memiliki implementasi default dalam IndexMut , maka akan masuk akal untuk mengizinkan spesialisasi metode index_set tanpa mengizinkan spesialisasi Output .

Saya mengalami kesulitan dengan video, jadi saya tidak bisa mencari video tertaut, namun, saya punya satu pertanyaan tentang #[min_specialization] . Apa adanya, ada atribut rustc_unsafe_specialization_marker pada sifat-sifat seperti FusedIterator yang memberikan petunjuk pengoptimalan, sehingga dapat dikhususkan. @matthewjasper menulis:

Ini tidak masuk akal tetapi kami mengizinkannya dalam jangka pendek karena tidak dapat menyebabkan penggunaan setelah pembebasan dengan kode yang murni aman dengan cara yang sama seperti mengkhususkan pada metode sifat.

Saya berasumsi bahwa rencananya adalah untuk mengimplementasikan proposal @aturon dan menambahkan modalitas spesialisasi untuk sifat-sifat seperti ini ( where specialize(T: FusedIterator) ). Tetapi saat ini, tampaknya kode apa pun dapat mengkhususkan diri pada sifat-sifat ini . Jika distabilkan apa adanya, orang dapat menulis spesialisasi stabil yang bergantung padanya, yang berarti bahwa ketidakseimbangan ini akan distabilkan.

Haruskah spesialisasi pada ciri-ciri ini juga dibatasi pada perpustakaan standar? Apakah perpustakaan standar memperoleh manfaat yang cukup karena dapat mengkhususkan diri pada perpustakaan tersebut?

Jika distabilkan apa adanya, orang dapat menulis spesialisasi stabil yang bergantung padanya, yang berarti bahwa ketidakseimbangan ini akan distabilkan.

Menurut pemahaman saya bahwa min_specialization apa adanya tidak dimaksudkan untuk stabilisasi.

Saya ingin yang kedua memiliki semacam penanda tentang spesialisasi impl. Ada beberapa kasus kode di rustc dan pustaka standar tidak berfungsi sebagaimana mestinya karena tidak ada cara untuk mengetahui bahwa spesialisasi sebenarnya terjadi:

Spesialisasi yang tidak perlu dari Copy :
https://github.com/rust-lang/rust/pull/72707/files#diff -3afa644e1d09503658d661130df65f59L1955

Sebuah "Spesialisasi" yang bukan:
https://github.com/rust-lang/rust/pull/71321/files#diff -da456bd3af6d94a9693e625ff7303113L1589

Implementasi yang dihasilkan oleh makro kecuali sebuah flag dilewatkan yang menggantikan impl default:
https://github.com/rust-lang/rust/pull/73851/files?file-filters%5B%5D=#diff -ebb36dd2ac01b28a3fff54a1382527ddR124

@matthewjasper tautan terakhir tampaknya tidak tertaut ke cuplikan tertentu.

Saya tidak yakin apakah ini adalah tujuan eksplisit, tetapi AIUI, fakta bahwa mengkhususkan impls tidak ditandai memberi Anda cara untuk menghindari kerusakan perubahan pada blanket impls. default impl<T> Trait for T tidak bertentangan dengan implikasi hilir - mereka hanya menjadi spesialis.

Mungkinkah ini peringatan hanya jika tidak ditandai?

Ada beberapa kasus kode di rustc dan pustaka standar tidak berfungsi sebagaimana mestinya karena tidak ada cara untuk mengetahui bahwa spesialisasi sebenarnya terjadi

Pengalaman saya dengan java serupa (meski tidak persis sama). Sulit untuk mengetahui subclass mana dari kelas yang benar-benar berjalan ...

Kami juga ingin beberapa penanda pada implan yang dapat dikhususkan, juga untuk kejelasan saat membaca, bukan?

Kita bisa meletakkan penanda di kedua tempat, yang kemudian memperbaiki kesalahan karat atau pesan peringatan karena sekarang mereka tahu apakah spesialisasi diinginkan dan dapat menunjuk ke tempat lain jika ada.

Jika peti hulu menambahkan impl, selain dari sekadar meningkatkan, peti hilir dapat menggunakan trik yang memungkinkan kompilasi terhadap versi baru dan lama, tidak yakin itu menguntungkan.

Saya pikir perbedaannya mungkin terlalu besar untuk menunjukkan perubahan. Ini mengarah ke ini: https://github.com/rust-lang/rust/blob/fb818d4321dee29e1938c002c1ff79b0e7eaadff/src/librustc_span/def_id.rs#L124

Re: Blanket menyiratkan, mereka tetap melanggar perubahan:

  • Mereka mungkin tumpang tindih sebagian dengan impl downstream, yang tidak diizinkan
  • Koherensi dapat mengasumsikan tidak adanya mereka dengan cara yang lebih halus (itulah sebabnya impls reserverasi ditambahkan secara internal)
  • Implikasi spesialisasi harus selalu dapat diterapkan, yang berarti:

    • Kami melanggar impls orang (apa yang dilakukan min_specialization ).

    • Kami meminta mereka untuk entah bagaimana menjelaskan batas sifat mereka sebagai selalu dapat diterapkan jika diperlukan.

    • Kami membuat perubahan yang selalu berlaku untuk mereka secara implisit dan berpotensi memunculkan bug runtime halus ketika impl default sekarang berlaku.

@cuviper sebenarnya, saya merasa masih ada kasus tepi di sekitar menambahkan impls selimut baru, bahkan dengan spesialisasi. Saya ingat saya mencoba mencari tahu apa yang diperlukan untuk mengizinkan kami menambahkan impl<T: Copy> Clone for T { } imp Saya menulis posting blog ini tentang hal itu , bagaimanapun juga ... tetapi saya tidak dapat mengingat sekarang apa kesimpulan saya .

Terlepas dari itu, kami dapat menjadikannya peringatan lint untuk tidak memiliki anotasi #[override] .

Yang mengatakan, jika kita bisa meminta pengguna menyatakan impls mana yang mereka mengkhususkan (tidak tahu bagaimana kita akan melakukannya), itu akan menyederhanakan beberapa hal. Saat ini kompilator harus menyimpulkan hubungan antara impls dan itu selalu agak rumit.

Salah satu hal tertunda yang harus kita lakukan dalam proyek kapur adalah mencoba dan kembali dan menjelaskan bagaimana spesialisasi harus diungkapkan di sana.

Ada beberapa kasus kode di rustc dan pustaka standar tidak berfungsi sebagaimana mestinya karena tidak ada cara untuk mengetahui bahwa spesialisasi sebenarnya terjadi

Pengalaman saya dengan java serupa (meski tidak persis sama). Sulit untuk mengetahui subclass mana dari kelas yang benar-benar berjalan ...

Kembali pada bulan Mei, saya mengusulkan alternatif untuk spesialisasi pada IRLO yang tidak benar-benar bergantung pada impl yang tumpang tindih, melainkan memungkinkan satu impl ke where match pada parameter tipenya:

impl<R, T> AddAssign<R> for T {
    fn add_assign(&mut self, rhs: R) where match T {
        T: AddAssignSpec<R> => self.add_assign(rhs),
        T: Add<R> + Copy => *self = *self + rhs,
        T: Add<R> + Clone => { let tmp = self.clone() + rhs; *self = tmp; }
    }
}

Crates downstream kemudian dapat menggunakan impl untuk mengimplementasikan "spesialisasi", karena menurut konvensi impl untuk sifat Trait pertama-tama akan cocok dengan jenis yang menerapkan ciri lain TraitSpec , dan jenis downstream akan dapat menerapkan sifat itu untuk menimpa perilaku umum:

// Crate upstream
pub trait Foo { fn foo(); }
pub trait FooSpec { fn foo(); }

impl<T> Foo for T {
    fn foo() where T {
        T : FooSpec => T::foo(),
        _ => { println!("generic implementation") }
    }
}

fn foo<T : Foo>(t: T) {
    T::foo()
}

// crate downstream
struct A {}
struct B {}

impl upstream::FooSpec for A {
    fn foo() { println!("Specialized"); }
}

fn main() {
    upstream::foo(A); // prints "specialized"
    upstream::foo(B); // prints "generic"
}

Formulasi ini memberikan lebih banyak kontrol ke upstream untuk memilih urutan impls yang berlaku, dan karena ini adalah bagian dari ciri / fungsi tanda tangan, ini akan muncul dalam dokumentasi. IMO ini mencegah "impl chasing" untuk mengetahui cabang mana yang sebenarnya dapat diterapkan, karena urutan penyelesaiannya eksplisit.

Ini mungkin dapat membuat kesalahan di sekitar masa hidup dan persamaan jenis lebih jelas karena hanya upstream yang dapat memenuhinya saat menerapkan spesialisasi (karena downstream hanya menerapkan "sifat spesialisasi".

Kekurangan dari formulasi ini adalah bahwa ini adalah rute yang sangat berbeda dari yang ada di RFC, dan telah diterapkan sejak 2016, dan bahwa setidaknya beberapa orang di thread tersebut menyatakan kekhawatiran bahwa itu tidak akan seekspresif dan / atau seintuitif saat ini. fitur spesialisasi (Saya menemukan "pencocokan pada jenis" cukup intuitif, tetapi saya bias saat saya mengusulkan formulasi).

Sintaks pertandingan mungkin memiliki manfaat (sintaksis) lain: Jika di beberapa titik di masa depan diperpanjang dengan penjaga pertandingan yang dievaluasi secara konstan maka seseorang tidak perlu melakukan senam jenis untuk mengekspresikan batas yang bergantung pada ekspresi konst. Misalnya seseorang dapat menerapkan spesialisasi berdasarkan size_of , align_of , needs_drop atau ukuran array.

@dureuill terima kasih atas infonya! Itu memang ide yang menarik. Satu kekhawatiran yang saya miliki adalah bahwa hal itu tidak serta merta menyelesaikan beberapa kasus penggunaan yang diantisipasi lainnya untuk spesialisasi, terutama kasus "perilaku perbaikan bertahap" seperti yang dijelaskan oleh posting blog ini . Tetap saja, perlu diingat.

@dureuill Idenya memang menarik dan mungkin memiliki banyak potensi, tetapi alternatif tidak selalu pertukaran yang setara.
Alasan saya tidak berpikir demikian, adalah karena seseorang tidak diberi kesempatan untuk sepenuhnya menggantikan implementasi yang lebih umum. Juga masalah lain mungkin adalah fakta bahwa kami sebenarnya tidak memiliki dukungan untuk semua fitur yang ada dalam sintaks where RFC yang menjadi tempat bergantung saran Anda.
Sarannya menarik, jadi mungkin itu bisa memiliki RFC sendiri sebagai fitur terpisah daripada pesaing untuk spesialisasi karena keduanya akan berguna dan saya tidak melihat alasan mengapa mereka tidak bisa hidup bersama.

@ the8472 @nikomatsakis , @ Dark-Legion: Terima kasih atas tanggapan positifnya! Saya mencoba menjawab beberapa komentar Anda di utas IRLO , karena saya tidak ingin terlalu berisik tentang masalah pelacakan (maaf untuk Anda masing-masing yang mengharapkan berita tentang spesialisasi dan baru saja menemukan ocehan saya: memerah :).

Saya dapat membuka RFC terpisah jika saya berhasil menulis sesuatu yang dapat diterbitkan. Sementara itu, saya sangat terbuka untuk umpan balik tentang utas IRLO yang ditautkan. Saya menambahkan contoh yang lebih panjang dari posting blog aturon, jadi silakan mengomentarinya!

Saya juga mendukung memiliki semacam penanda tentang impls yang mengkhususkan diri.

Pendekatan Edisi 2021, yang memungkinkan kami untuk memesan kata kunci lainnya (seperti specialize ). Melihat kompleksitas dan sejarah fitur ini, saya rasa fitur ini tidak akan stabil sebelum rilis Edisi 2021 (jangan ragu untuk membuktikan bahwa saya salah) yang berarti - menurut saya - bermain-main dengan (a) kata kunci baru ) masuk akal.

Jika tidak, satu-satunya kata kunci yang terlihat ... baik .. cocok sebagai penanda, mungkin super ?

Ringkasan dengan menggunakan kembali contoh @LLFourn dari https://github.com/rust-lang/rust/issues/31844#issuecomment -639977601:

  • super (sudah dipesan, tetapi bisa juga disalahartikan sebagai alternatif dari default )
super impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
  • specialize
specialize impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
  • spec (kependekan dari specialize seperti impl adalah untuk implement ) ( perhatian yang valid diajukan oleh @ssokolow di https://github.com/rust-lang / rust / issues / 31844 # Issuecomment-690980762)
spec impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
  • override (sudah dipesan, terima kasih @ the8472 https://github.com/rust-lang/rust/issues/31844#issuecomment-691042082)
override impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>

kata kunci yang sudah dipesan dapat ditemukan di sini

atau spec (kependekan dari specialize seperti impl adalah untuk implement )

"spesifikasi" sudah lebih dikenal oleh orang-orang sebagai singkatan dari "spesifikasi" (mis. "Spesifikasi HTML 5") jadi menurut saya itu bukan singkatan yang baik untuk "mengkhususkan".

override adalah kata kunci yang telah dipesan, saya asumsikan dimaksudkan untuk fungsi, jadi mungkin dapat digunakan untuk blok impl.

specialize Juga tergantung pada lokal - sebagai orang Australia, spesialisasinya untuk saya, jadi menggunakan 'spec' menghilangkan ambiguitas lokal.

specialize Juga tergantung pada lokal - sebagai orang Australia, spesialisasinya untuk saya, jadi menggunakan 'spec' menghilangkan ambiguitas lokal.

yang berfungsi, kecuali untuk 'spesifikasi' itu adalah singkatan umum untuk spesifikasi, jadi saya pikir menggunakan 'spesifikasi' untuk mengartikan spesialisasi akan membingungkan. Meskipun kata-kata tersebut dieja secara berbeda di Australia, semua orang masih dapat memahami kata mana yang dimaksudkan jika dieja dengan 'z' atau an 's'.

Sebagai orang Kanada, saya harus mengatakan bahwa mengkhususkan / mengkhususkan bukanlah satu-satunya kata yang digunakan dalam pemrograman yang bervariasi dengan lokal.

Di sini, kami menggunakan "warna", tetapi selalu membuat saya tersandung pada kesempatan langka ketika bahasa pemrograman atau perpustakaan menggunakan itu, bukan "warna". Baik atau buruk, bahasa Inggris Amerika adalah standar de facto dalam desain API dan, dengan kata-kata seperti color / color, memilih untuk memilih salah satu ejaan daripada yang lain tidak dapat dihindari tanpa dibuat-buat.

Mengingat seberapa kuat saya mengharapkan "spec" berarti "spesifikasi", saya pikir ini adalah situasi lain di mana kita harus mempertimbangkan ejaan bahasa Inggris Amerika sebagai opsi yang paling buruk.

Ini mungkin defacto, tetapi itu tidak berarti tidak apa-apa untuk menggunakannya. Saya menemukan diri saya melakukan impor seperti "gunakan warna sebagai warna" misalnya. Saya selalu tersandung pada s vs z juga. Saya pikir mengingat sikap positif Rust terhadap inklusivitas dan aksesibilitas, masuk akal untuk memilih istilah bahasa yang tidak bergantung pada lokal, karena frustrasi pengguna kecil seperti warna / warna dan s / z memang menumpuk.

Saya setuju pada prinsipnya. Saya hanya skeptis bahwa, untuk kasus ini, ada pilihan lokal-netral yang tidak menyebabkan lebih banyak masalah daripada memecahkannya.

Sebagai penutur asli non-Inggris, saya merasa agak lucu bahwa penutur asli bahasa Inggris akan mengeluh tentang tambahan u sebagai penghalang untuk inklusivitas. Bayangkan saja bagaimana jadinya jika semuanya tidak dieja sedikit aneh, tetapi ditulis dalam bahasa yang sepenuhnya berbeda.

Dengan kata lain: setiap istilah yang digunakan di Rust bergantung pada lokal.

Baik atau buruk, hal-hal dalam Rust dieja dalam bahasa Inggris AS. Bagi banyak orang di sini, ini berarti bekerja dalam bahasa kedua atau ketiga mereka; bagi orang lain itu berarti harus menyesuaikan ejaan sedikit. Inilah yang diperlukan untuk membuat banyak orang bekerja sama. Saya pikir manfaat mencoba memilih kata-kata yang dieja sama di banyak varian bahasa Inggris adalah marjinal dibandingkan dengan memilih istilah yang tidak ambigu - dan spec ambigu seperti yang ditunjukkan di atas.

gunakan special sebagai kata kunci?

Alternatifnya, buat dua kata kunci: specialize dan specialise dan jadikan setara ...

(Atau Anda orang non-Amerika yang lucu bisa belajar mengeja dengan benar: kami: 😂)

Saya tidak dapat berbicara tentang apa yang dilakukan kebanyakan bahasa, tetapi CSS menggunakan ejaan bahasa Inggris Amerika untuk segala sesuatu yang baru. Anekdot bahasa Inggris Amerika tampaknya lebih sering digunakan dalam pemrograman juga.

@ mark-im Sayangnya, itu adalah lereng licin yang mengarah ke argumen bahwa Rust harus memiliki kumpulan kata kunci alternatif dalam setiap bahasa utama yang mungkin berasal dari pelajar.

Ini juga tidak perlu memperumit bahasa untuk memiliki banyak sinonim untuk kata kunci karena orang dan parser hanya terbiasa dengan gagasan bahwa spasi dapat bervariasi seperti itu.

(Belum lagi bahwa itu berpotensi memicu dorongan untuk sinonim yang setara di perpustakaan, yang kemudian akan memerlukan desain dan implementasi rustdoc pekerjaan agar mereka tidak menjadi negatif bersih.)

Daripada berdebat tentang dialek bahasa Inggris mana yang kita inginkan untuk pengenal ini, mungkin kita bisa berkompromi dan menaruhnya dalam bahasa Ibrani ?

@ssokolow sementara argumen lereng licin secara umum bukanlah argumen yang kuat untuk digunakan, saya setuju dengan Anda dalam kasus ini. Orang dapat berargumen bahwa banyak bahasa baik-baik saja tetapi setidaknya ada dua alasan mengapa tidak:

  • Beberapa kata dalam bahasa yang berbeda terlihat sama tetapi memiliki arti yang berbeda (tidak dapat muncul dengan contoh yang berhubungan dengan pemrograman sekarang, tetapi contoh acak: a dalam bahasa Slowakia adalah and dalam bahasa Inggris)
  • Orang akan sangat kesulitan membaca kode dalam bahasa lain meskipun mereka tahu bahasanya . Saya tahu dari pengalaman. (Singkat cerita: Saya mengalami kesulitan besar dalam memahami beberapa teks dengan istilah yang diterjemahkan langsung dari bahasa Inggris ke "bahasa ibu" saya di penipuan pendidikan universitas .)

Sekarang bekerja mundur, mengapa dialek bahasa Inggris yang berbeda harus dipilih jika bukan bahasa lain? Saya tidak mengerti apa-apa. Konsistensi (semuanya dalam bahasa Inggris AS) tampaknya paling sederhana, paling mudah dipahami, dan paling tidak rawan kesalahan.

Semua ini dikatakan, saya akan sangat senang dengan "maksud Anda XXX?" pendekatan pesan kesalahan. Kata-kata netral yang tidak memiliki masalah lain juga boleh.

Setidaknya tidak ada yang perlu membahas sepakbola dengan kode. ;)

Sekitar 70% penutur asli bahasa Inggris tinggal di negara yang menggunakan ejaan AS.

Juga..

"Ejaan -ize sering salah dilihat sebagai Amerikanisme di Inggris. Ini telah digunakan sejak abad ke-15, mendahului -ise lebih dari satu abad. -Ize berasal langsung dari bahasa Yunani -ιζειν -izein dan Latin -izāre, sementara - ise datang melalui -iser. Oxford English Dictionary (OED) merekomendasikan -ize dan mencantumkan -ise form sebagai alternatif. "

"Publikasi oleh Oxford University Press (OUP) —seperti A Dictionary of Modern English Usage, Hart's Rules, dan The Oxford Guide to English Usage dari Henry Watson Fowler — juga merekomendasikan -ize. Namun, Modern English Usage karya Robert Allan's Pocket Fowler mempertimbangkan salah satu ejaan agar dapat diterima di mana saja kecuali di AS. "

ref. https://en.wikipedia.org/wiki/American_and_British_English_spelling_differences# -ise, _- ize _ (- isation, _- ization)

Tampaknya Spanyol dan Italia memiliki az atau dua, jadi tidak yakin dari mana Prancis mendapatkan -iser, mungkin dari Jerman?

Dapatkah bikeshedding lebih lanjut seputar kata kunci dan penamaan tertentu dipindahkan ke utas internal ? Saya mengikuti masalah ini untuk pembaruan kemajuan fitur, dan diskusi ini mulai menjadi sedikit berisik.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat