【问题标题】:Boxed Fn requires lifetime 'static only when testing?Boxed Fn 仅在测试时才需要生命周期'static?
【发布时间】:2016-08-01 17:07:57
【问题描述】:

使用rustc 1.10.0,我正在尝试编写一些绕过盒装闭包的代码——最终目标是在程序上生成分形动画。现在我有一些这样的函数签名:

pub fn interpolate_rectilinear(width: u32, height: u32, mut min_x: f64, mut max_x: f64, mut min_y: f64, mut max_y: f64)
    -> Box<Fn(u32, u32) -> Complex64 + Send + Sync + 'static> { ... }

pub fn interpolate_stretch(width: u32, height: u32, mut min_x: f64, mut max_x: f64, mut min_y: f64, mut max_y: f64)
    -> Box<Fn(u32, u32) -> Complex64 + Send + Sync + 'static> { ... }

pub fn parallel_image<F>(width: u32, height: u32, function: &F, interpolate: &Box<Fn(u32, u32) -> Complex64 + Send + Sync>, threshold: f64)
    -> ImageBuffer<image::Luma<u8>, Vec<u8>>
        where F: Sync + Fn(Complex64) -> Complex64
{ ... }

pub fn sequential_image<F>(width: u32, height: u32, function: &F, interpolate: &Box<Fn(u32, u32) -> Complex64>, threshold: f64)
    -> ImageBuffer<image::Luma<u8>, Vec<u8>>
        where F: Fn(Complex64) -> Complex64
{ ... }

在二进制文件中一次为一个图像运行此代码没有问题:

let interpolate = interpolate_rectilinear(width, height, -1.0, 1.0, -1.0, 1.0);
let image = parallel_image(width * 2, height * 2, &default_julia, &interpolate, 2.0);

但是,我想确保我的串行和并行图像生成都产生相同的结果,所以我编写了以下测试函数:

#[test]
fn test_serial_parallel_agree() {
    let (width, height) = (200, 200);
    let threshold = 2.0;
    let interpolate = interpolate_stretch(width, height, -1.0, 1.0, -1.0, 1.0);

    assert!(parallel_image(width, height, &default_julia, &interpolate, threshold)
        .pixels()
        .zip(sequential_image(width, height, &default_julia, &interpolate, threshold)
            .pixels())
        .all(|(p, s)| p == s));
}

这拒绝编译,我就是想不通。它给出的错误如下:

> cargo test
Compiling julia-set v0.3.0 
src/lib.rs:231:66: 231:78 error: mismatched types [E0308]
src/lib.rs:231             .zip(sequential_image(width, height, &default_julia, &interpolate, threshold)
                                                                                ^~~~~~~~~~~~
src/lib.rs:229:9: 233:36 note: in this expansion of assert! (defined in <std macros>)
src/lib.rs:231:66: 231:78 help: run `rustc --explain E0308` to see a detailed explanation
src/lib.rs:231:66: 231:78 note: expected type `&Box<std::ops::Fn(u32, u32) -> num::Complex<f64> + 'static>`
src/lib.rs:231:66: 231:78 note:    found type `&Box<std::ops::Fn(u32, u32) -> num::Complex<f64> + Send + Sync>`
error: aborting due to previous error
Build failed, waiting for other jobs to finish...
error: Could not compile `julia-set`.

我真的不知道那里发生了什么。我不知道为什么我需要在插值函数的盒装返回类型中手动标记SendSync,而编译器通常会自动派生这些特征。不过,我只是不断添加编译器建议的标记,直到一切正常。

真正的问题是,虽然我想我已经很好地猜到了为什么你不能只标记一个盒装的闭包'static,但我不知道在这种情况下需要什么生命周期或如何解决它。

我确实猜想问题可能是我试图同时从两个读取借阅中引用闭包(这应该没问题,但我很绝望);无论如何,将interpolate 包装在Rc 中会产生完全相同的错误,所以这不是问题。

【问题讨论】:

  • 你有 git repo 之类的吗?我想测试一下。
  • 除了 Veedrac 的评论之外,您应该在 Stack Overflow 上提问时提供minimal reproducible example(真的在任何地方,但尤其是在这里)。如果我们无法完全从此处中包含的内容重现您的问题,那么问题将结束。
  • @Veedrac 是的,存储库是here。调用起作用的位置是here,而不起作用的测试位置是here。我粘贴的内容和这里一样少,因为问题似乎已经相当长且详细,并且粘贴数百行无关的源代码不会是最少的。
  • @coriolinus:我想你误会了。目的是减少问题,直到它只有几行并且不需要任何板条箱。一旦您设法减少问题,您很可能最终会回答自己的问题。
  • 遗憾的是这是不可能的:play.rust-lang.org/… 仍然依赖于其他 crate 和此处未提供的类型。我们显然可以自己完成这项工作,但是您可以通过提供这样一个“可编译”示例来激发帮助。显然它不会编译,但它会产生你显示的确切错误。

标签: closures rust lifetime-scoping


【解决方案1】:

问题其实出在这里:

pub fn sequential_image<F>(
    ...,
    interpolate: &Box<Fn(u32, u32) -> Complex64>,
    ...) -> ...

interpolate 不期望 &amp;Box&lt;Fn(u32, u32) -&gt; Complex64 + Send + Sync&gt;,并且 Rust 在处理所有这些复杂性的差异方面非常糟糕。

一种解决方案是在调用它的地方进行转换:

sequential_image(width, height, &default_julia,
    &(interpolate as Box<Fn(u32, u32) -> Complex64>),
threshold)

但这需要sequential_image 的值大小写,而且非常丑陋。

更好的方法是将sequential_image 的参数固定为更通用且更易于编译器推理的东西:基本指针。

pub fn sequential_image<F>(
    ...,
    interpolate: &Fn(u32, u32) -> Complex64,
    ...) -> ...

现在你可以调用它了

sequential_image(width, height, &default_julia,
    &*interpolate,
threshold)

并且编译器可以自己做所有的变化魔法。

【讨论】:

  • 我首先从Box&lt;Fn&gt; rabbithole 开始的原因。如果我按照您的建议更改interpolate 的签名,parallel_image 会抱怨Fn(u32, u32) -&gt; Complex64 cannot be shared between threads safely。我尝试通过将函数包装在 Arc 中来解决这个问题:let interpolate = Arc::new(interpolate);,但这不会改变错误。相反,如果我将其包装为let interpolate = Arc::new(*interpolate);,则错误变为the trait bound std::ops::Fn(u32, u32) -&gt; num::Complex&lt;f64&gt;: std::marker::Sized is not satisfied。我还能如何处理这个问题?
  • 我希望 sequential_imageparallel_image 共享相同的签名,因为调用者不应该关心幕后发生的事情。
  • @coriolinus parallel_image 只需要取interpolate: &amp;(Fn(u32, u32) -&gt; Complex64 + Send + Sync)
  • 您的第一个解决方案有效;顺便。谢谢你给我看!我没有意识到添加 Send + Sync 会阻止 Box 工作。如果interpolate 的签名只是函数指针,我想我正在寻找如何让sequential_imageparallel_image 工作的建议。
  • @coriolinus 其实parallel_image 只需要interpolate: &amp;(Fn(u32, u32) -&gt; Complex64 + Sync),甚至不需要Send
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-29
  • 1970-01-01
  • 2023-03-11
  • 2020-09-06
  • 1970-01-01
  • 2022-06-13
相关资源
最近更新 更多