Подтвердить что ты не робот

Как мне сохранить замыкание в структуре в Rust?

До Rust 1.0 я мог написать структуру, используя этот устаревший синтаксис замыкания:

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

Теперь я могу сделать что-то вроде:

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

Но тогда какой тип объекта Foo я создаю?

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

Я также мог бы использовать ссылку:

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

Я думаю, что это медленнее, потому что

  1. разыменование указателя
  2. нет специализации для типа FnMut, который фактически используется
4b9b3361

Ответ 1

Замыкания старого стиля, в которых использовался синтаксис ||, были ссылками на замыкания, хранящиеся в стеке, что делало их эквивалентными &'a mut FnMut(usize) -> usize. proc старого типа были распределены в куче и были эквивалентны Box<dyn FnOnce(usize) -> usize> (вы можете вызвать proc только один раз).

Что касается типа, который вы бы использовали в своем третьем фрагменте кода, его нет; Типы замыканий являются анонимными и не могут быть названы напрямую. Вместо этого вы должны написать:

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

Если вы пишете код в контексте, где вам нужно указать, что вы хотите Foo, вы должны написать:

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

_ говорит системе типов выводить фактический типовой тип для вас.

Общее практическое правило для использования в порядке убывания:

  • Общие параметры: struct Foo<F: FnMut(usize) -> usize>. Это наиболее эффективно, но это означает, что конкретный экземпляр Foo может хранить только одно закрытие, поскольку каждое закрытие имеет свой конкретный тип.
  • Черты ссылок: &'a mut dyn FnMut(usize) -> usize. Есть косвенное указание, но теперь вы можете хранить ссылку на любое замыкание, которое имеет совместимую подпись вызова.
  • Закрытые коробки: Box<dyn FnMut(usize) -> usize>. Это включает в себя распределение замыкания в куче, но вам не нужно беспокоиться о продолжительности жизни. Как и со ссылкой, вы можете хранить любое закрытие с совместимой подписью.

Ответ 2

Дополнить существующий ответ еще кодом для демонстрации:

Закрытая коробка

Используйте универсальный тип:

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

impl<F> Foo<F>
where
    F: Fn(usize) -> usize,
{
    fn new(foo: F) -> Self {
        Self { foo }
    }
}

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

    (Foo::new(|a| a + 1).foo)(42);
}

Объект в штучной упаковке

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

impl Foo {
    fn new(foo: impl Fn(usize) -> usize + 'static) -> Self {
        Self { foo: Box::new(foo) }
    }
}

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

    (Foo::new(|a| a + 1).foo)(42);
}

Ссылка на объект черты

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

impl<'a> Foo<'a> {
    fn new(foo: &'a dyn Fn(usize) -> usize) -> Self {
        Self { foo }
    }
}

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

    (Foo::new(&|a| a + 1).foo)(42);
}

Указатель функции

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

impl Foo {
    fn new(foo: fn(usize) -> usize) -> Self {
        Self { foo }
    }
}

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

    (Foo::new(|a| a + 1).foo)(42);
}

какой тип объекта Foo я создаю?

Это неназванный, автоматически сгенерированный тип.

Я мог бы также использовать ссылку [...] медленнее, потому что [...] указатель разыграен [...] без специализации

Возможно, но это может быть намного проще для вызывающего абонента.

Смотрите также: