web-dev-qa-db-fra.com

Retour d'une fermeture d'une fonction

Note: Cette question a été posée avant la première version stable de Rust. Il y a eu beaucoup de changements depuis et la syntaxe utilisée dans la fonction n'est même plus valide. Pourtant, la réponse de Shepmaster est excellente et rend cette question intéressante.


Enfin, les fermetures sans boîte ont atterri, alors je les expérimente pour voir ce que vous pouvez faire.

J'ai cette fonction simple:

fn make_adder(a: int, b: int) -> || -> int {
    || a + b
}

Cependant, je reçois un missing lifetime specifier [E0106] Erreur. J'ai essayé de résoudre ce problème en changeant le type de retour en ||: 'static -> int, mais je reçois une autre erreur cannot infer an appropriate lifetime due to conflicting requirements.

Si je comprends bien, la fermeture est déballée donc elle possède a et b. Il me semble très étrange qu'il ait besoin d'une vie. Comment puis-je réparer cela?

34
aochagavia

À partir de Rust 1.26, vous pouvez utiliser impl trait :

fn make_adder(a: i32) -> impl Fn(i32) -> i32 {
    move |b| a + b
}

fn main() {
    println!("{}", make_adder(1)(2));
}

Cela permet de retourner une fermeture sans boîte même s'il est impossible de spécifier le type exact de la fermeture.

Cela ne vous aidera pas si l'une d'entre elles est vraie:

  1. Vous ciblez Rust avant cette version

  2. Vous avez tout type de conditionnel dans votre fonction:

    fn make_adder(a: i32) -> impl Fn(i32) -> i32 {
        if a > 0 {
            move |b| a + b
        } else {
            move |b| a - b
        }
    }
    

    Ici, il n'y a pas un seul type de retour; chaque fermeture a un type unique et non nommable.

  3. Vous devez pouvoir nommer le type retourné pour une raison quelconque:

    struct Example<F>(F);
    
    fn make_it() -> Example<impl Fn()> {
        Example(|| println!("Hello"))
    }
    
    fn main() {
        let unnamed_type_ok = make_it();
        let named_type_bad: /* No valid type here */ = make_it();
    }
    

    Vous ne pouvez pas ( encore ) utiliser impl SomeTrait comme type de variable.

Dans ces cas, vous devez utiliser l'indirection. La solution courante est un objet trait, comme décrit dans l'autre réponse .

36
Shepmaster

Il est possible de renvoyer des fermetures à l'intérieur de Boxes, c'est-à-dire sous la forme d'objets trait mettant en œuvre certains traits:

fn make_adder(a: i32) -> Box<Fn(i32) -> i32> {
    Box::new(move |b| a + b)
}

fn main() {
    println!("{}", make_adder(1)(2));
}

(essayez-le ici )

Il y a aussi n RFC ( son problème de suivi ) sur l'ajout de types de retour abstraits non encadrés qui permettraient de renvoyer les fermetures par valeur, sans cases, mais ce RFC a été reporté. Selon la discussion dans ce RFC, il semble que certains travaux ont été effectués récemment, il est donc possible que les types de retour abstraits non en boîte soient disponibles assez rapidement.

26
Vladimir Matveev

La syntaxe || Est toujours les anciennes fermetures encadrées, donc cela ne fonctionne pas pour la même raison qu'auparavant.

Et, cela ne fonctionnera même pas en utilisant la syntaxe de fermeture appropriée |&:| -> int, Car il s'agit littéralement de sucre pour certains traits. Pour le moment, la syntaxe du sucre est |X: args...| -> ret, Où le X peut être &, &mut Ou rien, correspondant au Fn , FnMut , FnOnce traits, vous pouvez également écrire Fn<(args...), ret> etc. pour la forme non sucrée. Le sucre est susceptible de changer (peut-être quelque chose comme Fn(args...) -> ret).

Chaque fermeture sans boîte possède un type unique et innommable généré en interne par le compilateur: la seule façon de parler des fermetures sans boîte est via les génériques et les limites des traits. En particulier, l'écriture

fn make_adder(a: int, b: int) -> |&:| -> int {
    |&:| a + b
}

c'est comme écrire

fn make_adder(a: int, b: int) -> Fn<(), int> {
    |&:| a + b
}

c'est-à-dire dire que make_adder renvoie une valeur de trait non encadrée; ce qui n'a pas beaucoup de sens pour le moment. La première chose à essayer serait

fn make_adder<F: Fn<(), int>>(a: int, b: int) -> F {
    |&:| a + b
}

mais cela signifie que make_adder renvoie tout F que l'appelant choisit, alors que nous voulons dire qu'il renvoie certains types fixes (mais "cachés"). Cela exigeait types de retour abstraits , qui dit, fondamentalement, "la valeur de retour implémente ce trait" tout en étant non encapsulée et résolue statiquement. Dans la langue de cette RFC (temporairement fermée),

fn make_adder(a: int, b: int) -> impl Fn<(), int> {
    |&:| a + b
}

Ou avec le sucre de fermeture.

(Un autre point mineur: je ne suis pas sûr à 100% des fermetures sans boîte, mais les anciennes fermetures capturent certainement toujours les choses par référence, ce qui est une autre chose qui coule le code comme proposé dans le problème. Ceci est corrigé dans # 1661 .)

4
huon