在结构定义上指定“Fn"特征绑定而不修复“Fn"参数之一 [英] Specify `Fn` trait bound on struct definition without fixing one of the `Fn` parameters

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

问题描述

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

struct Foo{功能:F,}

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

struct Foo在哪里∃ P 使得 F: Fn(i32, P),{功能:F,}

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

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

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

    struct Foo在哪里F: Fn(i32, P),{功能:F,}

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

    这个问题可以通过添加一个PhantomData

    字段来解决,但这应该不是必要的,更重要的是,用户不能再轻松地使用struct构造函数语法了.

  3. 最后我试过这个:

    struct Foo在哪里F: Fn(i32, _),{功能:F,}

    但这也不起作用:

    error[E0121]: 类型占位符 `_` 在项目签名的类型中是不允许的-->src/main.rs:3:20|3 |F: Fn(i32, _),|^ 不允许在类型签名中

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

<小时>

附注:为什么我要在结构上绑定特征,而不是仅仅在重要的 impl 块上绑定?

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

struct Foo在哪里F: Fn(T, _),{数据:T,F: F,}

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

解决方案

解决方案 #2 是我所知道的在结构上使用边界的唯一方法.在我看来,使其没有在结构上工作,正如 Peter Hall 建议的,通常是更可取,因为它只将边界放在真正有意义的地方,但如果您发现这很麻烦,那么额外的类型参数是您唯一的选择.

<块引用>

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

第二个参数是必需的.Fn 实现类型的参数类型是 Fn trait 的参数,所以原则上你可以同时拥有 implFn(i32, i32) for Ximpl Fn(i32, String) for X,就像你可以同时拥有 impl AsRef;对于 Ximpl AsRef对于 X.

事实上,如果你不仔细看,这就是 HRTB 的工作方式:一个函数可以为某些 实现 Fn(&'x i32)特定的生命周期'x,或者它可以实现for<'a>Fn(&'a i32),这意味着它实现了无数可能的 Fn 特性.

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

<块引用>

这个问题可以通过添加一个PhantomData

字段来解决,但这应该不是必需的

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

(正如链接的答案所述,约束——where 子句——不计入确定方差,这就是为什么 PhantomData 在这里是必要的.)>

但是PhantomData成员不应该PhantomData

,因为Foo<_, P>> 不包含 P.它包含一个以 P 作为参数的函数.相反,您应该使用 PhantomData,它向编译器发出信号,FooP 中的方差code>与fn(P)的方差相同——一个函数(指针)取P.换句话说,FooP 中是逆变的.对于人类读者来说,这似乎是多余的——毕竟,我们已经有一个 F 成员,并且 FP 中必须是逆变的.但是,好吧,编译器不够聪明,无法得出这个结论,所以你必须把它拼出来.

(请参阅 Nomicon 关于子类型的部分以获得更严格的方差的解释.)

这让我提出了您的最终反对意见:

<块引用>

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

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

I have a struct that contains a function object:

struct Foo<F> {
    func: F,
}

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,
}

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. 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.

  2. 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,
    }
    

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

    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.

  3. Lastly I tried this:

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

    But this also doesn't work:

    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: Why do I want to have the trait bound on the struct already instead of just the impl blocks where it's important?

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,
}

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.

解决方案

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. 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.

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.

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.

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

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

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?

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

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.

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

Which brings me to your final objection:

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

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天全站免登陆