Почему `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компилируется. Вот почему вы не можете передать обобщенную функцию в качестве аргумента и использовать эту функцию в полной общности (см. Этот вопрос )

С другой стороны, функция like 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 тесно связана с этим, и, в частности, комментарий Эддиба здесь дает по существу объяснение выше (хотя в контексте замыканий, а не обычных функций). Сама проблема не решает существующую проблему все же. Вместо этого он обращается к тому, что происходит, если вы назначаете унитаз на переменную перед его использованием (попробуйте!).

Возможно, что ситуация изменится в будущем, но это потребует довольно большого изменения в том, как универсальные функции мономорфизируются.


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 - использование так называемых вселенных . Нико имеет WIP, чтобы справиться с проверкой утечек с помощью вселенных. В соответствии с этим новым режимом обе части проблемы № 57642, упомянутой выше, как говорят, терпят неудачу с гораздо более четкими диагнозами. Я полагаю, что к тому времени компилятор также сможет правильно обрабатывать ваш пример кода.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.