将非平凡的gstreamer“pad callbacks”作为盒装闭包返回

时间:2018-06-17 19:02:31

标签: rust closures gstreamer

我正在尝试编写一个工厂函数来创建闭包,用作gstreamer中的“pad callbacks”。我提供了一个精简的示例,应该使用the gstreamer crate和gstreamer二进制文件/插件进行编译。

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

这就像我得到的那样接近。通过取消注释标记为Closure function using 'Box<>'的部分可以看出问题。我已尝试将Fn部分指定为带有where clause的类型参数,以及许多其他尝试。在这种尝试中,看起来问题是我不能将闭包函数取消装箱以用作局部变量的赋值,或者由于需要编译时大小而在add_probe回调中使用,这是首先是盒子的全部原因......

从stdin

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,但我似乎无法想象如何将任何解决方案应用于这个特定的功能。我在标题中加入了“非平凡”和“gstreamer”来区分。

[编辑] 对不起,这里有更多信息...只是不想让水变得混乱或使问题复杂化......

我无法发布我尝试过的所有内容。它有超过10个小时的小改动/尝试和许多标签。我可以重现一些似乎很接近的尝试,或者我希望能够工作的尝试。

上面的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(这个没有关闭任何堆栈值,因此没有'移动'。)

Rust closures from factory functions(更让我觉得我应该做的更多......)

盒及LT;&GT;生锈书的一部分: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`

所以,我想因为在编译时不知道闭包的大小,我不能将它作为参数传递?

将取消引用更改为位于'.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`

我理解基于堆栈的绑定需要编译时大小....所以除非add_probe函数本身采用了Boxed&lt;&gt;,否则这几乎感觉不可能。 arguement?

进行更多尝试。一些地方,包括add_probe函数签名本身使用Type参数和'where'子句来指定Fn特征。

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

使用'where'子句发布推荐:Sized is not implemented for the type Fn

所以,让我们试试,将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

它提到了'从'特性。我没有调查过,因为关闭闭包的特征似乎并不合适。我甚至不确定它是否可能?

我也尝试了他们似乎称之为类型归属的东西(而不是使用':F'代表'作为F'),但此时似乎不支持:https://github.com/rust-lang/rust/issues/23416

这家伙有同样的问题,但似乎他的解决方案是不使用类型参数而是指定没有where子句的Fn部分。 (这是我最重要的非工作尝试。)不完全确定,因为他没有发布他所做的修复工作。 https://github.com/rust-lang/rust/issues/51154

将impl关键字添加到任何框版本都没有任何帮助。像我在未装箱的“工作”版本中使用它的语法似乎是新的,我还没有找到很好的文档。以下是一些信息:https://github.com/rust-lang/rfcs/blob/master/text/1522-conservative-impl-trait.md

更多相关链接:

How do I store a closure in Rust?

Closure in the return type for a Rust function

expected trait core::ops::FnMut, found type parameter

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

1 个答案:

答案 0 :(得分:2)

这个问题可以简化为一个非常简洁的例子。 (截至本文撰写时,稳定的Rust为1.26。在这篇文章中,我使用的是beta 1.27,所以我可以利用dyn Trait syntax - 我认为这会让事情更加清晰。)

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
}

问题的核心是Box<dyn Fn()>没有实现Fn()。没有良好的原因这不起作用,但有一系列因素使其难以修复:

  1. 无法调用在特征对象上按值self获取的方法。这使得无法调用Box<dyn FnOnce()>。目前的解决方法是使用Box<dyn FnBox> 实施FnOnce()(但这并不直接适用于您的情况或上面的示例,因为您要使用{{1 }})。
  2. 尽管如此,it may one day become possible可以调用Fn,因此Box<dyn FnOnce()>处于一种Limbo,人们不想修复或稳定它以解决临时问题。
  3. FnBox添加到核心以使impl工作可能会与Fn()以我无法理解的方式发生冲突。 There are several comments about this on issue #28796
  4. 对于FnBox实施Fn()可能无法按语言完成。它也有可能完成,但出于兼容性的原因,这是一个坏主意;也有可能它可以做到并且是一个好主意,但还没有人做过。也就是说,就像现在的情况一样,你有几个最不愉快的选择。

    正如有人在问题评论中建议的那样,你可以创建自己的包装结构来封装一个闭包,并为Box<dyn Fn()>实现Fn()

    您可以创建自己的特征 Box<Wrapper<F>>,这是针对正确类型的任何关闭实现的,并为ProbeFn实现Fn()

    在某些情况下,您可以使用Box<dyn ProbeFn>代替&dyn Fn()。这适用于上面的例子:

    Box<dyn Fn()>

    call(&*h); 不同,Box<dyn Fn()> 实施&dyn Fn()。但这并不是一般的,因为显然它没有所有权。但是,它确实适用于稳定的编译器 - 自己实现Fn()需要不稳定。