Реализуемая Rust модель памяти оставляет свой отпечаток на всем, включая такие вещи как замыкания и функции обратного вызова. Привычные по другим языкам концепции в случае с Rust начинают вести себя иначе и далеко не с первого взгляда очевидно почему такое происходит.
В Rust имеются два вида замыканий: стековые и уникальные и указатели на функции. В некоторых случаях замыкания взаимозаменяемы и совместимы с указатели на функции, в некоторых нет. Поведение данных замыканий идентично поведению стековых данных и данных адресуемых посредствам уникальных указателей. Так как весь этот набор выглядит довольно обширным, то мне кажется что лучше всего разбираться на примерах.
При описании уникальных замыканих используется ключевое слово
func(l, r); // (1)
func(l, r); // (2)
}
Уникальное замыкание передается в качестве третьего параметра и на первый вгляд не сильно отличается от обычной функции
`func` moved here because it has type `proc:Send(int, int) -> int`, which is non-copyable (perhaps you meant to use clone()?)
Вопрос того, что с чем совместимо и почему лучше всего рассматривать на живых примерах, поэтому, для эксперементов понадобится немного вспомогательного кода:
a + b
}
fn main() {
let cl = |a: int, b: int| { // (2)
a + b
};
...
А именно: внешняя функция
do call_proc(1, 2) |a, b| { // (2)
a + b
}
call_proc(1, 2, test_fn); // (3)
Так как все три имеющихся в Rust пула памяти (стек, куча обмена и локальная куча) обладают принципиально разным поведением, каких-либо конверсий между ними не предусмотренно. Данная особенность отражается и на поведении замыканий. Так, компилятор не допустит использования стекового замыкания
(expected ~ closure, found & closure)
Это вызванно тем, что стековый объект копируется, а не реализаует семантику владения и не может быть отправлен в другую задачу. А вот различий между внешней фукнцией и уникальным замыканием куда меньше. Внешняя функция, так же как и уникальное замыкание, самодостаточна, не имеет собсвенного состояния, не требует себя копировать куда-либо и может быть вызвана из любой задачи. Как следствие, внешняя функция может
Функция принимающая в качестве аргумента другую функцию будет выглядеть следующим образом, где
func(l, r);
}
На функции, в отличие от уникальных замыканий, каких-либо ограничений по количеству вызовов не налагается и ничто не мешает сделать больше чем один вызов одной и той же функции.
do call_fn(1, 2) |a, b| { // (2)
a + b
}
call_fn(1, 2, test_fn); // тут и так все очевидно, правда?
Стековое замыкание
Хотя внешняя функция может использоваться вместо уникального замыкания, обратной совместимости между ними нет
Объявление стекового замыканя сильно отличается от предыдущих примеров не только синтаксисом, но и необходимостью явно указывать время жизни, при этом каких-либо ограничений на количество вызовов в стековых замыканиях нет.
func(l, r);
}
call_closure(1, 2, test_fn); // (1)
do call_closure(1, 2) |a, b| { // (2)
a + b
}
Наиболее универсальная конструкция, внешняя функция, вполне
Так что, несмотря на всю логичность модели памяти Rust, ей необходимо уделять пристальное внимание при изучении это великолепного языка. Данная заметка – вторая и не последняя заметка, из серии посвященной модели памяти Rust.