Pourquoi «std :: mem :: drop» n'est-il pas exactement le même que la fermeture | _ | () dans les limites de traits de rang supérieur?


13

L'implémentation de std::mem::dropest documentée comme suit:

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

En tant que tel, je m'attendrais à ce que la fermeture |_| ()(familièrement connue sous le nom de fermeture des toilettes ) soit un remplacement potentiel de 1: 1 dropdans les deux sens. Cependant, le code ci-dessous montre que ce dropn'est pas compatible avec un trait de rang supérieur lié au paramètre de la fonction, contrairement à la fermeture des toilettes.

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

Message d'erreur du compilateur:

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

Étant donné que dropc'est censé être générique par rapport à n'importe quelle taille T, il semble déraisonnable que la signature "plus générique" fn(_) -> _ne soit pas compatible avec for<'a> fn (&'a _) -> _. Pourquoi le compilateur n'admet-il pas la signature d' dropici, et qu'est-ce qui le rend différent lorsque la fermeture des toilettes est placée à sa place?

Réponses:


4

Le cœur du problème est qu'il dropne s'agit pas d'une fonction unique, mais plutôt d'un ensemble de fonctions paramétrées qui suppriment chacune un type particulier. Pour satisfaire une limite de trait de rang supérieur (ci-après hrtb), vous auriez besoin d'une seule fonction qui peut simultanément prendre des références à un type avec une durée de vie donnée.


Nous utiliserons dropnotre exemple typique d'une fonction générique, mais tout cela s'applique plus généralement aussi. Voici le code de référence: fn drop<T>(_: T) {}.

Conceptuellement, ce dropn'est pas une fonction unique, mais plutôt une fonction pour chaque type possible T. Toute instance particulière de dropprend uniquement des arguments d'un seul type. C'est ce qu'on appelle la monomorphisation . Si un autre Test utilisé avec drop, une version différente de dropest compilée. C'est pourquoi vous ne pouvez pas passer une fonction générique comme argument et utiliser cette fonction en général (voir cette question )

D'un autre côté, une fonction comme fn pass(x: &i32) -> &i32 {x}satisfait le hrtb for<'a> Fn(&'a i32) -> &'a i32. Contrairement à drop, nous avons une seule fonction qui satisfait simultanément Fn(&'a i32) -> &'a i32pour chaque vie 'a. Cela se reflète dans la façon dont il passpeut être utilisé.

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

(terrain de jeux)

Dans l'exemple, les durées de vie 'aet 'bn'ont aucun rapport les uns avec les autres: ni l'un ni l'autre n'englobe complètement l'autre. Il n'y a donc pas de sous-typage ici. Une seule instance de passest réellement utilisée avec deux durées de vie différentes et sans rapport.

C'est pourquoi dropne satisfait pas for<'a> FnOnce(&'a T). Toute instance particulière de dropne peut couvrir qu'une seule vie (en ignorant le sous-typage). Si nous avons passé dropdans two_usesl'exemple ci - dessus (avec de légères modifications de signature et en supposant que le compilateur nous laisse), il faudrait choisir une vie particulière 'aet l'instance de dropla portée de two_usesserait Fn(&'a i32)pour quelque béton vie 'a. Étant donné que la fonction ne s'appliquerait qu'à une seule durée de vie 'a, il ne serait pas possible de l'utiliser avec deux durées de vie non liées.

Alors, pourquoi la fermeture des toilettes obtient-elle un hrtb? Lors de la déduction du type d'une fermeture, si le type attendu indique qu'une limite de trait de rang supérieur est nécessaire, le compilateur essaiera de faire un ajustement . Dans ce cas, cela réussit.


Le problème # 41078 est étroitement lié à cela et en particulier, le commentaire d'eddyb ici donne essentiellement l'explication ci-dessus (bien que dans le contexte de fermetures, plutôt que de fonctions ordinaires). Le problème lui-même ne résout cependant pas le problème actuel. Il traite plutôt de ce qui se passe si vous attribuez la fermeture des toilettes à une variable avant de l'utiliser (essayez-la!).

Il est possible que la situation change à l'avenir, mais cela nécessiterait un changement assez important dans la façon dont les fonctions génériques sont monomorphisées.


4

En bref, les deux lignes devraient échouer. Mais comme une étape dans l'ancienne façon de gérer les durées de vie hrtb, à savoir le contrôle des fuites , présente actuellement un problème de solidité, rustcfinit par accepter (incorrectement) l'un et en laissant l'autre avec un message d'erreur assez mauvais.

Si vous désactivez la vérification des fuites avec rustc +nightly -Zno-leak-check, vous pourrez voir un message d'erreur plus sensible:

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

Mon interprétation de cette erreur est que le &xdans le corps de la foofonction n'a qu'une durée de vie de portée limitée audit corps, donc a f(&x)également la même durée de vie de portée qui ne peut probablement pas satisfaire la for<'a>quantification universelle requise par le trait lié.

La question que vous présentez ici est presque identique au numéro 57642 , qui comporte également deux parties contrastées.

La nouvelle façon de traiter les durées de vie hrtb est d'utiliser des soi-disant univers . Niko a un WIP pour s'attaquer au contrôle des fuites avec les univers. Sous ce nouveau régime, les deux parties du problème # 57642 liées ci-dessus échoueraient toutes avec des diagnostics beaucoup plus clairs. Je suppose que le compilateur devrait être capable de gérer correctement votre exemple de code d'ici là aussi.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.