Как мне сохранить замыкание в структуре в 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,
}
Я думаю, что это медленнее, потому что
- разыменование указателя
- нет специализации для типа
FnMut
, который фактически используется
Ответы
Ответ 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
я создаю?
Это неназванный, автоматически сгенерированный тип.
Я мог бы также использовать ссылку [...] медленнее, потому что [...] указатель разыграен [...] без специализации
Возможно, но это может быть намного проще для вызывающего абонента.
Смотрите также: