Closures

Closures sind anonyme Funktionen, die Variablen aus ihrer Umgebung erfassen können. Das Erfassen von Variablen aus der Umgebung durch Closures nennt sich auch Currying. Beispielsweise gibt f den Wert von x zurück und hat selbst keine Argumente.

#![allow(unused)]
fn main() {
let x = vec![0];
let f = || x;
assert_eq!(f()[0], 0);
}

Wenn wir f aber ein 2tes Mal aufrufen, bekommen wir einen Fehler.

#![allow(unused)]
fn main() {
// kompiliert nicht
let x = vec![0];
let f = || x;
f();
assert_eq!(f()[0], 0);
}

Die Rückgabe von x ist ein Move. Den können wir nur einmal ausführen. Daher können wir f auch nur einmal aufrufen. Closures, die mindestens einmal aufgerufen werden können, implementieren den Trait FnOnce.

fn sort<F>(f: F) -> Vec<i32> 
where
    F: FnOnce() -> Vec<i32> 
{
    let mut v = f();
    v.sort();
    v
}
fn main() {
    let x = vec![1, 0, 2];
    let f = || x;
    let sorted = sort(f);
    assert_eq!(sorted, vec![0, 1, 2]);
}

Wir klonen den Rückgabewert von f, damit wir f mehrfach aufrufen können. Der Trait für beliebig oft aufrufbare Closures heißt Fn.

fn sort<F>(f: F) -> Vec<i32> 
where
    F: Fn() -> Vec<i32> 
{
    f();
    let mut v = f();
    v.sort();
    v
}
fn main() {
    let x = vec![1, 0, 2];
    let f = || x.clone();
    let sorted = sort(f);
    assert_eq!(sorted, vec![0, 1, 2]);
    f();
}

Bei der Übergabe an sort findet ein Move oder eine Kopie von f und aller erfassten Variablen von f statt. Die Variable x aus der Umgebung wird in diesem Fall per Referenz erfasst. Da Referenzen kopierbar sind, kann man f nach Übergabe an sort erneut ausführen. Durch Verwendung des Schlüsselworts move werden die Variablen nicht mehr per Referenz erfasst, sondern verschoben oder kopiert abhängig vom Vorhandensein einer Copy-Implementierung. Mit move ist ein Aufrufen fs nach Übergabe an sort nicht möglich.

// kompiliert nicht
fn sort<F>(f: F) -> Vec<i32> 
where
    F: Fn() -> Vec<i32> 
{
    let mut v = f();
    v.sort();
    v
}
fn main() {
    let x = vec![1, 0, 2];
    let f = move || x.clone();
    let sorted = sort(f);
    f();
}

Man kann f natürlich per Referenz an sort übergeben.

fn sort<F>(f: &F) -> Vec<i32> 
where
    F: Fn() -> Vec<i32> 
{
    let mut v = f();
    v.sort();
    v
}
fn main() {
    let x = vec![1, 0, 2];
    let f = move || x.clone();
    let sorted = sort(&f);
    assert_eq!(sorted, vec![0, 1, 2]);
    f();
}

Es gibt auch Closures, die Umgebungsvariablen manipulieren. Diese implementieren FnMut.

fn sort<F>(f: &mut F) -> Vec<i32> 
where
    F: FnMut() -> Vec<i32>
{
    let mut v = f();
    v.sort();
    v
}
fn main() {
    let mut x = vec![1, 0, 2];
    let mut f = move || { 
        x[0] += 5; 
        x.clone()
    };
    let sorted = sort(&mut f);
    assert_eq!(sorted, vec![0, 2, 6]);
    assert_eq!(f(), vec![11, 0, 2]);
}

Quiz

  • Warum ist das erste Element des Vektors im letzten Assert 11?

  • Warum würde im obigen Beispiel fn sort(f: fn() -> Vec<i32>) -> Vec<i32> nicht funktionieren?

Eine Closure, die Fn implementiert, implementiert auch FnMut. Eine Closure die FnMut implementiert, implementiert auch FnOnce.