Quy tắc tự động hội thảo chính xác của Rust là gì?


181

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
  • 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ại Thà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 &Tvà đượ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?


Tôi đã đăng chéo này lên subreddit Rust với hy vọng nhận được một số câu trả lời hay!
Người quản lý 16/2/2015

Để có thêm niềm vui, hãy thử lặp lại thí nghiệm trong thuốc generic và so sánh kết quả.
user2665887 16/2/2015

Câu trả lời:


137

Mã giả của bạn là khá nhiều chính xác. Trong ví dụ này, giả sử chúng ta có một cuộc gọi phương thức foo.bar()ở đâu foo: T. Tôi sẽ sử dụng cú pháp đủ điều kiện (FQS) để không rõ ràng về loại phương thức đang được gọi với, ví dụ A::bar(foo)hoặc A::bar(&***foo). Tôi sẽ viết một đống chữ in hoa ngẫu nhiên, mỗi chữ chỉ là một kiểu / đặc điểm tùy ý, ngoại trừ Tluôn là kiểu của biến ban đầu foomà phương thức được gọi.

Cốt lõi của thuật toán là:

  • Đối với mỗi "bước bổ nhiệm" U (nghĩa là đặt U = Tvà sau đó U = *T, ...)
    1. nếu có một phương thức bartrong đó loại máy thu (loại selftrong phương thức) khớp Uchính xác, hãy sử dụng nó ( một "phương thức giá trị" )
    2. mặt khác, thêm một tự động ref (lấy &hoặc &mutcủa người nhận) và, nếu người nhận của một số phương thức khớp &U, hãy sử dụng nó ( một "phương thức autorefd" )

Đáng chú ý, tất cả mọi thứ coi là "loại máy thu" của phương pháp này, không những Selfloại tính trạng, tức là impl ... for Foo { fn method(&self) {} }nghĩ về &Fookhi kết hợp phương pháp, và fn method2(&mut self)sẽ suy nghĩ về &mut Fookhi kết hợp.

Đó là một lỗi nếu có nhiều phương thức tính trạng hợp lệ trong các bước bên trong (nghĩa là chỉ có thể có 0 hoặc một phương thức tính trạng hợp lệ trong mỗi 1. hoặc 2., nhưng có thể có một phương thức hợp lệ cho mỗi bước: một từ 1 sẽ được thực hiện trước) và các phương thức vốn có được ưu tiên hơn các đặc điểm. Đó cũng là một lỗi nếu chúng ta đi đến cuối vòng lặp mà không tìm thấy bất cứ điều gì phù hợp. Đó cũng là một lỗi khi Derefthực hiện đệ quy , làm cho vòng lặp trở nên vô hạn (chúng sẽ đạt "giới hạn đệ quy").

Các quy tắc này dường như có ý nghĩa trong hầu hết các trường hợp, mặc dù có khả năng viết biểu mẫu FQS rõ ràng rất hữu ích trong một số trường hợp cạnh và đối với các thông báo lỗi hợp lý cho mã được tạo macro.

Chỉ có một tham chiếu tự động được thêm vào vì

  • nếu không có ràng buộc, mọi thứ trở nên xấu / chậm, vì mỗi loại có thể có một số lượng tham chiếu tùy ý được thực hiện
  • lấy một tham chiếu &fooduy trì một kết nối mạnh đến foo(đó là địa chỉ của foochính nó), nhưng lấy thêm bắt đầu để mất nó: &&foolà địa chỉ của một số biến tạm thời trên ngăn xếp lưu trữ &foo.

Ví dụ

Giả sử chúng ta có một cuộc gọi foo.refm(), nếu foocó loại:

  • X, sau đó chúng tôi bắt đầu U = X, refmcó loại máy thu &..., vì vậy bước 1 không khớp, lấy tự động giới thiệu cho chúng tôi &Xvà điều này không khớp (với Self = X), vì vậy cuộc gọi làRefM::refm(&foo)
  • &X, bắt đầu bằng U = &X, khớp với &selfbước đầu tiên (với Self = X) và vì vậy cuộc gọi làRefM::refm(foo)
  • &&&&&X, điều này không khớp với một trong hai bước (đặc điểm không được triển khai cho &&&&Xhoặc &&&&&X), vì vậy chúng tôi đã quy định một lần để có được U = &&&&X, khớp với 1 (với Self = &&&X) và cuộc gọi làRefM::refm(*foo)
  • Z, không khớp với một trong hai bước để nó được hủy đăng ký một lần, để nhận Y, cũng không khớp, do đó, nó lại được điều chỉnh lại, để nhận X, không khớp với 1, nhưng khớp với sau khi tự động trả lời, vì vậy cuộc gọi là RefM::refm(&**foo).
  • &&A, 1. không khớp và cũng không 2. vì tính trạng không được triển khai cho &A(cho 1) hoặc &&A(cho 2), do đó, nó được quy định lại &A, khớp với 1., vớiSelf = A

Giả sử chúng ta có foo.m(), và đó Akhông phải là Copy, nếu foocó loại:

  • A, sau đó U = Akhớp selftrực tiếp để cuộc gọi được thực hiện M::m(foo)vớiSelf = A
  • &A, sau đó 1. không khớp và cũng không 2. ( &Acũng không &&Athực hiện tính trạng), do đó, nó được quy định lại A, không khớp, nhưng M::m(*foo)yêu cầu lấy Atheo giá trị và do đó di chuyển ra foo, do đó lỗi.
  • &&A, 1. không phù hợp, nhưng autorefing cho &&&A, mà không phù hợp, do đó gọi là M::m(&foo)Self = &&&A.

(Câu trả lời này dựa trên khá gần với README (hơi lỗi thời) . Niko Matsakis, tác giả chính của phần này của trình biên dịch / ngôn ngữ, cũng liếc qua câu trả lời này.)


15
Câu trả lời này có vẻ đầy đủ và chi tiết nhưng tôi nghĩ rằng nó thiếu một bản tóm tắt ngắn gọn và dễ tiếp cận của các quy tắc. Một triệu tập như vậy được đưa ra trong nhận xét này bởi Shepmaster : "Nó [thuật toán deref] sẽ hủy đăng ký nhiều lần nhất có thể ( &&String-> &String-> String-> str) và sau đó tham chiếu tối đa một lần ( str-> &str)".
Lii

(Tôi không biết chính xác và đầy đủ lời giải thích đó như thế nào.)
Lii

1
Trong những trường hợp nào xảy ra hội nghị tự động xảy ra? Có phải nó chỉ được sử dụng cho biểu thức nhận cho cuộc gọi phương thức? Đối với truy cập trường cũng? Phân công bên phải? Bên tay trái? Thông số chức năng? Trả về biểu thức giá trị?
Lii

1
Lưu ý: Hiện nay, có một lưu ý nomicon TODO để ăn cắp thông tin từ câu trả lời này và viết nó trong static.rust-lang.org/doc/master/nomicon/dot-operator.html
Samb

1
Là sự ép buộc (A) đã thử trước cái này hay (B) đã thử sau cái này hay (C) đã thử trong mỗi bước của thuật toán này hay (D) cái gì khác?
haslersn

8

Tham chiếu Rust có một chương về biểu thức gọi phương thức . Tôi đã sao chép phần quan trọng nhất dưới đây. Nhắc nhở: chúng ta đang nói về một biểu thức recv.m(), trong đó recvđược gọi là "biểu thức người nhận" bên dưới.

Bước đầu tiên là xây dựng một danh sách các loại người nhận ứng cử viên. Có được những điều này bằng cách lặp đi lặp lại loại bỏ biểu thức của người nhận, thêm từng loại gặp phải vào danh sách, sau đó cuối cùng thử một sự ép buộc không được thống nhất ở cuối và thêm loại kết quả nếu điều đó thành công. Sau đó, đối với mỗi ứng cử viên T, thêm &T&mut Tvào danh sách ngay sau đó T.

Ví dụ, nếu người nhận có loại Box<[i32;2]>, sau đó các loại ứng cử viên sẽ được Box<[i32;2]>, &Box<[i32;2]>, &mut Box<[i32;2]>, [i32; 2](bởi dereferencing), &[i32; 2], &mut [i32; 2], [i32](bằng cách ép buộc không đúng cở), &[i32]và cuối cùng &mut [i32].

Sau đó, đối với từng loại ứng viên T, hãy tìm kiếm một phương thức có thể nhìn thấy với một người nhận loại đó ở những nơi sau:

  1. TCác phương thức vốn có (các phương thức được triển khai trực tiếp trên T[¹]).
  2. Bất kỳ phương pháp nào được cung cấp bởi một đặc điểm có thể nhìn thấy được thực hiện bởi T. [...]

( Lưu ý về [] : Tôi thực sự nghĩ rằng cụm từ này là sai. Tôi đã mở một vấn đề . Hãy bỏ qua câu đó trong ngoặc đơn.)


Hãy xem qua một vài ví dụ từ mã của bạn một cách chi tiết! Đối với các ví dụ của bạn, chúng tôi có thể bỏ qua phần về "cưỡng chế không thống nhất" và "phương pháp vốn có".

(*X{val:42}).m(): loại biểu thức người nhận là i32. Chúng tôi thực hiện các bước sau:

  • Tạo danh sách các loại người nhận ứng cử viên:
    • i32 không thể hủy đăng ký, vì vậy chúng tôi đã hoàn thành bước 1. Danh sách: [i32]
    • Tiếp theo, chúng tôi thêm &i32&mut i32. Danh sách:[i32, &i32, &mut i32]
  • Tìm kiếm các phương thức cho từng loại người nhận ứng cử viên:
    • Chúng tôi tìm thấy <i32 as M>::mcó loại người nhận i32. Vậy là chúng ta đã hoàn thành.


Cho đến nay thật dễ dàng. Bây giờ hãy chọn một ví dụ khó khăn hơn : (&&A).m(). Kiểu biểu thức người nhận là &&A. Chúng tôi thực hiện các bước sau:

  • Tạo danh sách các loại người nhận ứng cử viên:
    • &&Acó thể được hủy đăng ký &A, vì vậy chúng tôi thêm nó vào danh sách. &Acó thể được hủy đăng ký lại, vì vậy chúng tôi cũng thêm Avào danh sách. Akhông thể bị hủy bỏ, vì vậy chúng tôi dừng lại. Danh sách:[&&A, &A, A]
    • Tiếp theo, đối với mỗi loại Ttrong danh sách, chúng tôi thêm &T&mut Tngay sau đó T. Danh sách:[&&A, &&&A, &mut &&A, &A, &&A, &mut &A, A, &A, &mut A]
  • Tìm kiếm các phương thức cho từng loại người nhận ứng cử viên:
    • Không có phương thức với loại máy thu &&A, vì vậy chúng tôi đi đến loại tiếp theo trong danh sách.
    • Chúng tôi tìm thấy phương pháp <&&&A as M>::mthực sự có loại máy thu &&&A. Vậy là xong.

Dưới đây là danh sách người nhận ứng cử viên cho tất cả các ví dụ của bạn. Loại được đính kèm trong đó ⟪x⟫là loại "thắng", tức là loại đầu tiên có thể tìm thấy phương pháp phù hợp. Cũng nên nhớ rằng loại đầu tiên trong danh sách luôn là loại của biểu thức người nhận. Cuối cùng, tôi định dạng danh sách theo dòng ba, nhưng đó chỉ là định dạng: danh sách này là một danh sách phẳng.

  • (*X{val:42}).m()<i32 as M>::m
    [i32, &i32, &mut i32]
  • X{val:42}.m()<X as M>::m
    [⟪X⟫, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&X{val:42}).m()<&X as M>::m
    [&X⟫, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&X{val:42}).m()<&&X as M>::m
    [&&X⟫, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&X{val:42}).m()<&&&X as M>::m
    [&&&X⟫, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&&X{val:42}).m()<&&&X as M>::m
    [&&&&X, &&&&&X, &mut &&&&X,&&&X⟫, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&&&X{val:42}).m()<&&&X as M>::m
    [&&&&&X, &&&&&&X, &mut &&&&&X, 
     &&&&X, &&&&&X, &mut &&&&X,&&&X⟫, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]


  • (*X{val:42}).refm()<i32 as RefM>::refm
    [i32,&i32, &mut i32]
  • X{val:42}.refm()<X as RefM>::refm
    [X,&X⟫, &mut X, 
     i32, &i32, &mut i32]
  • (&X{val:42}).refm()<X as RefM>::refm
    [&X⟫, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&X{val:42}).refm()<&X as RefM>::refm
    [&&X⟫, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&X{val:42}).refm()<&&X as RefM>::refm
    [&&&X⟫, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&&X{val:42}).refm()<&&&X as RefM>::refm
    [&&&&X⟫, &&&&&X, &mut &&&&X, 
     &&&X, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&&&X{val:42}).refm()<&&&X as RefM>::refm
    [&&&&&X, &&&&&&X, &mut &&&&&X,&&&&X⟫, &&&&&X, &mut &&&&X, 
     &&&X, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]


  • Y{val:42}.refm()<i32 as RefM>::refm
    [Y, &Y, &mut Y,
     i32,&i32, &mut i32]
  • Z{val:Y{val:42}}.refm()<i32 as RefM>::refm
    [Z, &Z, &mut Z,
     Y, &Y, &mut Y,
     i32,&i32, &mut i32]


  • A.m()<A as M>::m
    [⟪A⟫, &A, &mut A]
  • (&A).m()<A as M>::m
    [&A, &&A, &mut &A,
     ⟪A⟫, &A, &mut A]
  • (&&A).m()<&&&A as M>::m
    [&&A,&&&A⟫, &mut &&A,
     &A, &&A, &mut &A,
     A, &A, &mut A]
  • (&&&A).m()<&&&A as M>::m
    [&&&A⟫, &&&&A, &mut &&&A,
     &&A, &&&A, &mut &&A,
     &A, &&A, &mut &A,
     A, &A, &mut A]
  • A.refm()<A as RefM>::refm
    [A,&A⟫, &mut A]
  • (&A).refm()<A as RefM>::refm
    [&A⟫, &&A, &mut &A,
     A, &A, &mut A]
  • (&&A).refm()<A as RefM>::refm
    [&&A, &&&A, &mut &&A,&A⟫, &&A, &mut &A,
     A, &A, &mut A]
  • (&&&A).refm()<&&&A as RefM>::refm
    [&&&A,&&&&A⟫, &mut &&&A,
     &&A, &&&A, &mut &&A,
     &A, &&A, &mut &A,
     A, &A, &mut A]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.