Perché `std :: mem :: drop` non è esattamente uguale alla chiusura | _ | () nei limiti dei tratti di livello superiore?


13

L'implementazione di std::mem::dropè documentata come la seguente:

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

In quanto tale, mi aspetterei che la chiusura |_| ()(colloquialmente nota come chiusura del gabinetto ) sia un potenziale sostituto 1: 1 dropin entrambe le direzioni. Tuttavia, il codice seguente mostra che dropnon è compatibile con un tratto più alto associato al parametro della funzione, mentre la chiusura della toilette è.

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

Il messaggio di errore del compilatore:

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

Considerando che dropsi suppone sia generico rispetto a qualsiasi dimensione T, sembra irragionevole che la firma "più generica" fn(_) -> _non sia compatibile for<'a> fn (&'a _) -> _. Perché il compilatore non ammette la firma di dropqui, e cosa lo rende diverso quando la chiusura della toilette è al suo posto?

Risposte:


4

Il nocciolo del problema è che dropnon è una singola funzione, ma piuttosto un insieme parametrico di funzioni che rilasciano un tipo particolare. Per soddisfare un limite di tratto superiore (di seguito hrtb), avresti bisogno di una singola funzione che può prendere simultaneamente riferimenti a un tipo con una data durata.


Useremo dropcome esempio tipico di una funzione generica, ma tutto ciò si applica anche più in generale. Ecco il codice di riferimento: fn drop<T>(_: T) {}.

Concettualmente, dropnon è una singola funzione, ma piuttosto una funzione per ogni tipo possibile T. Qualsiasi istanza particolare di dropaccetta solo argomenti di un singolo tipo. Questo si chiama monomorfizzazione . Se Tviene utilizzato dropun diverso, dropviene compilata una versione diversa di . Ecco perché non puoi passare una funzione generica come argomento e utilizzare quella funzione in generale (vedi questa domanda )

D'altra parte, una funzione come fn pass(x: &i32) -> &i32 {x}soddisfa l'hrtb for<'a> Fn(&'a i32) -> &'a i32. A differenza drop, abbiamo una singola funzione che soddisfa contemporaneamente Fn(&'a i32) -> &'a i32per ogni vita 'a. Ciò si riflette su come passpuò essere utilizzato.

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

(terreno di gioco)

Nell'esempio, le vite 'ae 'bnon hanno alcuna relazione l'una con l'altra: né abbraccia completamente l'altro. Quindi non c'è una sorta di sottotitolo che sta succedendo qui. Una singola istanza di passviene realmente utilizzata con due vite diverse, non correlate.

Questo è il motivo per cui dropnon soddisfa for<'a> FnOnce(&'a T). Qualsiasi istanza particolare di droppuò coprire solo una vita (ignorando il sottotipo). Se siamo passati dropin two_usesdall'esempio precedente (con lievi modifiche di firma e supponendo che il compilatore ci ha lasciato), avrebbe dovuto scegliere alcuni particolari di vita 'ae l'istanza di dropnel perimetro di two_usessarebbe Fn(&'a i32)per qualche concreta vita 'a. Poiché la funzione si applica solo alla singola vita 'a, non sarebbe possibile utilizzarla con due vite non correlate.

Quindi, perché la chiusura del water ottiene un hrtb? Quando si deduce il tipo per una chiusura, se il tipo previsto suggerisce che è necessario un limite di tratto di livello superiore, il compilatore tenterà di adattarlo . In questo caso, ci riesce.


Il numero 41078 è strettamente correlato a questo e in particolare, il commento di eddyb qui fornisce essenzialmente la spiegazione sopra (sebbene nel contesto delle chiusure, piuttosto che delle normali funzioni). Il problema in sé non risolve tuttavia il problema attuale. Si rivolge invece a ciò che accade se si assegna la chiusura del WC a una variabile prima di usarla (provalo!).

È possibile che la situazione cambi in futuro, ma richiederebbe un cambiamento piuttosto grande nel modo in cui le funzioni generiche sono monomorfizzate.


4

In breve, entrambe le linee dovrebbero fallire. Ma dal momento che un passo nel vecchio modo di gestire la vita di hrtb, vale a dire il controllo delle perdite , attualmente ha qualche problema di solidità, rustcfinisce per (erroneamente) accettarne uno e lasciare l'altro con un messaggio di errore piuttosto grave.

Se disabiliti il ​​controllo delle perdite con rustc +nightly -Zno-leak-check, sarai in grado di vedere un messaggio di errore più sensibile:

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

La mia interpretazione di questo errore è che &xnel corpo della foofunzione ha solo una durata dell'ambito limitata a detto corpo, quindi f(&x)ha anche la stessa durata dell'ambito che non può possibilmente soddisfare la for<'a>quantificazione universale richiesta dal limite del tratto.

La domanda che poni qui è quasi identica al numero 57642 , che ha anche due parti contrastanti.

Il nuovo modo di elaborare la vita di hrtb consiste nell'utilizzare i cosiddetti universi . Niko ha un WIP per affrontare il controllo delle perdite con gli universi. In base a questo nuovo regime, si dice che entrambe le parti del problema n. 57642 sopra collegate falliscono con diagnosi molto più chiare. Suppongo che anche allora il compilatore dovrebbe essere in grado di gestire correttamente il codice di esempio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.