Tại sao `std :: mem :: drop` hoàn toàn giống với bao đóng | _ | () trong giới hạn tính trạng được xếp hạng cao hơn?


13

Việc thực hiện std::mem::dropđược ghi lại như sau:

pub fn drop<T>(_x: T) { }

Như vậy, tôi hy vọng việc đóng cửa |_| ()(thông thường được gọi là đóng cửa nhà vệ sinh ) sẽ là sự thay thế tiềm năng 1: 1 cho dropcả hai hướng. Tuy nhiên, mã dưới đây cho thấy dropkhông tương thích với một đặc điểm được xếp hạng cao hơn ràng buộc vào tham số của chức năng, trong khi đóng cửa nhà vệ sinh là.

fn foo<F, T>(f: F, x: T)
where
    for<'a> F: FnOnce(&'a T),
{
    dbg!(f(&x));
}

fn main() {
    foo(|_| (), "toilet closure"); // this compiles
    foo(drop, "drop"); // this does not!
}

Thông báo lỗi của trình biên dịch:

error[E0631]: type mismatch in function arguments
  --> src/main.rs:10:5
   |
1  | fn foo<F, T>(f: F, x: T)
   |    ---
2  | where
3  |     for<'a> F: FnOnce(&'a T),
   |                ------------- required by this bound in `foo`
...
10 |     foo(drop, "drop"); // this does not!
   |     ^^^
   |     |
   |     expected signature of `for<'a> fn(&'a _) -> _`
   |     found signature of `fn(_) -> _`

error[E0271]: type mismatch resolving `for<'a> <fn(_) {std::mem::drop::<_>} as std::ops::FnOnce<(&'a _,)>>::Output == ()`
  --> src/main.rs:10:5
   |
1  | fn foo<F, T>(f: F, x: T)
   |    ---
2  | where
3  |     for<'a> F: FnOnce(&'a T),
   |                ------------- required by this bound in `foo`
...
10 |     foo(drop, "drop"); // this does not!
   |     ^^^ expected bound lifetime parameter 'a, found concrete lifetime

Xem xét dropđược cho là chung chung đối với bất kỳ kích thước nào T, có vẻ không hợp lý khi chữ ký "chung chung hơn" fn(_) -> _không tương thích for<'a> fn (&'a _) -> _. Tại sao trình biên dịch không thừa nhận chữ ký dropở đây, và điều gì làm cho nó khác biệt khi đóng cửa nhà vệ sinh được đặt thay cho nó?

Câu trả lời:


4

Cốt lõi của vấn đề dropkhông phải là một hàm duy nhất, mà là một tập hợp các hàm được tham số hóa mà mỗi loại bỏ một số loại cụ thể. Để đáp ứng một đặc điểm được xếp hạng cao hơn bị ràng buộc (sau đây là hrtb), bạn cần một hàm duy nhất có thể đồng thời đưa các tham chiếu đến một loại với bất kỳ thời gian nhất định nào.


Chúng ta sẽ sử dụng droplàm ví dụ điển hình của chúng ta về một hàm chung, nhưng tất cả điều này cũng áp dụng chung hơn. Đây là mã để tham khảo : fn drop<T>(_: T) {}.

Về mặt khái niệm, dropkhông phải là một chức năng duy nhất, mà là một chức năng cho mọi loại có thể T. Bất kỳ trường hợp cụ thể nào dropchỉ mất các đối số của một loại duy nhất. Điều này được gọi là đơn hình . Nếu một khác Tđược sử dụng với drop, một phiên bản khác dropđược biên dịch. Đó là lý do tại sao bạn không thể vượt qua một hàm chung làm đối số và sử dụng hàm đó một cách tổng quát (xem câu hỏi này )

Mặt khác, một chức năng như fn pass(x: &i32) -> &i32 {x}thỏa mãn hrtb for<'a> Fn(&'a i32) -> &'a i32. Không giống như drop, chúng ta có một chức năng duy nhất đồng thời đáp ứng Fn(&'a i32) -> &'a i32cho mọi cuộc sống 'a. Điều này được phản ánh trong cách passcó thể được sử dụng.

fn pass(x: &i32) -> &i32 {
    x
}

fn two_uses<F>(f: F)
where
    for<'a> F: Fn(&'a i32) -> &'a i32, // By the way, this can simply be written
                                       // F: Fn(&i32) -> &i32 due to lifetime elision rules.
                                       // That applies to your original example too.
{
    {
        // x has some lifetime 'a
        let x = &22;
        println!("{}", f(x));
        // 'a ends around here
    }
    {
        // y has some lifetime 'b
        let y = &23;
        println!("{}", f(y));
        // 'b ends around here
    }
    // 'a and 'b are unrelated since they have no overlap
}

fn main() {
    two_uses(pass);
}

(sân chơi)

Trong ví dụ, thời gian sống 'a'bkhông có mối quan hệ với nhau: không hoàn toàn bao gồm người khác. Vì vậy, không có một số loại phụ xảy ra ở đây. Một ví dụ duy nhất passthực sự được sử dụng với hai vòng đời khác nhau, không liên quan.

Đây là lý do tại sao dropkhông thỏa mãn for<'a> FnOnce(&'a T). Bất kỳ trường hợp cụ dropthể nào chỉ có thể bao gồm một đời (bỏ qua phân nhóm). Nếu chúng ta truyền dropvào two_usestừ ví dụ trên (với những thay đổi nhỏ về chữ ký và giả sử trình biên dịch cho chúng ta), thì nó sẽ phải chọn một số thời gian cụ thể 'avà ví dụ droptrong phạm vi two_usessẽ là Fn(&'a i32)trong một khoảng thời gian cụ thể'a . Vì chức năng sẽ chỉ áp dụng cho một đời duy nhất 'a, nên sẽ không thể sử dụng nó với hai vòng đời không liên quan.

Vậy tại sao việc đóng cửa nhà vệ sinh lại có được một hrtb? Khi suy ra kiểu đóng, nếu kiểu dự kiến ​​gợi ý rằng một đặc điểm xếp hạng cao hơn là cần thiết, trình biên dịch sẽ cố gắng làm cho phù hợp . Trong trường hợp này, nó thành công.


Vấn đề # 41078 liên quan chặt chẽ đến vấn đề này và đặc biệt, nhận xét của eddyb ở đây đưa ra lời giải thích cơ bản ở trên (mặc dù trong bối cảnh đóng cửa, thay vì các chức năng thông thường). Vấn đề tự nó không giải quyết vấn đề hiện tại mặc dù. Nó thay vào đó giải quyết những gì xảy ra nếu bạn chỉ định đóng cửa nhà vệ sinh cho một biến trước khi sử dụng nó (hãy thử!).

Có thể tình hình sẽ thay đổi trong tương lai, nhưng nó sẽ đòi hỏi một sự thay đổi khá lớn trong cách các chức năng chung được đơn hình hóa.


4

Tóm lại, cả hai dòng nên thất bại. Nhưng vì một bước trong cách xử lý cũ của hrtb, cụ thể là kiểm tra rò rỉ , hiện có một số vấn đề về âm thanh, rustckết thúc (không chính xác) chấp nhận một và để lại thông báo lỗi khá xấu.

Nếu bạn vô hiệu hóa kiểm tra rò rỉ rustc +nightly -Zno-leak-check, bạn sẽ có thể thấy một thông báo lỗi hợp lý hơn:

error[E0308]: mismatched types
  --> src/main.rs:10:5
   |
10 |     foo(drop, "drop");
   |     ^^^ one type is more general than the other
   |
   = note: expected type `std::ops::FnOnce<(&'a &str,)>`
              found type `std::ops::FnOnce<(&&str,)>`

Giải thích của tôi về lỗi này là &xphần thân của foohàm chỉ có vòng đời phạm vi giới hạn trong phần thân đã nói, do đó f(&x)cũng có vòng đời phạm vi tương tự không thể đáp ứng for<'a>định lượng phổ theo yêu cầu của tính trạng bị ràng buộc.

Câu hỏi bạn trình bày ở đây gần giống với vấn đề # 57642 , cũng có hai phần tương phản.

Cách mới để xử lý vòng đời hrtb là sử dụng cái gọi là vũ trụ . Niko có WIP để xử lý kiểm tra rò rỉ với vũ trụ. Theo chế độ mới này, cả hai phần của vấn đề # 57642 được liên kết ở trên được cho là đều thất bại với các chẩn đoán rõ ràng hơn nhiều. Tôi cho rằng trình biên dịch cũng có thể xử lý mã ví dụ của bạn một cách chính xác.

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.