Saya belajar / bereksperimen dengan Rust, dan dalam semua keanggunan yang saya temukan dalam bahasa ini, ada satu kekhasan yang membingungkan saya dan tampaknya benar-benar tidak pada tempatnya.
Karat secara otomatis referensi pointer saat membuat panggilan metode. Saya membuat beberapa tes untuk menentukan perilaku yang tepat:
struct X { val: i32 }
impl std::ops::Deref for X {
type Target = i32;
fn deref(&self) -> &i32 { &self.val }
}
trait M { fn m(self); }
impl M for i32 { fn m(self) { println!("i32::m()"); } }
impl M for X { fn m(self) { println!("X::m()"); } }
impl M for &X { fn m(self) { println!("&X::m()"); } }
impl M for &&X { fn m(self) { println!("&&X::m()"); } }
impl M for &&&X { fn m(self) { println!("&&&X::m()"); } }
trait RefM { fn refm(&self); }
impl RefM for i32 { fn refm(&self) { println!("i32::refm()"); } }
impl RefM for X { fn refm(&self) { println!("X::refm()"); } }
impl RefM for &X { fn refm(&self) { println!("&X::refm()"); } }
impl RefM for &&X { fn refm(&self) { println!("&&X::refm()"); } }
impl RefM for &&&X { fn refm(&self) { println!("&&&X::refm()"); } }
struct Y { val: i32 }
impl std::ops::Deref for Y {
type Target = i32;
fn deref(&self) -> &i32 { &self.val }
}
struct Z { val: Y }
impl std::ops::Deref for Z {
type Target = Y;
fn deref(&self) -> &Y { &self.val }
}
#[derive(Clone, Copy)]
struct A;
impl M for A { fn m(self) { println!("A::m()"); } }
impl M for &&&A { fn m(self) { println!("&&&A::m()"); } }
impl RefM for A { fn refm(&self) { println!("A::refm()"); } }
impl RefM for &&&A { fn refm(&self) { println!("&&&A::refm()"); } }
fn main() {
// I'll use @ to denote left side of the dot operator
(*X{val:42}).m(); // i32::m() , Self == @
X{val:42}.m(); // X::m() , Self == @
(&X{val:42}).m(); // &X::m() , Self == @
(&&X{val:42}).m(); // &&X::m() , Self == @
(&&&X{val:42}).m(); // &&&X:m() , Self == @
(&&&&X{val:42}).m(); // &&&X::m() , Self == *@
(&&&&&X{val:42}).m(); // &&&X::m() , Self == **@
println!("-------------------------");
(*X{val:42}).refm(); // i32::refm() , Self == @
X{val:42}.refm(); // X::refm() , Self == @
(&X{val:42}).refm(); // X::refm() , Self == *@
(&&X{val:42}).refm(); // &X::refm() , Self == *@
(&&&X{val:42}).refm(); // &&X::refm() , Self == *@
(&&&&X{val:42}).refm(); // &&&X::refm(), Self == *@
(&&&&&X{val:42}).refm(); // &&&X::refm(), Self == **@
println!("-------------------------");
Y{val:42}.refm(); // i32::refm() , Self == *@
Z{val:Y{val:42}}.refm(); // i32::refm() , Self == **@
println!("-------------------------");
A.m(); // A::m() , Self == @
// without the Copy trait, (&A).m() would be a compilation error:
// cannot move out of borrowed content
(&A).m(); // A::m() , Self == *@
(&&A).m(); // &&&A::m() , Self == &@
(&&&A).m(); // &&&A::m() , Self == @
A.refm(); // A::refm() , Self == @
(&A).refm(); // A::refm() , Self == *@
(&&A).refm(); // A::refm() , Self == **@
(&&&A).refm(); // &&&A::refm(), Self == @
}
( Taman bermain )
Jadi, sepertinya itu, kurang lebih:
- Compiler akan memasukkan sebanyak mungkin operator dereference untuk memanggil metode.
- Kompiler, ketika menyelesaikan metode yang dinyatakan menggunakan
&self
(call-by-reference):- Pertama kali mencoba memanggil dereference tunggal
self
- Kemudian coba panggil jenis persis
self
- Lalu, coba masukkan sebanyak mungkin operator dereferensi untuk pertandingan
- Pertama kali mencoba memanggil dereference tunggal
- Metode yang dinyatakan menggunakan
self
(panggilan-oleh-nilai) untuk tipeT
berperilaku seolah-olah mereka dideklarasikan menggunakan&self
(panggilan-oleh-referensi) untuk tipe&T
dan dipanggil pada referensi untuk apa pun yang ada di sisi kiri operator titik. - Aturan di atas pertama kali dicoba dengan dereferencing bawaan bawaan, dan jika tidak ada kecocokan, kelebihan dengan
Deref
sifat tersebut digunakan.
Apa aturan auto-dereferensi yang tepat? Adakah yang bisa memberikan alasan formal untuk keputusan desain seperti itu?
reference
dereference
formal-semantics
rust
kFYatek
sumber
sumber
Jawaban:
Kode semu Anda cukup benar. Untuk contoh ini, misalkan kita memiliki pemanggilan metode di
foo.bar()
manafoo: T
. Saya akan menggunakan sintaksis yang sepenuhnya memenuhi syarat (FQS) untuk menjadi tidak ambigu tentang apa jenis metode yang dipanggil, misalnyaA::bar(foo)
atauA::bar(&***foo)
. Saya hanya akan menulis setumpuk huruf kapital acak, masing-masing hanya beberapa jenis / sifat sewenang-wenang, kecualiT
selalu jenis variabel aslifoo
yang dipanggil oleh metode ini.Inti dari algoritma ini adalah:
U
(yaitu, aturU = T
dan kemudianU = *T
, ...)bar
mana jenis penerima (jenisself
dalam metode) cocokU
persis, gunakan itu ( "metode nilai" )&
atau&mut
dari penerima), dan, jika beberapa penerima metode cocok&U
, gunakan itu ( "metode autorefd" )Terutama, semuanya menganggap "jenis penerima" metode, bukan yang
Self
jenis sifat tersebut, yaituimpl ... for Foo { fn method(&self) {} }
berpikir tentang&Foo
saat pencocokan metode, danfn method2(&mut self)
akan berpikir tentang&mut Foo
saat pencocokan.Ini adalah kesalahan jika ada beberapa metode sifat yang valid di langkah-langkah dalam (yaitu, hanya ada nol atau satu metode sifat yang valid di masing-masing 1. atau 2., tetapi bisa ada satu yang valid untuk masing-masing: satu dari 1 akan diambil terlebih dahulu), dan metode bawaan lebih diutamakan daripada yang sifat. Ini juga kesalahan jika kita sampai pada akhir loop tanpa menemukan apa pun yang cocok. Ini juga merupakan kesalahan untuk memiliki rekursif
Deref
implementasi , yang membuat loop tak terbatas (mereka akan mencapai "batas rekursi").Aturan-aturan ini tampaknya melakukan apa yang saya maksudkan di sebagian besar keadaan, walaupun memiliki kemampuan untuk menulis formulir FQS yang tidak ambigu sangat berguna dalam beberapa kasus tepi, dan untuk pesan kesalahan yang masuk akal untuk kode yang dihasilkan makro.
Hanya satu referensi otomatis yang ditambahkan karena
&foo
mempertahankan koneksi yang kuat kefoo
(itu adalah alamatfoo
itu sendiri), tetapi mengambil lebih banyak mulai kehilangan itu:&&foo
adalah alamat dari beberapa variabel sementara di stack yang menyimpan&foo
.Contohnya
Misalkan kita memiliki panggilan
foo.refm()
, jikafoo
memiliki tipe:X
, kemudian kita mulai denganU = X
,refm
memiliki tipe penerima&...
, jadi langkah 1 tidak cocok, mengambil auto-ref memberi kita&X
, dan ini tidak cocok (denganSelf = X
), jadi panggilannya adalahRefM::refm(&foo)
&X
, dimulai denganU = &X
, yang cocok&self
dengan langkah pertama (denganSelf = X
), dan panggilannyaRefM::refm(foo)
&&&&&X
, ini tidak cocok dengan salah satu langkah (sifat tidak diterapkan untuk&&&&X
atau&&&&&X
), jadi kami dereferensi sekali untuk mendapatkanU = &&&&X
, yang cocok dengan 1 (denganSelf = &&&X
) dan panggilannya adalahRefM::refm(*foo)
Z
, tidak cocok dengan langkah mana pun sehingga dereferensi sekali, untuk mendapatkanY
, yang juga tidak cocok, jadi dereferensi lagi, untuk mendapatkanX
, yang tidak cocok dengan 1, tetapi cocok dengan setelah autorefing, jadi panggilannyaRefM::refm(&**foo)
.&&A
, 1. tidak cocok dan 2. tidak karena sifat tersebut tidak diterapkan untuk&A
(untuk 1) atau&&A
(untuk 2), sehingga dereferenced ke&A
, yang cocok dengan 1., denganSelf = A
Misalkan kita memiliki
foo.m()
, dan ituA
tidakCopy
, jikafoo
memiliki tipe:A
, MakaU = A
pertandinganself
langsung sehingga panggilan tersebutM::m(foo)
denganSelf = A
&A
, maka 1. tidak cocok, dan 2. tidak (tidak&A
juga&&A
mengimplementasikan sifat), sehingga dereferenced keA
, yang cocok, tetapiM::m(*foo)
membutuhkan pengambilanA
oleh nilai dan karenanya keluarfoo
, karenanya kesalahan.&&A
, 1. tidak cocok, tetapi autorefing memberi&&&A
, yang cocok, jadi panggilannya adalahM::m(&foo)
denganSelf = &&&A
.(Jawaban ini didasarkan pada kode , dan cukup dekat dengan README (agak ketinggalan jaman) . Niko Matsakis, penulis utama dari bagian kompiler / bahasa ini, juga melirik jawaban ini.)
sumber
&&String
->&String
->String
->str
) dan kemudian referensi pada maks sekali (str
->&str
)".Referensi Rust memiliki bab tentang ekspresi pemanggilan metode . Saya menyalin bagian terpenting di bawah ini. Pengingat: kita berbicara tentang ekspresi
recv.m()
, di manarecv
disebut "ekspresi penerima" di bawah ini.( Catatan tentang [¹] : Saya benar-benar berpikir frasa ini salah. Saya telah membuka masalah . Mari kita abaikan saja kalimat itu dalam tanda kurung.)
Mari kita telusuri beberapa contoh dari kode Anda secara detail! Sebagai contoh Anda, kami dapat mengabaikan bagian tentang "paksaan tanpa ukuran" dan "metode yang melekat".
(*X{val:42}).m()
: tipe ekspresi penerima adalahi32
. Kami melakukan langkah-langkah ini:i32
tidak dapat disangkal, jadi kita sudah selesai dengan langkah 1. Daftar:[i32]
&i32
dan&mut i32
. Daftar:[i32, &i32, &mut i32]
<i32 as M>::m
yang memiliki tipe penerimai32
. Jadi kita sudah selesai.Sejauh ini sangat mudah. Sekarang mari kita memilih contoh yang lebih sulit:
(&&A).m()
. Jenis ekspresi penerima adalah&&A
. Kami melakukan langkah-langkah ini:&&A
dapat ditinjau ulang&A
, jadi kami menambahkannya ke daftar.&A
dapat direferensikan lagi, jadi kami juga menambahkanA
ke daftar.A
tidak bisa dereferensi, jadi kami berhenti. Daftar:[&&A, &A, A]
T
dalam daftar, kami tambahkan&T
dan&mut T
segera setelahnyaT
. Daftar:[&&A, &&&A, &mut &&A, &A, &&A, &mut &A, A, &A, &mut A]
&&A
, jadi kami pergi ke jenis berikutnya dalam daftar.<&&&A as M>::m
yang memang memiliki tipe penerima&&&A
. Jadi kita selesai.Berikut adalah daftar calon penerima untuk semua contoh Anda. Jenis yang dilampirkan
⟪x⟫
adalah yang "menang", yaitu jenis pertama yang dapat ditemukan metode pemasangan. Juga ingat bahwa tipe pertama dalam daftar selalu tipe ekspresi penerima. Terakhir, saya memformat daftar dalam baris tiga, tapi itu hanya memformat: daftar ini adalah daftar datar.(*X{val:42}).m()
→<i32 as M>::m
X{val:42}.m()
→<X as M>::m
(&X{val:42}).m()
→<&X as M>::m
(&&X{val:42}).m()
→<&&X as M>::m
(&&&X{val:42}).m()
→<&&&X as M>::m
(&&&&X{val:42}).m()
→<&&&X as M>::m
(&&&&&X{val:42}).m()
→<&&&X as M>::m
(*X{val:42}).refm()
→<i32 as RefM>::refm
X{val:42}.refm()
→<X as RefM>::refm
(&X{val:42}).refm()
→<X as RefM>::refm
(&&X{val:42}).refm()
→<&X as RefM>::refm
(&&&X{val:42}).refm()
→<&&X as RefM>::refm
(&&&&X{val:42}).refm()
→<&&&X as RefM>::refm
(&&&&&X{val:42}).refm()
→<&&&X as RefM>::refm
Y{val:42}.refm()
→<i32 as RefM>::refm
Z{val:Y{val:42}}.refm()
→<i32 as RefM>::refm
A.m()
→<A as M>::m
(&A).m()
→<A as M>::m
(&&A).m()
→<&&&A as M>::m
(&&&A).m()
→<&&&A as M>::m
A.refm()
→<A as RefM>::refm
(&A).refm()
→<A as RefM>::refm
(&&A).refm()
→<A as RefM>::refm
(&&&A).refm()
→<&&&A as RefM>::refm
sumber