Rust方法内如何实现可自调用的递归闭包处理递归结构体
问题原因
这个编译错误是Rust和JavaScript的闭包实现模型差异导致的:
- Rust的闭包本质是实现了
Fn/FnMut/FnOncetrait的匿名结构体,在let mut recursive = |s: &Self| {...}语句中,等号右侧的闭包必须先完成所有外部变量捕获、完成自身构造,才能赋值给左侧的recursive变量 - 闭包构造阶段
recursive变量还未完成初始化,不可能被闭包捕获,因此直接触发E0425错误,提示作用域内找不到recursive函数 - 就算提前声明
recursive变量占位,Rust的借用规则也不允许闭包在持有外部可变捕获引用的同时,持有自身的可变引用,会直接触发借用冲突,无法像JS那样直接在闭包内部递归调用自身。
解决方案
方案1:内部函数传上下文(推荐,零运行时开销,符合Rust编码习惯)
不需要硬套JS的闭包递归范式,直接在方法内定义普通递归函数,把需要共享的可变状态(字符串缓存、首次调用标记)作为参数传入即可,既不会触发借用错误,也没有任何额外运行时开销:
enum Enum { T1, T2, T3 } struct StructExample { value_type: Enum, value: Option<Box<StructExample>> } impl StructExample { fn to_str(&self) -> String { // 内部递归函数,所有需要共享的上下文通过参数传入 fn recursive(s: &StructExample, string_var: &mut String, first: &mut bool) { match s.value_type { Enum::T1 => { if *first { string_var.push('i'); *first = false; } string_var.push('H'); } Enum::T2 => { if *first { string_var.push('H'); *first = false; } string_var.push('i'); } Enum::T3 => { // 注意这里要加&,避免拿走s.value的所有权 if let Some(val) = &s.value { *first = false; string_var.push('<'); recursive(val, string_var, first); string_var.push('>'); } } } } let mut string_var = String::new(); let mut first = true; recursive(self, &mut string_var, &mut first); string_var } }
这种写法是Rust中处理这类递归逻辑的常规方式,不需要依赖任何黑魔法,编译后性能和直接写递归方法没有区别。
方案2:Y组合子实现闭包递归(适合需要保留闭包捕获写法的场景)
如果一定要保持闭包直接捕获外部变量的写法,可以用Y不动点组合子实现闭包递归,稳定版Rust即可使用,不需要unstable特性:
impl StructExample { fn to_str(&self) -> String { let mut string_var = String::new(); let mut first = true; // 通用Y组合子实现 fn y_combinator<T, R>(f: impl Fn(&dyn Fn(T) -> R, T) -> R) -> impl Fn(T) -> R { move |x| f(&|y| y_combinator(&f)(y), x) } let recursive = y_combinator(|rec, s: &Self| { match s.value_type { Enum::T1 => { if first { string_var.push('i'); first = false; } string_var.push('H'); } Enum::T2 => { if first { string_var.push('H'); first = false; } string_var.push('i'); } Enum::T3 => { if let Some(val) = &s.value { first = false; string_var.push('<'); rec(val); string_var.push('>'); } } } }); recursive(self); string_var } }
提示:从JS转Rust时很容易惯性照搬JS的闭包写法,但Rust的所有权、借用规则和闭包实现逻辑和JS差异极大,这类需要递归、共享可变状态的场景,用内部函数传参的写法往往比强行实现闭包递归更简洁,也不容易碰到编译错误。
内容的提问来源于stack exchange,提问作者tatoci5107
相关产品推荐
相关产品推荐

