2014-10-22 17 views
6

Próbuję zbudować rozwiązanie do Graham´s accumulator factory challenge, które zasadniczo wymaga funkcji zwrócenia zamknięcia, które zamyka zmienną zmienną numeryczną, której wartość początkowa jest odbierana przez parametr. Każde wywołanie tego zamknięcia zwiększa tę przechwyconą zmienną o wartość, która jest parametrem zamknięcia i zwraca skumulowaną wartość.Zwracanie zamknięcia z zmiennym środowiskiem

Po przeczytaniu closures RFC i kilku pytaniach dotyczących zwrotu zamkniętych zamknięć (w szczególności this). W końcu mogłem wymyślić rozwiązanie, które się kompiluje, ale wynik nie jest tym, czego bym się spodziewał.

#![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 to zamknięcie przechwytuje acc przez wartość, wygenerowany struct, który działa jako środowisko jest zmienny i acc_cl powinny zachować tę samą instancję środowiska między rozmowami.

W obu przypadkach wynik drukowania jest 6, więc wygląda na to, że zmodyfikowana wartość nie występuje. I co bardziej mylące jest to, jak obliczany jest ten wynik. Przy każdym wykonaniu zamknięcia początkowa wartość acc to 3, mimo że n jest 5, gdy zostanie wywołana.

Jeśli zmodyfikować generator do tego:

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

następnie wykonywanie zawsze powrócić 3 i wartość początkowa acc zawsze 0 w pozycji zamknięcia.

Ta różnica w semantyce wygląda jak błąd. Ale poza tym, dlaczego środowisko jest resetowane między połączeniami?

To zostało wykonane z rustc 0.12.0.

+1

dla innego podejścia możesz sprawdzić to rozwiązanie: https://github.com/Hoverbear/rust-rosetta/blob/master/src/accumulator_factory.rs. Nowe zamknięte w rudzie zamknięcia w rdzeniu to cukier na strukturę i implementacja cechy. Ta wersja pisze długą "nieusuwalną" wersję. –

Odpowiedz

8

Tryb przechwytywania zamknięć ostatnio się zmienił. Zamknięcia są zorientowane na przechwytywanie wszystkiego przez odniesienie, ponieważ najczęstszym przypadkiem użycia dla zamknięć jest przekazywanie ich do funkcji w dół stosu wywołań, a przechwytywanie przez odniesienie sprawia, że ​​praca ze środowiskiem jest bardziej naturalna.

Czasami uchwycenie przez odniesienie jest ograniczone. Na przykład nie można zwrócić takich zamknięć z funkcji, ponieważ ich środowisko jest powiązane ze stosem wywołań. Dla takich zamknięć należy umieścić słowa kluczowego move przed zamknięciem:

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)); 
} 

Ten program działa zgodnie z przeznaczeniem:

n: 10 
22 
34 

Te dwa rodzaje zamknięć są objęte this RFC.

+0

Czy można nazwać "acc (12.0)' zamiast 'acc.call_mut ((12.0,))'? Składnia wywołania wydaje się nieco przytłaczająca. –

+0

@MatthieuM., Dlatego powiedziałem, że nie rozumiem, dlaczego przeciążone połączenia nie działają. Są one również wyposażone w funkcje, ale nawet po podniesieniu tej bramy kompilator wciąż narzeka. –

+0

@VladimirMatveev Dzięki. Nie zdawałem sobie sprawy z tej zmiany w składni. Sądzę, że rzeczy wciąż się zmieniają bardzo szybko. Teraz, kiedy próbuję zmienić F64 na typ ogólny, mam problemy z życiem. Ale napiszę na to kolejne pytanie. –

Powiązane problemy