Как передать функцию в качестве параметра в Rust?
Использование трейтов Fn, FnMut и FnOnce для передачи функций
В Rust функции передаются через трейты-замыкания: Fn, FnMut и FnOnce. Они позволяют гибко работать как с обычными функциями, так и с замыканиями (closures). Вот правильное решение вашей задачи:
fn apply<F>(func: F, x: i32) -> i32
where
F: Fn(i32) -> i32,
{
func(x)
}
fn double(x: i32) -> i32 {
x * 2
}
fn main() {
let result = apply(double, 5);
println!("{}", result); // Выведет: 10
// Также можно передать замыкание:
let triple = |x: i32| x * 3;
let result2 = apply(triple, 5);
println!("{}", result2); // Выведет: 15
}
Разбор решения
- Обобщённый параметр
F— позволяет принимать любой тип, реализующий нужный трейт. Fn(i32) -> i32— трейт, означающий, что функция может быть вызвана многократно без изменения состояния. Подходит для обычных функций и замыканий, которые не захватывают изменяемые переменные.- where-ограничение — явно указывает, что тип
Fдолжен реализовывать трейтFn.
Разница между Fn, FnMut и FnOnce
Выбор трейта зависит от того, как функция будет использоваться:
Fn— функция может быть вызвана сколько угодно раз, не изменяя захваченные данные. Используйте по умолчанию.FnMut— функция может изменять захваченные переменные (мутабельная ссылка). Пример:
fn apply_mut<F>(mut func: F, x: i32) -> i32
where
F: FnMut(i32) -> i32,
{
func(x)
}
let mut multiplier = 2;
let mut closure = |x: i32| x * multiplier;
multiplier = 3; // Ошибка! closure уже захватил multiplier
// Правильнее:
let mut closure = |x: i32| x * multiplier;
apply_mut(closure, 5);
FnOnce— функция может быть вызвана только один раз. Используется, когда замыкание потребляет захваченные значения (перемещает их).
fn apply_once<F>(func: F, x: i32) -> i32
where
F: FnOnce(i32) -> i32,
{
func(x)
}
let name = String::from("Hello");
let closure = |x: i32| {
println!("{}", name); // name перемещается в замыкание
x + 1
};
apply_once(closure, 5); // Работает
// closure(); // Ошибка: closure уже перемещён
Практический совет
Если вы не уверены, какой трейт использовать, начинайте с Fn. Компилятор подскажет, если нужно FnMut или FnOnce. Для обычных функций (не замыканий) всегда подходит Fn.
Полный пример с разными типами функций
fn main() {
// Обычная функция
fn add_one(x: i32) -> i32 { x + 1 }
println!("{}", apply(add_one, 10)); // 11
// Замыкание без захвата
let add_two = |x: i32| x + 2;
println!("{}", apply(add_two, 10)); // 12
// Срез как аргумент (функция, принимающая &str)
fn process<F>(f: F, s: &str) where F: Fn(&str) -> String {
println!("{}", f(s));
}
process(|s| format!("Hello, {}!", s), "World");
}
<<Альтернативный подход: указатели на функции (fn pointer)
Если вам не нужны замыкания и вы работаете только с обычными функциями (не closures), можно использовать тип fn(i32) -> i32 — это указатель на функцию. Он проще, но менее гибкий:
fn apply(func: fn(i32) -> i32, x: i32) -> i32 {
func(x)
}
fn double(x: i32) -> i32 {
x * 2
}
fn main() {
let result = apply(double, 5);
println!("{}", result); // 10
// Замыкания без захвата тоже можно передать, но не все:
let triple = |x: i32| x * 3;
// apply(triple, 5); // Ошибка! Замыкание — не fn pointer
}
Когда использовать fn pointer?
- Когда нужно передать только именованные функции (не замыкания).
- Для совместимости с C-кодом (extern "C" fn).
- Когда важна минимальная сложность кода.
Недостатки
Указатели на функции не работают с замыканиями, которые захватывают переменные. Также они не поддерживают мутабельное состояние. В большинстве случаев рекомендуется использовать обобщённый подход с трейтами Fn, FnMut или FnOnce.
Сравнение
// fn pointer — работает только с обычными функциями
fn with_fn_ptr(f: fn(i32) -> i32, x: i32) -> i32 { f(x) }
// Fn — работает с функциями и замыканиями (без захвата мутабельных данных)
fn with_fn<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x) }
// Пример использования
fn square(x: i32) -> i32 { x * x }
let closure = |x: i32| x + 10;
println!("{}", with_fn_ptr(square, 5)); // 25
// println!("{}", with_fn_ptr(closure, 5)); // Ошибка!
println!("{}", with_fn(closure, 5)); // 15