闭包内定义的结构体作用域解析:为何多次闭包调用生成的实例可被收集为同一类型Vec?
我之前写了一段Rust代码,本来笃定它编译不过,结果却顺利通过了:
fn main() { (0..3) .map(|i| { struct S { v: usize, } S { v: i } }) .collect::<Vec<_>>(); }
我原本以为,每次调用闭包时都会定义一个全新的结构体类型,这样map返回的就会是不同类型的实例,根本没法收集成单一类型的Vec。但事实却相反——这个S似乎并不是每次调用都重新定义的局部类型。
这背后到底是怎么运作的?我想要严谨的官方解释,最好能对应到《Rust参考手册》的具体章节,所以加上了#language-lawyer标签。
如果抛开对Rust的了解,单看这段代码其实有两种可能的解读,我之前一直以为第二种才是对的,但代码能编译说明第一种才是正确的。这到底是为什么?这就是我的疑问。
解读1
//`S`在某种意义上是「全局」的 struct S { v: usize, } //闭包的每次调用只是创建这个类型的新实例 for i in 0..3 { vec.push(S { v: 0 }); }
解读2
{ //闭包的每次调用都定义一个全新的局部结构体类型 struct __S_anonymous_01 { v: usize, } //然后创建该类型的实例 vec.push(__S_anonymous_01 { v: 0 }); } { struct __S_anonymous_02 { v: usize, } vec.push(__S_anonymous_02 { v: 1 }); } { struct __S_anonymous_03 { v: usize, } vec.push(__S_anonymous_03 { v: 2 }); } //`__S_anonymous_{01|02|03}`都是不同的类型 //哪怕它们结构完全一样,因为Rust不支持鸭子类型
其实问题的核心,得从Rust里「项(Item)」和「表达式(Expression)」的本质区别说起,再结合闭包的编译规则来拆解:
首先,你得搞清楚:struct定义属于「项」——根据《Rust参考手册》里的项的定义章节,像struct、fn这种「项」,是编译阶段就静态确定的实体,不是运行时才会执行的代码片段。哪怕你把它写在闭包里面,它也不会变成“每次调用闭包就重新定义的局部类型”。
说白了,这个struct S在编译的时候就已经是一个固定的单一类型了,和你把它写在闭包外面没本质区别——只是它的作用域被限制在闭包内部而已,但类型本身是唯一的。
其次,再看闭包的规则:根据闭包表达式的相关规定,每个闭包本身对应一个唯一的匿名类型,而闭包内部定义的项会绑定到这个闭包类型的作用域上。也就是说,当你写完这个闭包的那一刻,里面的S就已经是一个确定的结构体类型了,后续每次调用闭包,只是创建这个已有类型的新实例而已,完全不是你之前以为的“每次调用都生成新类型”。
这就是代码能编译的原因:所有闭包调用返回的都是同一个S类型的实例,自然能被收集成同一个Vec。
备注:内容来源于stack exchange,提问作者ynn

