Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Трейты замыканий

Замыкания, или лямбда-выражения, имеют типы, которые нельзя назвать. Однако они реализуют специальные трейты Fn, FnMut и FnOnce:

Специальные типы fn(..) -> T обозначают указатели на функции: либо адрес функции, либо замыкание, которое ничего не захватывает.

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn apply_and_log(
    func: impl FnOnce(&'static str) -> String,
    func_name: &'static str,
    input: &'static str,
) {
    println!("Calling {func_name}({input}): {}", func(input))
}

fn main() {
    let suffix = "-itis";
    let add_suffix = |x| format!("{x}{suffix}");
    apply_and_log(&add_suffix, "add_suffix", "senior");
    apply_and_log(&add_suffix, "add_suffix", "appendix");

    let mut v = Vec::new();
    let mut accumulate = |x| {
        v.push(x);
        v.join("/")
    };
    apply_and_log(&mut accumulate, "accumulate", "red");
    apply_and_log(&mut accumulate, "accumulate", "green");
    apply_and_log(&mut accumulate, "accumulate", "blue");

    let take_and_reverse = |prefix| {
        let mut acc = String::from(prefix);
        acc.push_str(&v.into_iter().rev().collect::<Vec<_>>().join("/"));
        acc
    };
    apply_and_log(take_and_reverse, "take_and_reverse", "reversed: ");
}
This slide should take about 10 minutes.

Fn (например, add_suffix) не потребляет и не изменяет захваченные значения. Его можно вызывать, требуя только разделяемой ссылки на замыкание, поэтому замыкание можно выполнять многократно и даже одновременно.

FnMut (например, accumulate) может изменять захваченные значения. Объект замыкания доступен через эксклюзивную ссылку, поэтому его можно вызывать многократно, но не одновременно.

Если у вас есть FnOnce (например, take_and_reverse), его можно вызвать только один раз. Вызов потребляет замыкание и значения, захваченные перемещением.

FnMut является подтипом FnOnce, а Fn — подтипом FnMut и FnOnce. То есть там, где ожидается вызов FnOnce, можно использовать FnMut, а там, где ожидается FnMut или FnOnce, можно использовать Fn.

Когда вы определяете функцию, которая принимает замыкание, берите FnOnce, если можете (то есть вызываете один раз), иначе FnMut, и, наконец, Fn. Так вызывающему коду доступно больше всего вариантов.

Наоборот, когда у вас есть замыкание, наибольшую гибкость даёт Fn (его можно передать в любой из трёх трейтов замыканий), затем FnMut, и, наконец, FnOnce.

Компилятор также выводит Copy (например, для add_suffix) и Clone (например, для take_and_reverse) в зависимости от того, что захватывает замыкание. Указатели на функции (ссылки на элементы fn) реализуют Copy и Fn.