これは非常に単純な例ですが、次のようなことをするにはどうすればよいですか。
let fact = |x: u32| {
match x {
0 => 1,
_ => x * fact(x - 1),
}
};
この特定の例は反復で簡単に実行できることは知っていますが、Rustで、より複雑なもの(ツリーのトラバースなど)に対して再帰関数を作成できるかどうか疑問に思っています。代わりに自分のスタックを使用する必要があります。
これを行うにはいくつかの方法があります。
クロージャーを構造体に入れ、この構造体をクロージャーに渡すことができます。関数内で構造体をインラインで定義することもできます。
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));
}
これは、無限型(それ自体を引数として受け取る関数)の問題と、fact
がlet fact = |x| {...}
を書き込んだときにクロージャー自体の内部でまだ定義されていないという問題を回避します。そこでは参照できません。
これはRust 1.17で機能しますが、 ブログ投稿で説明されているように、場合によっては危険であるため、将来は違法になる可能性があります)繰り返し閉鎖の場合 。ただし、突然変異がないため、ここでは完全に安全です。
もう1つのオプションは、再帰関数をfn
アイテムとして記述することです。これは、関数内でインラインで定義することもできます。
fn main() {
fn fact(x: u32) -> u32 { if x == 0 {1} else {x * fact(x - 1)} }
println!("{}", fact(5));
}
これは、環境から何もキャプチャする必要がない場合に正常に機能します。
もう1つのオプションは、fn
itemソリューションを使用することですが、必要な引数/環境を明示的に渡します。
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));
}
これらはすべてRust 1.17で機能し、バージョン0.6以降で機能している可能性があります。fn
s内で定義されているfn
は、トップレベルで定義されているものと同じです。 fn
内でのみアクセス可能であることを除いて、それらは内部で定義されています。
これが私が思いついた本当に醜く冗長な解決策です:
use std::{
cell::RefCell,
rc::{Rc, Weak},
};
fn main() {
let weak_holder: Rc<RefCell<Weak<dyn Fn(u32) -> u32>>> =
Rc::new(RefCell::new(Weak::<fn(u32) -> u32>::new()));
let weak_holder2 = weak_holder.clone();
let fact: Rc<dyn Fn(u32) -> u32> = Rc::new(move |x| {
let fact = weak_holder2.borrow().upgrade().unwrap();
if x == 0 {
1
} else {
x * fact(x - 1)
}
});
weak_holder.replace(Rc::downgrade(&fact));
println!("{}", fact(5)); // prints "120"
println!("{}", fact(6)); // prints "720"
}
これの利点は、期待されるシグネチャ(追加の引数は不要)で関数を呼び出すことです。これは、変数を(移動によって)キャプチャできるクロージャーであり、新しい構造体を定義する必要がなく、クロージャーは関数またはそれが作成されたスコープよりも長生きする場所に格納される(Rc<Fn...>
)そしてそれはまだ動作します。