Чому `std :: mem :: drop` точно так само, як і закриття | _ | () у межах вищого рангу?


13

Реалізація std::mem::dropдокументально підтверджена таким чином:

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

Як таке, я б очікував, що закриття |_| ()(у розмові відоме як закриття туалету ) буде потенційною заміною 1: 1 dropв обох напрямках. Однак, наведений нижче код показує, що dropвін не сумісний із ознакою вищого рейтингу, прив'язаною до параметра функції, тоді як закриття туалету є.

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!
}

Повідомлення про помилку компілятора:

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

Враховуючи, що dropце нібито загальне відносно будь-якого розміру T, звучить нерозумно, що "більш загальний" підпис fn(_) -> _не сумісний for<'a> fn (&'a _) -> _. Чому компілятор не допускає тут підпису drop, і чим він відрізняється, коли замість нього закривається унітаз?

Відповіді:


4

Суть проблеми полягає в тому, що dropце не одна функція, а скоріше параметризований набір функцій, кожен з яких випадає певного типу. Щоб задовольнити більш високий рейтинг, пов'язаний з ознакою (далі hrtb), вам знадобиться одна функція, яка може одночасно приймати посилання на тип протягом будь-якого періоду життя.


Ми будемо використовувати dropяк наш типовий приклад загальної функції, але все це стосується і більш загального. Ось код для довідки: fn drop<T>(_: T) {}.

Концептуально - dropце не одна функція, а одна функція для кожного можливого типу T. Будь-який конкретний примірник dropбере лише аргументи одного типу. Це називається мономорфізацією . Якщо Tвикористовується dropінша версія drop, складається інша версія . Тому не можна передавати загальну функцію як аргумент і використовувати цю функцію в повній загальності (див. Це питання )

З іншого боку, така функція, як fn pass(x: &i32) -> &i32 {x}задовольняє hrtb for<'a> Fn(&'a i32) -> &'a i32. В відміну від drop, ми маємо єдину функцію , яка одночасно задовольняє Fn(&'a i32) -> &'a i32для кожної життя 'a. Це відображається в тому, як passможна використовувати.

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);
}

(дитячий майданчик)

У прикладі, життя 'aі 'bвзагалі не мають відношення один до одного: ні повністю не охоплюють іншого. Так що тут не відбувається якась підробка. Один екземпляр passдійсно використовується з двома різними, не пов'язаними між собою життями.

Ось чому dropце не задовольняє for<'a> FnOnce(&'a T). Будь-який конкретний екземпляр dropможе охоплювати лише одне життя (ігноруючи підтипи). Якби ми перейшли dropдо two_usesнаведеного вище прикладу (з незначними змінами підпису та припускаючи, що компілятор дозволить нам), йому доведеться вибрати певний термін експлуатації, 'aа примірник dropв області застосування two_usesбув би Fn(&'a i32)протягом певного конкретного життя 'a. Оскільки функція застосовуватиметься лише до одного періоду життя 'a, неможливо було б використовувати її у два непов'язані періоди життя.

То чому ж закриття туалету отримує hrtb? Якщо виводити тип для закриття, якщо очікуваний тип натякає на необхідність прив’язки вищої категорії, компілятор спробує зробити один відповідним . У цьому випадку це вдається.


Випуск №41078 тісно пов’язаний із цим, зокрема коментар Eddyb тут по суті дає пояснення вище (правда, у контексті закриття, а не звичайних функцій). Однак ця проблема не стосується цієї проблеми. Натомість він вирішує питання, що станеться, якщо ви призначите закриття туалету змінною перед її використанням (спробуйте!).

Можливо, що ситуація зміниться в майбутньому, але це вимагатиме досить великих змін у тому, як родові функції мономорфізуються.


4

Коротше кажучи, обидві лінії повинні вийти з ладу. Але оскільки один крок старого способу обробки hrtb життя, а саме перевірка на герметичність , в даний час має певну проблему з надійністю, rustcзакінчується (неправильно) прийняттям одного і залишенням другого з досить поганим повідомленням про помилку.

Якщо ви відключите перевірку витоку rustc +nightly -Zno-leak-check, ви зможете побачити більш розумне повідомлення про помилку:

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,)>`

Моя інтерпретація цієї помилки полягає в тому, що &xв тілі fooфункції лише термін експлуатації обмежений зазначеним тілом, тому f(&x)також є такий самий термін експлуатації, який не може задовольнити for<'a>універсальне кількісне визначення, яке вимагає пов'язана ознака.

Питання, яке ви подаєте тут, майже ідентичне питання № 57642 , яке також має дві контрастні частини.

Новий спосіб опрацювати життя hrtb - це використання так званих всесвітів . Ніко має ВІП для вирішення питань протікання всесвітів. Відповідно до цього нового режиму, обидві частини випуску № 57642, пов'язані вище, вважаються невдалими при більш чітких діагнозах. Я припускаю, що до того часу компілятор повинен мати можливість правильно обробляти ваш прикладний код.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.