web-dev-qa-db-fra.com

Comment puis-je stocker une fermeture dans une structure à Rust?

Avant Rust 1.0, je pouvais écrire une structure en utilisant cette syntaxe de fermeture obsolète:

struct Foo {
    pub foo: |usize| -> usize,
}

Maintenant, je peux faire quelque chose comme:

struct Foo<F: FnMut(usize) -> usize> {
    pub foo: F,
}

Mais alors quel est le type d'un objet Foo que je crée?

let foo: Foo<???> = Foo { foo: |x| x + 1 };

Je pourrais également utiliser une référence:

struct Foo<'a> {
    pub foo: &'a mut FnMut(usize) -> usize,
}

Je pense que c'est plus lent car

  1. la déréférence du pointeur
  2. il n'y a pas de spécialisation pour le type de FnMut qui finit par être utilisé
31
bfops

Les fermetures à l'ancienne qui utilisaient la syntaxe || Faisaient référence à des fermetures stockées sur la pile, ce qui les rend équivalentes à &'a mut FnMut(usize) -> usize. Les anciens procs étaient alloués en tas et équivalaient à Box<dyn FnOnce(usize) -> usize> (vous ne pouvez appeler une proc qu'une seule fois).

Quant au type que vous utiliseriez dans votre troisième extrait de code, il n'est pas un; les types de fermeture sont anonymes et ne peuvent pas être directement nommés. Au lieu de cela, vous écririez:

let foo = Foo { foo: |x| x + 1 };

Si vous écrivez du code dans un contexte où vous besoin pour spécifier que vous voulez un Foo, vous écririez:

let foo: Foo<_> = Foo { foo: |x| x + 1 };

Le _ Indique au système de types de déduire le type générique réel pour vous.

La règle générale pour qui à utiliser, dans l'ordre décroissant:

  • Paramètres génériques: struct Foo<F: FnMut(usize) -> usize>. C'est le plus efficace, mais cela signifie qu'une instance Foo spécifique ne peut stocker que one fermeture, car chaque fermeture a un type de béton différent.
  • Références de caractère: &'a mut dyn FnMut(usize) -> usize. Il y a une indirection de pointeur, mais maintenant vous pouvez stocker une référence à toute fermeture qui a une signature d'appel compatible.
  • Fermetures en boîte: Box<dyn FnMut(usize) -> usize>. Cela implique d'allouer la fermeture sur le tas, mais vous n'avez pas à vous soucier des durées de vie. Comme pour une référence, vous pouvez stocker n'importe quelle fermeture avec une signature compatible.
30
DK.

Compléter la réponse existante avec un peu plus de code à des fins de démonstration:

Fermeture sans boîte

Utilisez un type générique:

struct Foo<F>
where
    F: Fn(usize) -> usize,
{
    pub foo: F,
}

fn main() {
    let foo = Foo { foo: |a| a + 1 };
    (foo.foo)(42);
}

Objet trait en boîte

struct Foo {
    pub foo: Box<dyn Fn(usize) -> usize>,
}

fn main() {
    let foo = Foo {
        foo: Box::new(|a| a + 1),
    };
    (foo.foo)(42);
}

Référence d'objet trait

struct Foo<'a> {
    pub foo: &'a dyn Fn(usize) -> usize,
}

fn main() {
    let foo = Foo { foo: &|a| a + 1 };
    (foo.foo)(42);
}

Pointeur de fonction

struct Foo {
    pub foo: fn(usize) -> usize,
}

fn main() {
    let foo = Foo { foo: |a| a + 1 };
    (foo.foo)(42);
}

quel est le type d'un objet Foo que je crée?

C'est un type innommable, généré automatiquement.

Je pourrais aussi utiliser une référence [...] plus lente car le pointeur [...] ne deref aucune spécialisation

Peut-être, mais cela peut être beaucoup plus facile pour l'appelant.

Voir également:

11
Shepmaster