Tôi đang học / thử nghiệm với Rust, và trong tất cả sự tao nhã mà tôi tìm thấy trong ngôn ngữ này, có một điều đặc biệt gây trở ngại cho tôi và dường như hoàn toàn không phù hợp.
Rust tự động dereferences con trỏ khi thực hiện cuộc gọi phương thức. Tôi đã thực hiện một số thử nghiệm để xác định hành vi chính xác:
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 == @
}
( Sân chơi )
Vì vậy, có vẻ như, ít nhiều:
- Trình biên dịch sẽ chèn càng nhiều toán tử dereference cần thiết để gọi một phương thức.
- Trình biên dịch, khi giải quyết các phương thức được khai báo bằng cách sử dụng
&self
(gọi theo tham chiếu):- Đầu tiên cố gắng kêu gọi một sự trung thành duy nhất của
self
- Sau đó thử gọi loại chính xác của
self
- Sau đó, cố gắng chèn càng nhiều toán tử quy định càng cần thiết cho một trận đấu
- Đầu tiên cố gắng kêu gọi một sự trung thành duy nhất của
- Các phương thức được khai báo bằng cách sử dụng
self
(gọi theo giá trị) cho loạiT
hành xử như thể chúng được khai báo bằng cách sử dụng&self
(gọi theo tham chiếu) cho loại&T
và được gọi trên tham chiếu đến bất cứ thứ gì ở phía bên trái của toán tử dấu chấm. - Các quy tắc trên được thử đầu tiên với hội thảo thô tích hợp sẵn và nếu không có kết quả khớp, quá tải với
Deref
đặc điểm được sử dụng.
Các quy tắc tự động hội nghị chính xác là gì? Bất cứ ai có thể đưa ra bất kỳ lý do chính thức cho một quyết định thiết kế như vậy?