将 non-trivial gstreamer "pad callbacks" 作为盒装闭包返回

Returning non-trivial gstreamer "pad callbacks" as boxed closures

我正在尝试编写一个工厂函数来创建闭包,以便在 gstreamer 中用作 'pad callbacks'。我提供了一个精简的示例,应该在安装 the gstreamer crate 和 gstreamer binaries/plugins 的情况下进行编译。

通过我的研究,我已经通过使用 'impl trait' 方法而不是装箱来使工厂函数工作。不过,我想弄清楚盒装方法,因为它在某些情况下似乎更合适。

这是我得到的最接近结果。通过取消注释标记为 Closure function using 'Box<>' 的部分可以看到该问题。我已经尝试将 Fn 部分指定为带有 where clause 的类型参数,以及许多其他尝试。在这次尝试中,问题似乎是我无法将闭包函数拆箱以用作对局部变量的赋值,或者由于需要 compile-time 大小而在 add_probe 回调中使用,这首先是盒子的全部原因...

Ctrl+C 或来自标准输入的 'exit\n' 应该关闭程序。

extern crate gstreamer as gst;

use gst::prelude::*;
use std::io;

fn create_impl_probe_fn(
    x: i32,
) -> impl Fn(&gst::Pad, &mut gst::PadProbeInfo) -> gst::PadProbeReturn + Send + Sync + 'static {
    move |_, _| {
        println!("Idle... {}", x);

        gst::PadProbeReturn::Pass
    }
}

fn create_boxed_probe_fn(
    x: i32,
) -> Box<Fn(&gst::Pad, &mut gst::PadProbeInfo) -> gst::PadProbeReturn + Send + Sync + 'static> {
    Box::new(move |_, _| {
        println!("Idle... {}", x);

        gst::PadProbeReturn::Pass
    })
}

fn main() {
    println!("Starting...");
    //TODO Pass args to gst?
    gst::init().unwrap();

    //GStreamer
    let parse_line = "videotestsrc ! autovideosink name=mysink";

    let pipeline = gst::parse_launch(parse_line).unwrap();
    let ret = pipeline.set_state(gst::State::Playing);
    assert_ne!(ret, gst::StateChangeReturn::Failure);

    //Inline closure
    let mut x = 1;
    pipeline
        .clone()
        .dynamic_cast::<gst::Bin>()
        .unwrap()
        .get_by_name("mysink")
        .unwrap()
        .get_static_pad("sink")
        .unwrap()
        .add_probe(gst::PadProbeType::BLOCK, move |_, _| {
            println!("Idle... {}", x);

            gst::PadProbeReturn::Pass
        });

    //Closure function using 'impl'
    x = 20;
    let impl_probe_fn = create_impl_probe_fn(x);
    //TEMP Test
    pipeline
        .clone()
        .dynamic_cast::<gst::Bin>()
        .unwrap()
        .get_by_name("mysink")
        .unwrap()
        .get_static_pad("sink")
        .unwrap()
        .add_probe(gst::PadProbeType::BLOCK, impl_probe_fn);

    /*
    //Closure function using 'Box<>'
    x = 300;
    let boxed_probe_fn = create_boxed_probe_fn(x);
    //TEMP Test
    pipeline
        .clone()
        .dynamic_cast::<gst::Bin>()
        .unwrap()
        .get_by_name("mysink")
        .unwrap()
        .get_static_pad("sink")
        .unwrap()
        .add_probe(gst::PadProbeType::BLOCK, *boxed_probe_fn);
    */

    //Input Loop
    loop {
        let mut input = String::new();
        io::stdin().read_line(&mut input).unwrap();

        match input.trim() {
            "exit" => break,
            "info" => {
                let (state_change_return, cur_state, old_state) =
                    pipeline.get_state(gst::CLOCK_TIME_NONE);
                println!(
                    "Pipeline Info: {:?} {:?} {:?}",
                    state_change_return, cur_state, old_state
                );
            }
            "pause" => {
                let _ = pipeline.set_state(gst::State::Paused);
                println!("Pausing");
            }
            "resume" => {
                let _ = pipeline.set_state(gst::State::Playing);
                println!("Resuming");
            }
            _ => println!("Unrecognized command: '{}'", input.trim()),
        }

        println!("You've entered: {}", input.trim());
    }

    //Shutdown
    let ret = pipeline.set_state(gst::State::Null);
    assert_ne!(ret, gst::StateChangeReturn::Failure);
    println!("Shutting down streamer");
}

我知道网上和 SO 上存在几个类似的问题,但我似乎无法弄清楚如何将任何解决方案应用于此特定功能。我在标题中加入了 "non-trivial" 和 "gstreamer" 来区分。

[编辑] 抱歉,这里有更多信息...只是不想搅浑水或使问题复杂化...

我无法 post 我尝试过的一切。超过 10 小时的小 changes/attempts 和许多选项卡。我可以重现一些看起来很接近或我期望成功的尝试。

上面的 Box 尝试是我根据此处的信息认为它可以工作的方式: https://doc.rust-lang.org/1.4.0/book/closures.html

https://doc.rust-lang.org/book/second-edition/ch19-05-advanced-functions-and-closures.html(这个不会关闭任何堆栈值,因此没有 'move'。)

(更多让我觉得我拥有的东西应该有用...)

盒子<>锈书部分:https://doc.rust-lang.org/book/second-edition/ch15-01-box.html

这是 add_probe 签名:https://sdroege.github.io/rustdoc/gstreamer/gstreamer/trait.PadExtManual.html#tymethod.add_probe

这是上面的错误(违规的 add_probe 调用未注释):

error[E0277]: the trait bound `for<'r, 's, 't0> std::ops::Fn(&'r gst::Pad, &'s mut gst::PadProbeInfo<'t0>) -> gst::PadProbeReturn + std::marker::Send + std::marker::Sync: std::marker::Sized` is not satisfied
  --> src/main.rs:63:14
   |
63 |             .add_probe(gst::PadProbeType::BLOCK, *boxed_probe_fn);
   |              ^^^^^^^^^ `for<'r, 's, 't0> std::ops::Fn(&'r gst::Pad, &'s mut gst::PadProbeInfo<'t0>) -> gst::PadProbeReturn + std::marker::Send + std::marker::Sync` does not have a constant size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `for<'r, 's, 't0> std::ops::Fn(&'r gst::Pad, &'s mut gst::PadProbeInfo<'t0>) -> gst::PadProbeReturn + std::marker::Send + std::marker::Sync`

所以,我想由于闭包的大小在 compile-time 处未知,我不能将其作为参数传递?

将取消引用更改为位于“.add_probe”上方的分配行会产生类似的错误:

error[E0277]: the trait bound `for<'r, 's, 't0> std::ops::Fn(&'r gst::Pad, &'s mut gst::PadProbeInfo<'t0>) -> gst::PadProbeReturn + std::marker::Send + std::marker::Sync: std::marker::Sized` is not satisfied
  --> src/main.rs:57:13
   |
57 |         let boxed_probe_fn = *create_boxed_probe_fn(x);
   |             ^^^^^^^^^^^^^^ `for<'r, 's, 't0> std::ops::Fn(&'r gst::Pad, &'s mut gst::PadProbeInfo<'t0>) -> gst::PadProbeReturn + std::marker::Send + std::marker::Sync` does not have a constant size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `for<'r, 's, 't0> std::ops::Fn(&'r gst::Pad, &'s mut gst::PadProbeInfo<'t0>) -> gst::PadProbeReturn + std::marker::Send + std::marker::Sync`
   = note: all local variables must have a statically known size

error[E0277]: the trait bound `for<'r, 's, 't0> std::ops::Fn(&'r gst::Pad, &'s mut gst::PadProbeInfo<'t0>) -> gst::PadProbeReturn + std::marker::Send + std::marker::Sync: std::marker::Sized` is not satisfied
  --> src/main.rs:63:14
   |
63 |             .add_probe(gst::PadProbeType::BLOCK, boxed_probe_fn);
   |              ^^^^^^^^^ `for<'r, 's, 't0> std::ops::Fn(&'r gst::Pad, &'s mut gst::PadProbeInfo<'t0>) -> gst::PadProbeReturn + std::marker::Send + std::marker::Sync` does not have a constant size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `for<'r, 's, 't0> std::ops::Fn(&'r gst::Pad, &'s mut gst::PadProbeInfo<'t0>) -> gst::PadProbeReturn + std::marker::Send + std::marker::Sync`

我理解基于堆栈的绑定需要 compile-time 大小....所以这几乎感觉不可能做到,除非 add_probe 函数本身采用 Boxed<> 争论?

继续尝试。几个地方,包括 add_probe 函数签名本身使用 Type 参数和 'where' 子句来指定 Fn 特征。

add_probe 声明:https://github.com/sdroege/gstreamer-rs/blob/db3fe694154c697afdaf3efb6ec65332546942e0/gstreamer/src/pad.rs

Post 推荐使用 'where' 子句:

所以,让我们尝试一下,将 create_boxed_probe_fn 更改为:

fn create_boxed_probe_fn<F>(x: i32) -> Box<F>
    where F: Fn(&gst::Pad, &mut gst::PadProbeInfo) -> gst::PadProbeReturn + Send + Sync + 'static {
    Box::new(move |_, _| {
        println!("Idle... {}", x);

        gst::PadProbeReturn::Pass
    })
}

错误:

error[E0308]: mismatched types
  --> src/main.rs:15:18
   |
15 |           Box::new(move |_, _| {
   |  __________________^
16 | |             println!("Idle... {}", x);
17 | |
18 | |             gst::PadProbeReturn::Pass
19 | |         })
   | |_________^ expected type parameter, found closure
   |
   = note: expected type `F`
              found type `[closure@src/main.rs:15:18: 19:10 x:_]`

这似乎是因为我们在上面指定了类型,但是闭包当然是它自己的类型。尝试以下是行不通的,因为它是一个特征,不能用 'as':

fn create_boxed_probe_fn<F>(x: i32) -> Box<F>
    where F: Fn(&gst::Pad, &mut gst::PadProbeInfo) -> gst::PadProbeReturn + Send + Sync + 'static {
    Box::new(move |_, _| {
        println!("Idle... {}", x);

        gst::PadProbeReturn::Pass
    } as F)
}

错误:

error[E0308]: mismatched types
  --> src/main.rs:15:18
   |
15 |           Box::new(move |_, _| {
   |  __________________^
16 | |             println!("Idle... {}", x);
17 | |
18 | |             gst::PadProbeReturn::Pass
19 | |         } as F)
   | |______________^ expected type parameter, found closure
   |
   = note: expected type `F`
              found type `[closure@src/main.rs:15:18: 19:15 x:_]`

error[E0605]: non-primitive cast: `gst::PadProbeReturn` as `F`
  --> src/main.rs:15:30
   |
15 |           Box::new(move |_, _| {
   |  ______________________________^
16 | |             println!("Idle... {}", x);
17 | |
18 | |             gst::PadProbeReturn::Pass
19 | |         } as F)
   | |______________^
   |
   = note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait

它提到了 'From' 特征。我没有对此进行调查,因为闭包的隐含特征似乎不正确。我什至不确定这是否可能?

我也尝试过他们所谓的类型归属(而不是 'as F' 使用':F'),但目前似乎不受支持:https://github.com/rust-lang/rust/issues/23416

这个人遇到了同样的问题,但他的解决方案似乎是不使用类型参数,而是在没有 where 子句的情况下指定 Fn 部分。 (这是我的最高 non-working 尝试。)不完全确定,因为他没有 post 他做了什么来修复它。 https://github.com/rust-lang/rust/issues/51154

将 impl 关键字添加到任何 box 版本似乎都无济于事。像我在未装箱的 "working" 版本中那样使用它的语法似乎是新的,我还没有找到很好的文档。这里有一些关于它的信息:https://github.com/rust-lang/rfcs/blob/master/text/1522-conservative-impl-trait.md

更多相关链接:

https://doc.rust-lang.org/std/boxed/trait.FnBox.html

自 Rust 1.35

Box<dyn Fn(...)> 实现了 Fn(...),所以你可以简单地将 Box 直接传递给 add_probe:

        .add_probe(gst::PadProbeType::BLOCK, boxed_probe_fn);

原回答

This problem can be reduced to a surprisingly concise example:

fn call<F: Fn()>(f: F) {
    f();
}

fn main() {
    let g = || ();                            // closure that takes nothing and does nothing
    let h = Box::new(|| ()) as Box<dyn Fn()>; // that but as a Fn() trait object
    call(g); // works
    call(h); // fails
}

The heart of the problem is that Box<dyn Fn()> does not implement Fn(). There's no good reason this doesn't work, but there are a collection of factors that make it awkward to fix:

  1. It's not possible to call a method that takes self by value on a trait object. This makes it not possible to call a Box<dyn FnOnce()>. The current workaround is to use Box<dyn FnBox>, which does implement FnOnce() (but this does not directly apply to your situation or the example above, since you want to use Fn).
  2. Despite this, it may one day become possible to call a Box<dyn FnOnce()>, so FnBox is in a sort of Limbo where people don't want to fix or stabilize it to work around a temporary issue.
  3. Adding an impl to core to make Fn() work may nevertheless conflict with FnBox in ways that I don't quite grasp. There are several comments about this on issue #28796.

It's possible that implementing Fn() for Box<dyn Fn()> just can't be done in the language as is. It's also possible that it could be done, but is a bad idea for forwards compatibility reasons; and it's also possible that it could be done and is a good idea, but nobody has done it yet. That said, with things as they are now, you have a couple of mostly unpleasant options.

As someone suggested in the question comments, you could make your own wrapper struct that wraps a closure, and implement Fn() for Box<Wrapper<F>>.

You could make your own trait ProbeFn, which is implemented for any closure of the correct type, and implement Fn() for Box<dyn ProbeFn>.

In some cases, you may be able to use a &dyn Fn() instead of a Box<dyn Fn()>. This works in the above example:

    call(&*h);

Unlike Box<dyn Fn()>, &dyn Fn() does implement Fn(). It's not as general, though, because obviously it doesn't have ownership. However, it does work on the stable compiler -- implementing Fn() yourself requires unstable.