2013-06-05 10 views
21

Jest to bardzo prosty przykład, ale jak chciałbym zrobić coś podobnego do:Czy można dokonać rekurencyjnego zamknięcia w Rust?

let fact = |x: u32| { 
    match x { 
     0 => 1, 
     _ => x * fact(x - 1), 
    } 
}; 

wiem, że ten konkretny przykład można łatwo zrobić z iteracji, ale zastanawiam się, czy to możliwe, aby rekurencyjny funkcja w Rust dla bardziej skomplikowanych rzeczy (takich jak przechodzenie przez drzewa) lub jeśli zamiast tego muszę użyć mojego własnego stosu.

+1

Niko Matsakis napisał [wspaniały post] (http://smallcultfollowing.com/babysteps/blog/2013/04/30/the-case-of-the-recurring-closure/) o tym, jak możesz potencjalnie (ab) używaj rekursji w zamknięciach już teraz i dlaczego to na pewno zostanie usunięte (jeśli nie jest jeszcze w 'przychodzącym'). Oczywiście zawsze możesz zdefiniować funkcję, która wywołuje samą siebie, ale nie przechwyci zmiennych zewnętrznych. –

Odpowiedz

20

Istnieje kilka sposobów, aby to zrobić.

Możesz umieścić zamknięcia w strukturze i przekazać tę strukturę do zamknięcia. Można nawet określić elemencie inline w funkcji:

fn main() { 
    struct Fact<'s> { f: &'s Fn(&Fact, u32) -> u32 } 
    let fact = Fact { 
     f: &|fact, x| if x == 0 {1} else {x * (fact.f)(fact, x - 1)} 
    }; 

    println!("{}", (fact.f)(&fact, 5)); 
} 

ten omija problem mający typu (nieskończoną funkcję, która zaczyna się jako argument) i problemu fact nie została jeszcze zdefiniowana wewnątrz zamknięcia samemu, gdy pisze się let fact = |x| {...} i dlatego nie można tam go odnieść.

Ta funkcja działa w wersji 1.17, ale w przyszłości może stać się nielegalna, ponieważ w niektórych przypadkach jest niebezpieczna, zgodnie z informacjami podanymi w the blog post The Case of the Recurring Closure. Jest tu całkowicie bezpieczny, ponieważ nie ma mutacji.


Inną opcją jest po prostu napisz rekurencyjną funkcję jako fn elementu, który może być także zdefiniowane w funkcji inline:

fn main() { 
    fn fact(x: u32) -> u32 { if x == 0 {1} else {x * fact(x - 1)} } 

    println!("{}", fact(5)); 
} 

Działa to dobrze, jeśli nie muszą przechwycić niczego ze środowiska.


Jeszcze jedna opcja to użycie rozwiązania fn pozycji, ale wyraźnie przekazać args/Środowisko chcesz.

fn main() { 
    struct FactEnv { base_case: u32 } 
    fn fact(env: &FactEnv, x: u32) -> u32 { 
     if x == 0 {env.base_case} else {x * fact(env, x - 1)} 
    } 

    let env = FactEnv { base_case: 1 }; 
    println!("{}", fact(&env, 5)); 
} 

Wszystkie te elementy działają z rdzeniem 1.17 i prawdopodobnie działały od wersji 0.6. Zdefiniowane wewnątrz fn s nie różnią się od zdefiniowanych na najwyższym poziomie, z wyjątkiem tego, że są dostępne tylko w obrębie fn, które są zdefiniowane w środku.