我有一个包含函数对象的 struct :

struct Foo<F> {
    func: F,
}

我想在 struct 定义中添加一个Fn trait绑定.问题是:我确实关心第一个参数(必须是i32),但不关心第二个参数.我真正想写的是这样的:

struct Foo<F> 
where
    ∃ P so that F: Fn(i32, P),
{
    func: F,
}

所以在英语中,类型F必须是一个接受两个参数的函数,第一个参数是i32(第二个参数可以是任何参数).上述语法显然无效.我想到了三种可能的解决方案:

  1. for<>语法在这里没有帮助.除此之外,它还不适用于非生命参数,它是通用的("所有")而不是存在的("存在").这就结束了.

  2. 另一种可能是向 struct 添加类型参数.我已经不喜欢这个解决方案了,因为参数本身并不属于 struct .

    struct Foo<F, P> 
    where
        F: Fn(i32, P),
    {
        func: F,
    }
    

    但这不起作用:参数P没有被使用,除非在where绑定中,所以编译器会抱怨.

    这个问题可以通过添加PhantomData<P>字段来解决,但这不应该是必要的,更重要的是,用户不能再轻易地使用struct构造函数语法了.

  3. 最后我试了一下:

    struct Foo<F> 
    where
        F: Fn(i32, _),
    {
        func: F,
    }
    

    但这也不起作用:

    error[E0121]: the type placeholder `_` is not allowed within types on item signatures
     --> src/main.rs:3:20
      |
    3 |         F: Fn(i32, _),
      |                    ^ not allowed in type signatures
    

Is there a way to achieve what I want?


Side note:为什么我想把trait绑定到struct上,而不是仅仅绑定到重要的impl个块上?

首先,一旦实现了"隐含特征边界"RFC,这就允许我从所有impl个块中省略重复的特征边界.其次,有了这个界限,它可以帮助编译器进行类型推断.考虑一下:

struct Foo<F, T> 
where
    F: Fn(T, _),
{
    data: T,
    F: F,
}

如果边界是可能的(我用上面的PhantomData个"解决方案"进行了try ),编译器可以更容易地推断闭包的第一个参数的类型.如果只在impl块上指定trait界限,编译器就会遇到困难.

推荐答案

解决方案#2是我所知道的唯一一种在 struct 上使用边界的方法.在我看来,让它在 struct 上工作without个边界,比如Peter Hall suggests,通常是更可取的,因为它只把边界放在真正有意义的地方,但是如果你觉得很繁重,一个额外的类型参数是你唯一的 Select .

  1. 另一种可能是向 struct 添加类型参数.我已经不喜欢这个解决方案了,因为参数本身并不属于 struct .

第二个参数是必需的.Fn实现类型的参数类型是parameters of the 100 trait,所以原则上可以有impl Fn(i32, i32) for Ximpl Fn(i32, String) for X,就像可以有impl AsRef<i32> for Ximpl AsRef<String> for X一样.

事实上,如果你不太仔细看的话,这就是HRTBs已经在工作的方式:一个函数可以在大约particular个生命周期内实现Fn(&'x i32)'x,或者它可以实现for<'a> Fn(&'a i32)个,这意味着它可以实现无限多的Fn个特征.

但您发现了为P添加参数的问题:该参数未使用.

这个问题可以通过添加PhantomData<P>字段来解决,但这不是必需的

The compiler peers inside structs to determine the variance of their parameters.在本例中,假设P是引用类型.将Foo<_, &'static T>传递给预期为Foo<_, &'a T>的函数安全吗?反过来呢?

(正如链接的答案所述,约束条件——where个从句——不算作确定方差的条件,这就是为什么这里需要PhantomData个从句的原因.)

但是PhantomData个成员shouldn't应该是PhantomData<P>,因为Foo<_, P>不包含P.它包含一个function that takes a 103 as an argument.相反,您应该使用PhantomData<fn(P)>,它向编译器发出信号,PFoo<F, P>的方差与fn(P)的方差相同——一个函数(指针)取P.换句话说,FooP中是反变的.对人类读者来说,这似乎是多余的——毕竟,我们已经有了F个成员,而FP中必须是反变的.但是,编译器还不够聪明,不能得出这样的结论,所以你必须把它解释清楚.

(有关更严格的方差解释,请参见the section of the Nomicon on subtyping.)

这就引出了你最后的反对意见:

更重要的是,用户不能再轻易地使用struct构造函数语法了.

不幸的是,除了"编写一个好的构造函数",我想不出解决这个问题的方法.也许有一天,一个更智能的编译器会减轻这个负担,但现在,PhantomData是我们所拥有的.

Rust相关问答推荐

收集RangeInclusive T到Vec T<><>

当Option为None时,Option数组是否占用Rust中的内存?

为什么我可以跟踪以前borrow 过的变量?房主在哪里?

在Rust中,在实现特征`Display`时,如何获取调用方指定的格式?

将数组转换为HashMap的更简单方法

在铁 rust 中传递所有权

将PathBuf转换为字符串

Pin<;&;mut可能将Uninit<;T>;>;合并为Pin<;&;mut T>;

如果死 struct 实现了/派生了一些特征,为什么Rust会停止检测它们?

如何实现Serde::Ser::Error的调试

将Vec<;U8&>转换为Vec<;{Float}&>

如何使用Actix Web for Rust高效地为大文件服务

找不到 .has_func 或 .get_func 的 def

当我try 使用 SKI 演算中的S I I实现递归时,为什么 Rust 会失败?

Rust 并行获取对 ndarray 的每个元素的可变引用

无法将`&Vec>`转换为`&[&str]`

有什么方法可以通过使用生命周期来减轻嵌套生成器中的当生成器产生时borrow 可能仍在使用错误?

Rust 中的自动取消引用是如何工作的?

为什么 File::read_to_end 缓冲区容量越大越慢?

为什么1..=100返回一个范围而不是一个整数?