2014-10-22 13 views
6

Ich versuche eine Lösung zu Graham´s accumulator factory challenge aufzubauen, die im Grunde eine Funktion erfordert, um einen Abschluss zurückzugeben, der über eine veränderbare numerische Variable schließt, deren Anfangswert über einen Parameter empfangen wird. Jeder Aufruf dieser Schließung inkrementiert diese erfasste Variable um einen Wert, der ein Parameter für den Abschluss ist, und gibt den akkumulierten Wert zurück.Rückgabe eines Abschlusses mit veränderbarer Umgebung

Nach dem Lesen der closures RFC und einige Fragen über die Rückgabe von ungeöffneten Verschlüssen (insbesondere this). Ich könnte endlich eine Lösung finden, die kompiliert, aber das Ergebnis ist nicht das, was ich erwarten würde.

#![feature(unboxed_closures, unboxed_closure_sugar)] 

fn accumulator_factory(n: f64) -> Box<|&mut: f64| -> f64> { 
    let mut acc = n; 
    box |&mut: i: f64| { 
     acc += i; 
     acc 
    } 
} 

fn main() { 
    let mut acc_cl = accumulator_factory(5f64); 
    println!("{}", acc_cl.call_mut((3f64,))); 
    println!("{}", acc_cl.call_mut((3f64,))); 
} 

AFAIK dieser Verschluss fängt acc von Wert, die erzeugte Struktur, die die Umgebung wirkt wie wandelbar ist und acc_cl sollte die gleiche Umgebung Instanz zwischen den Anrufen halten.

Aber das Druckergebnis ist in beiden Fällen 6, so scheint es, dass der modifizierte Wert nicht persistiert. Und noch verwirrender ist, wie dieses Ergebnis berechnet wird. Bei jeder Ausführung des Verschlusses ist der Anfangswert acc3, obwohl n5 genannt wird.

Wenn ich ändern den Generator dazu:

fn accumulator_factory(n: f64) -> Box<|&mut: f64| -> f64> { 
    println!("n {}", n); 
    let mut acc = n; 
    box |&mut: i: f64| { 
     acc += i; 
     acc 
    } 
} 

dann immer die Ausführung zurückkehren 3 und der Anfangswert von acc ist immer 0 beim Schließen Eintrag.

Dieser Unterschied in der Semantik sieht wie ein Fehler aus. Aber warum wird die Umgebung zwischen den Anrufen zurückgesetzt?

Dies wurde mit Rustc 0.12.0 durchgeführt.

+1

für einen anderen Ansatz können Sie diese Lösung überprüfen: https://github.com/Hoverbear/rust-rosetta/blob/master/src/accumulator_factory.rs. Neue ungeöffnete Verschlüsse im Rost sind Zucker für eine Struktur und eine Trait-Implementierung. Diese Version schreibt die lange "ungezuckerte" Version –

Antwort

8

Der Aufnahmemodus von Verschlüssen hat sich kürzlich geändert. Closures sind voreingenommen, um alles per Referenz zu erfassen, da der häufigste Anwendungsfall für Closures sie an Funktionen im Call-Stack weiterleitet, und die Erfassung per Referenz die Arbeit mit der Umgebung natürlicher macht.

Manchmal ist die Erfassung per Referenz limitierend. Zum Beispiel können Sie solche Schließungen nicht von Funktionen zurückgeben, da ihre Umgebung an den Aufruf-Stack gebunden ist. Für solche Verschlüsse müssen Sie das move Schlüsselwort vor der Schließung setzen:

fn accumulator_factory(n: f64) -> Box<FnMut(f64) -> f64> { 
    println!("n: {}", n); 
    let mut acc = n; 
    Box::new(move |i: f64| { 
     acc += i; 
     acc 
    }) 
} 

fn main() { 
    let mut acc = accumulator_factory(10.0); 
    println!("{}", acc(12.0)); 
    println!("{}", acc(12.0)); 
} 

Dieses Programm funktioniert wie beabsichtigt:

n: 10 
22 
34 

Diese beiden Verschlussarten durch this RFC abgedeckt sind.

+0

Könnten Sie 'acc (12.0)' anstelle von 'acc.call_mut ((12.0,))' '? Die Aufrufsyntax scheint ein wenig überwältigend zu sein. –

+0

@MatthieuM., Deshalb habe ich gesagt, dass ich nicht verstehe, warum überladene Anrufe nicht funktionieren. Sie sind auch Feature-Gated, aber selbst mit diesem Gate aufgehoben beschwert sich der Compiler noch. –

+0

@VladimirMatveev Danke. Ich war mir dieser Änderung in der Syntax nicht bewusst. Ich denke, die Dinge ändern sich immer noch sehr schnell. Jetzt, wenn ich versuche, f64 zu einem generischen Typ zu ändern, laufe ich auf Lebenszeitprobleme. Aber ich werde eine andere Frage dazu schreiben. –

Verwandte Themen