【问题标题】:Returning non-trivial gstreamer "pad callbacks" as boxed closures将非平凡的 gstreamer“pad 回调”作为盒装闭包返回
【发布时间】:2018-11-26 17:15:51
【问题描述】:

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

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

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

Ctrl+C 或 'exit\n' from stdin 应该关闭程序。

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”来区分。

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

我无法发布我尝试过的所有内容。这是超过 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(更多让我觉得我应该工作的东西......)

盒子部分锈书: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 争论?

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

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

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

我还尝试了他们似乎称之为类型归属的方法(而不是使用 ':F' 的 'as 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

【问题讨论】:

  • 也许您可以创建一个新类型 struct BoxedProbe(Box&lt;Fn...&gt;),然后为转发呼叫的新类型实现 Fn()...
  • 我相信这是因为Box&lt;Fn(...)&gt; 本身并没有实现Fn(...)Issue #38132 是开放的...只是可能没有人费心做 PR。
  • @rodrigo 我是 rust 新手。我想如果我的新结构可以实现 add_probe 需要作为参数的确切 Fn 签名,那么我可以传递具有已知大小的类型吗?那么它的方法会被称为 add_probe 回调吗?我觉得这将是很多样板。
  • @trentcl 不完全相同的错误,但我觉得你可能是正确的。如果 Box of Fn did 实现了 Fn 本身,那么我可以将装箱的值传入吗?该问题的副本有一个更好的例子:github.com/rust-lang/rust/issues/47024
  • 不知道add_probe 的定义,我只是猜测,但似乎确实如此。我希望,如果适当的 impls 被添加到核心,那么事情将“正常工作”。

标签: rust closures gstreamer


【解决方案1】:

从 Rust 1.35 开始

Box&lt;dyn Fn(...)&gt; 实现了Fn(...),因此您可以简单地将Box 直接传递给add_probe

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

原答案

这个问题可以简化为一个非常简洁的例子:

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&lt;dyn Fn()&gt;没有实现 Fn()。没有好的理由这不起作用,但有一个 导致难以修复的因素的集合:

  1. 不可能在特征对象上调用按值获取self 的方法。这使得无法调用Box&lt;dyn FnOnce()&gt;。当前的解决方法是使用Box&lt;dyn FnBox&gt;,它 确实实现了FnOnce()(但这并不直接适用于您的情况或上面的示例,因为您想使用Fn)。
  2. 尽管如此,it may one day become possible 打电话给Box&lt;dyn FnOnce()&gt;,所以FnBox 处于一种边缘地带 人们不想修复或稳定它以解决临时问题 问题。
  3. impl 添加到核心以使Fn() 工作可能与FnBox 发生冲突,但我不太了解。 There are several comments about this on issue #28796

Box&lt;dyn Fn()&gt; 实现Fn() 可能无法实现 按原样以语言完成。也有可能是 完成,但出于向前兼容性的原因,这是一个坏主意;它是 也有可能做到这一点,这是一个好主意,但没有人有 还没做。也就是说,按照现在的情况,你有几个 大多数不愉快的选择。

正如问题 cmets 中有人建议的那样,您可以自己制作 包装闭包的包装结构,并实现Fn() for Box&lt;Wrapper&lt;F&gt;&gt;.

你可以创建自己的 trait ProbeFn,它是为 任何正确类型的闭包,并为Box&lt;dyn ProbeFn&gt; 实现Fn()

在某些情况下,您可以使用 &amp;dyn Fn() 而不是 Box&lt;dyn Fn()&gt;。这在上面的示例中有效:

    call(&*h);

不同于Box&lt;dyn Fn()&gt;&amp;dyn Fn()确实实现了Fn()。它不是 不过,一般来说,因为显然它没有所有权。 但是,它确实适用于稳定的编译器——实现Fn() 自己需要不稳定的。

【讨论】:

  • 好吧,总结一下,如果我到目前为止了解所有内容。我的第一个示例中的工厂方法会编译,并生成一个 Boxed 闭包。这是必需的,因为目前不支持未调整大小的右值。简而言之,我永远无法将闭包拆箱以将其作为参数传递给 add_probe。如果未调整大小的右值是/是?实施到 rust 中,我可以将闭包拆箱以将其传递给 add_probe()。如果 Box 曾经实现 Fn() 本身,那么我可以将 Box 本身传递给 add_probe()?
  • 正确,我认为。由于Fn(...) 未调整大小,因此您无法取消装箱。如果(希望何时)实现了未设置大小的右值,FnBox 可能会被弃用,以便为装箱的 Fn 特征对象打开实现各种 Fn 特征的方式,.add_probe(..., boxed_probe_fn) 将开始工作。
  • @JamesNewman Here's what I imagine the ProbeFn solution might look like in your case。它肯定需要一些调整,但可能会给你一个粗略的想法
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-20
  • 1970-01-01
  • 2017-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多