在结构定义上指定绑定的Fn特性,而无需固定Fn参数之一 [英] Specify `Fn` trait bound on struct definition without fixing one of the `Fn` parameters

查看:107
本文介绍了在结构定义上指定绑定的Fn特性,而无需固定Fn参数之一的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a struct that contains a function object:

struct Foo<F> {
    func: F,
}

我想添加绑定到结构定义的Fn特征.问题是:我确实关心第一个参数(必须为i32),而不关心第二个参数.我实际上想写的是这样的:

I want to add an Fn trait bound to the struct definition. The problem is: I do care about the first parameter (it has to be i32), but not the second one. What I actually want to write is something like this:

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

所以用英语来说:类型F必须是一个带有两个参数的函数,其中第一个是i32(第二个可以是任何东西).上面的语法显然是无效的.我考虑了三种可能的解决方案:

So in English: the type F has to be a function that takes two parameters, the first of which is an i32 (and the second one can be anything). The syntax above is obviously not valid. I thought about three potential solutions:

  1. for<>语法在这里无济于事.除了它还不能用于非生命周期参数这一事实外,它是通用的(对于所有人")而不是存在的(存在").这样就可以了.

  1. The for<> syntax won't help here. Apart from the fact that it doesn't work for non-lifetime parameter yet, it is universal ("for all") and not existential ("there exists"). So that's out.

另一种可能性是向结构添加类型参数.我已经不喜欢这种解决方案了,因为该参数并非固有地属于该结构.

The other possibility is to add a type parameter to the struct. I already don't like that solution, because the parameter doesn't inherently belong to the struct.

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

但这是行不通的:除了where边界外,未使用参数P,因此编译器会抱怨.

But this doesn't work: the parameter P is not used, except in the where bound, so the compiler complains.

可以通过添加PhantomData<P>字段来解决此问题,但这不是必须的,更重要的是,用户无法再轻松使用struct构造函数语法.

This problem can be solved by adding a PhantomData<P> field, but this shouldn't be necessary and more importantly, users cannot use the struct constructor syntax easily anymore.

最后我尝试了此操作:

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

有没有办法实现我想要的?

旁注:为什么我想已经将特征绑定到结构上,而不是仅仅将重要的impl块绑定到了?

Side note: Why do I want to have the trait bound on the struct already instead of just the impl blocks where it's important?

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

First, once the "implied trait bounds" RFC is implemented, this allows me to omit the duplicate trait bounds from all the impl blocks. Second, with this bound, it helps the compiler with its type inference. Consider this:

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

如果可能的话(我在上面的PhantomData解决方案"中尝试过),编译器可以更容易地推断出闭包的第一个参数的类型.如果特征边界仅在impl块上指定,则编译器会遇到困难.

If the bound were possible (I tried it with the PhantomData "solution" above), the compiler can more easily infer the type of the closure's first argument. If the trait bounds would only be specified on impl blocks, the compiler has difficulties.

推荐答案

解决方案#2是我知道的在结构上使用界限的唯一方法.在我看来,如 Peter Hall建议所示,使它在结构上不受限制通常是可行的更好,因为它只将界限放在真正有意义的地方,但是如果您觉得麻烦,则唯一的选择是附加类型参数.

Solution #2 is the only way I know of to make this work with bounds on the struct. In my opinion making it work without bounds on the struct, as Peter Hall suggests, is usually preferable because it puts the bounds only where they are truly meaningful, but if you find that onerous, an extra type parameter is your only option.

  1. 另一种可能性是向结构添加类型参数.我已经不喜欢这种解决方案了,因为该参数并非固有地属于该结构.

第二个参数是必需的. Fn实现类型的参数的类型是Fn特质的参数,因此原则上可以同时具有impl Fn(i32, i32) for Ximpl Fn(i32, String) for X,就像可以同时具有impl AsRef<i32> for Ximpl AsRef<String> for X.

The second parameter is necessary. The types of the arguments of a Fn-implementing type are parameters of the Fn trait, so in principle you could have both impl Fn(i32, i32) for X and impl Fn(i32, String) for X, just as you can have both impl AsRef<i32> for X and impl AsRef<String> for X.

实际上,如果您不太看重,这就是HRTB的工作方式:一个函数可以在特定的生存期'x内实现Fn(&'x i32),或者可以实现for<'a> Fn(&'a i32),这意味着它可以实现无限数量的Fn特征.

In fact, if you don't look at it too hard, this is kind of how HRTBs already work: a function can implement Fn(&'x i32) for some particular lifetime 'x, or it can implement for<'a> Fn(&'a i32), which means there are an infinite number of possible Fn traits that it implements.

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

But you found the problem of adding a parameter for P: the parameter is unused.

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

This problem can be solved by adding a PhantomData<P> field, but this shouldn't be necessary

编译器会在结构内部查看其参数的差异.在这种情况下,假设P是一个参考类型.将Foo<_, &'static T>传递给需要Foo<_, &'a T>的函数是否安全?反过来呢?

The compiler peers inside structs to determine the variance of their parameters. In this case, suppose P is a reference type. Is it safe to pass a Foo<_, &'static T> to a function expecting a Foo<_, &'a T>? What about the other way around?

(作为链接的答案,约束-where子句-不用于确定方差,这就是为什么PhantomData在这里是必需的.)

(As the linked answer states, constraints -- where clauses -- don't count for determining variance, which is why PhantomData is necessary here.)

但是PhantomData成员不应PhantomData<P>,因为Foo<_, P>不包含P.它包含一个函数,该函数以P作为参数.相反,您应该使用PhantomData<fn(P)>,它向编译器发出信号,告知PFoo<F, P>的方差与fn(P)的方差相同-函数(指针)采用P.换句话说,FooP是相反的.对于人类读者来说,这似乎是多余的-毕竟,我们已经有一个F成员,并且FP中必须是互变的.但是,好吧,编译器还不足以得出结论,因此您必须将其拼写出来.

But the PhantomData member shouldn't be PhantomData<P>, because Foo<_, P> doesn't contain a P. It contains a function that takes a P as an argument. Instead, you should use PhantomData<fn(P)>, which signals to the compiler that the variance of Foo<F, P> in P is the same as the variance of fn(P) -- a function (pointer) taking P. In other words, Foo is contravariant in P. To the human reader, this might seem redundant -- after all, we already have an F member, and F must be contravariant in P. But, well, the compiler isn't really smart enough to draw that conclusion, so you have to spell it out.

(有关更严格的信息,请参见 Nomicon中有关分型的部分差异的解释.)

(See the section of the Nomicon on subtyping for a more rigorous explanation of variance.)

哪位带给我我最后的反对意见:

Which brings me to your final objection:

更重要的是,用户再也无法轻松使用struct构造函数语法.

and more importantly, users cannot use the struct constructor syntax easily anymore.

不幸的是,除了编写一个不错的构造函数"之外,我想不出解决方案.也许有一天,一个更聪明的编译器将减轻这一负担,但是目前,PhantomData才是我们所拥有的.

Unfortunately, I can't think of a solution to this besides "write a nice constructor function". Perhaps a smarter compiler will one day lift this burden, but for now, PhantomData is what we have.

这篇关于在结构定义上指定绑定的Fn特性,而无需固定Fn参数之一的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆