Dlaczego `std :: mem :: drop` nie jest dokładnie taki sam jak zamknięcie | _ | () w wyższych zakresach cech?

13

Realizacja std::mem::dropjest udokumentowana następująco:

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

Jako taki, oczekiwałbym, że zamknięcie |_| ()(potocznie zwane zamknięciem toalety ) będzie potencjalnym zamiennikiem 1: 1 dropw obu kierunkach. Jednak poniższy kod pokazuje, że dropnie jest zgodny z cechą wyższego rzędu związaną z parametrem funkcji, podczas gdy zamknięcie toalety jest.

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

Komunikat o błędzie kompilatora:

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

Biorąc pod uwagę, że dropjest to podobno ogólne w odniesieniu do dowolnej wielkości T, brzmi nieuzasadnione, że „bardziej ogólny” podpis fn(_) -> _nie jest zgodny for<'a> fn (&'a _) -> _. Dlaczego kompilator nie przyjmuje droptutaj podpisu i co go wyróżnia, gdy zamiast niego znajduje się zamknięcie toalety?

E_net4 jest niebezpieczny
źródło

Odpowiedzi:

4

Istotą problemu jest to, że dropnie jest to pojedyncza funkcja, ale raczej sparametryzowany zestaw funkcji, z których każda upuszcza jakiś określony typ. Aby spełnić wyższą rangę związaną z cechą (dalej hrtb), potrzebujesz jednej funkcji, która może jednocześnie odwoływać się do typu o dowolnym okresie życia.


Użyjemy dropjako naszego typowego przykładu funkcji ogólnej, ale wszystko to dotyczy również bardziej ogólnie. Oto kod dla odniesienia: fn drop<T>(_: T) {}.

Pod względem koncepcyjnym dropnie jest to pojedyncza funkcja, ale raczej jedna funkcja dla każdego możliwego typu T. Każde konkretne wystąpienie dropprzyjmuje tylko argumenty jednego typu. Nazywa się to monomorfizacją . Jeśli Tużywany dropjest inny, dropkompilowana jest inna wersja . Dlatego nie można przekazać funkcji ogólnej jako argumentu i używać tej funkcji w pełnej ogólności (patrz to pytanie )

Z drugiej strony funkcja taka jak fn pass(x: &i32) -> &i32 {x}hrtb for<'a> Fn(&'a i32) -> &'a i32. W przeciwieństwie do dropmamy pojedynczą funkcję, która spełnia równocześnie Fn(&'a i32) -> &'a i32dla każdego życia 'a. Znajduje to odzwierciedlenie w sposobie passkorzystania.

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

(plac zabaw)

W przykładzie czasy życia 'ai 'bnie mają ze sobą żadnego związku: żaden z nich całkowicie nie obejmuje siebie nawzajem. Więc nie dzieje się tutaj żadna podtytuł. Pojedyncza instancja passjest naprawdę używana w dwóch różnych, niezwiązanych ze sobą okresach życia.

Dlatego dropnie spełnia for<'a> FnOnce(&'a T). Każde konkretne wystąpienie dropmoże obejmować tylko jeden okres istnienia (ignorowanie podtypów). Jeśli mijaliśmy dropsię two_usesz powyższego przykładu (z niewielkimi zmianami podpisu i przy założeniu, że kompilator niech nas), to trzeba wybrać jakąś szczególną żywotność 'ai wystąpienie dropw zakresie two_usesbyłaby Fn(&'a i32)dla jakiegoś betonowego życia 'a. Ponieważ funkcja dotyczyłaby tylko jednego życia 'a, nie byłoby możliwe użycie jej z dwoma niepowiązanymi okresami życia.

Więc dlaczego zamknięcie toalety dostaje hrtb? Podczas wnioskowania o typie zamknięcia, jeśli oczekiwany typ wskazuje, że potrzebne jest ograniczenie cechy wyższej rangi, kompilator spróbuje dopasować jeden . W tym przypadku się to udaje.


Problem # 41078 jest ściśle związane z tym, w szczególności, komentarz eddyb jest tutaj daje zasadniczo wyjaśnienie powyżej (choć w kontekście zamknięcia, zamiast zwykłych funkcji). Sam problem nie rozwiązuje jednak obecnego problemu. Zamiast tego zajmuje się tym, co się stanie, jeśli przypiszesz zamknięcie toalety do zmiennej przed jej użyciem (wypróbuj!).

Możliwe, że sytuacja ulegnie zmianie w przyszłości, ale wymagałoby to dość dużej zmiany w sposobie monomorfizacji funkcji ogólnych.

SCappella
źródło
4

Krótko mówiąc, obie linie powinny zawieść. Ale ponieważ jeden krok w starym sposobie obsługi czasu życia hrtb, a mianowicie kontrola szczelności , ma obecnie pewien problem z dźwiękiem , rustckończy się (niepoprawnie) zaakceptowaniem jednego i pozostawieniem drugiego z dość złym komunikatem o błędzie.

Jeśli wyłączysz sprawdzanie nieszczelności za pomocą rustc +nightly -Zno-leak-check, zobaczysz bardziej sensowny komunikat o błędzie:

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

Moją interpretacją tego błędu jest to, że &xw ciele foofunkcji ma tylko okres istnienia zakresu ograniczony do wspomnianego ciała, a więc f(&x)ma również ten sam okres istnienia zakresu, który nie jest w stanie spełnić for<'a>uniwersalnej kwantyfikacji wymaganej przez związaną cechę.

Pytanie, które tu przedstawiłeś, jest prawie identyczne z wydaniem # 57642 , które również ma dwie kontrastujące części.

Nowym sposobem przetwarzania czasu życia hrtb jest użycie tak zwanych wszechświatów . Niko ma PWT, aby poradzić sobie z kontrolą szczelności za pomocą wszechświatów. Zgodnie z tym nowym reżimem obie części numeru 57642, które zostały połączone powyżej, ponoszą porażkę, a diagnozy są znacznie bardziej wyraźne. Podejrzewam, że do tego czasu kompilator powinien być w stanie poprawnie obsługiwać przykładowy kod.

edwardw
źródło