【问题标题】:Rust standard library closure parameters: run time or compile time?Rust 标准库闭包参数:运行时还是编译时?
【发布时间】:2014-06-14 22:16:26
【问题描述】:

我正在浏览Rust 标准库。 当闭包作为参数传递给函数时,它是在运行时传递的,这让我感到很震惊。例如来自Iterator trait

fn filter<'r>(self, predicate: 'r |&A| -> bool) -> Filter<'r, A, Self>

这里的'predicate' 不是一个通用参数,而是一个普通的运行时参数。但这是否意味着编译器不能内联对“谓词”的调用?这是一个深思熟虑的设计选择 [例如避免代码膨胀] 还是 Rust 语言没有提供在编译时传递闭包的方法?

【问题讨论】:

    标签: parameters closures inline std rust


    【解决方案1】:

    你说得对,闭包目前实际上是 trait 对象,也就是说,它们存储了一个指向其实际代码的函数指针(类似于 C++ 中的std::function)。

    这被认为是不够的,并不是最终的设计,实际上目前正在"unboxed closures" 上进行工作,通过制作像 C++11 一样的 Rust 闭包来解决这个问题,其中每个闭包都有一个实现的唯一类型使其可调用的适当方法。 (当前的 Rust 提案包含 3 个特性以实现完全的灵活性(FnFnMutFnOnce),有关更多详细信息,请参阅上面的 RFC 链接。

    在那之后,filter 可能看起来像

    fn filter<'r, F: FnMut<(&A,), bool)>(self, predicate: F) -> Filter<A, Self, F>
    

    (可能有糖,所以绑定可以写F: |&amp;A| -&gt; bool,甚至more sugar,这样就可以直接写predicate: impl |&amp;A| -&gt; bool之类的东西,而无需额外的类型参数(尽管这不适用于@具体是987654331@,因为它需要将类型参数传递给返回类型)。)

    在这种方案下,仍然可以通过与 trait 对象工作完全相同的机制来拥有擦除的函数类型(例如,停止代码膨胀,或在某些数据结构中存储许多不同的闭包),编写类似的东西predicate: &amp;mut FnMut&lt;(&amp;A,), bool&gt;,但这些不会在迭代器适配器中使用。


    此外,LLVM 现在可能内联动态闭包,但它绝对不像静态分派的未装箱闭包那样容易或有保证,例如

    fn main() {
        for _ in range(0, 100).filter(|&x| x % 3 == 0) {
            std::io::println("tick") // stop the loop being optimised away
        }
    }
    

    编译为以下优化的 LLVM IR(通过 rustc --emit=ir -O):

    ; Function Attrs: uwtable
    define internal void @_ZN4main20h9f09eab975334327eaa4v0.0E() unnamed_addr #0 {
    entry-block:
      %0 = alloca %str_slice, align 8
      %1 = getelementptr inbounds %str_slice* %0, i64 0, i32 0
      %2 = getelementptr inbounds %str_slice* %0, i64 0, i32 1
      br label %match_else.i
    
    match_else.i:                                     ; preds = %loop_body.i.backedge, %entry-block
      %.sroa.012.0.load1624 = phi i64 [ 0, %entry-block ], [ %3, %loop_body.i.backedge ]
      %3 = add i64 %.sroa.012.0.load1624, 1
      %4 = srem i64 %.sroa.012.0.load1624, 3
      %5 = icmp eq i64 %4, 0
      br i1 %5, label %match_else, label %loop_body.i.backedge
    
    match_else:                                       ; preds = %match_else.i
      store i8* getelementptr inbounds ([4 x i8]* @str1233, i64 0, i64 0), i8** %1, align 8
      store i64 4, i64* %2, align 8
      call void @_ZN2io5stdio7println20h44016c4e880db7991uk11v0.11.0.preE(%str_slice* noalias nocapture nonnull %0)
      br label %loop_body.i.backedge
    
    loop_body.i.backedge:                             ; preds = %match_else, %match_else.i
      %exitcond = icmp eq i64 %3, 100
      br i1 %exitcond, label %join4, label %match_else.i
    
    join4:                                            ; preds = %loop_body.i.backedge
      ret void
    }
    

    特别是过滤器调用是完全内联的:都在match_else.i:块中,你可以看到%4 = srem i64 ..., 3调用是% 3那段代码,icmp eq i64 %4, 0== 0位.

    【讨论】:

    • 很好的答案!谢谢。
    • 我只想当 dbaupp 一天 :)
    猜你喜欢
    • 2013-08-29
    • 2017-08-25
    • 2012-06-26
    • 1970-01-01
    • 2012-09-24
    • 2011-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多