提问人:SkyPPeX 提问时间:11/1/2022 最后编辑:Chayim FriedmanSkyPPeX 更新时间:11/1/2022 访问量:136
如何使用不同的泛型参数约束泛型约束?
How can i constrain a generic constraint using a different generic argument?
问:
所以我有一个结构。pub struct Foo<TFn, TArg, TReturn> where TFn: Fn(TArg) -> TReturn { func: TFn }
这让我习惯了 C# 泛型,但为什么它在 rust 中不起作用? 我希望字段“func”的类型为 Fn,其中参数的类型为“TArg”,返回值的类型为“TReturn”。
编译器抱怨参数“TArg”和“TReturn”从未使用过,但它们有助于定义 TFn 值的签名。
我尝试删除“从未使用过”的参数,只是在约束中显式写入类型。这很好用。
答:
在 rust 中,你的结构必须使用它所泛型的所有泛型类型。并使用 mean,它们必须出现在至少一种类型的字段中。您可以使用特殊类型 PhantomData
解决您的问题。它是一种标记类型,用于向编译器提供附加信息,并在编译时删除。这些文档甚至为您提供了如何使用它来解决“未使用的类型参数”错误的示例。TLDR 在这里:
use std::marker::PhantomData;
pub struct Foo<TFn, TArg, TReturn>
where
TFn: Fn(TArg) -> TReturn
{
func: TFn,
_marker: PhantomData<(TArg, TRetur)> // This line tells the compiler that
// this struct should act like it owned
// type (Targ, TReturn)
}
当你想创建结构的实例时,只需把作为一个值:PhantomData
let s = Foo { func: f, _marker: PhantomData };
评论
Foo
TArg
TReturn
@Aleksander克劳兹的答案是正确的,但没有考虑到一个利基问题:方差。考虑一下:
fn x<'s>(&'s str) -> String { // ...
x
是一个可以处理任何存在的函数。特别是,这意味着可以处理任何寿命长于 ,例如。这是真的,因为是 的子类型。在 Rust 中,作为 (written) 的子类型意味着只要我能接受 a ,我也可以接受 a ,因为子类型可以做它的超类型可以做的任何事情,甚至更多(例如,活得更久)。&str
's
x
&str
's
&'static str
&'static str
&'s str
T
U
T: U
U
T
子类型最常出现在 Rust 中的生命周期中,如果寿命超过(即长于),则生命周期是生命周期的子类型。所以如果我能接受一个,我也可以接受一个只要。换言之,是 if 的子类型是 的子类型。这称为协方差,是所谓的方差的一个实例。'a
'b
'a
'b
&'b T
&'a T
'a: 'b
&'a T
&'b T
'a
'b
但是,函数在这方面很有趣。如果我有类型,并且是 if 的子类型,而不是相反。也就是说,如果我需要一个可以处理长生存期的函数,那么一个可以处理较短生存期的函数也可以,因为我可以传递的任何参数都将是它期望的参数的子类型。这称为逆变,是我们非常希望函数具有的属性。fn(&'a T)
fn(&'b T)
fn(&'a T)
fn(&'b T)
'b
'a
你的类型或多或少是一个函数,所以我们希望它表现得像一个函数,并在它的参数上是逆变的。但事实并非如此。它是协变的。这是因为 Rust 中的结构体从其字段继承了它的方差。类型在 和 上是协变的(因为类型是),因此将在 上协变。为了让它像函数一样运行,我们可以用适当的类型标记它:.这将是逆变和协变(函数在其返回类型上是协变的;我希望从上面的解释中得出结论)。Foo
PhantomData<(TArg, TReturn)>
TArg
TReturn
(TArg, TReturn)
Foo
TArg
PhantomData<fn(TArg) -> TReturn>
TArg
TReturn
我写了一个小例子(尽管是人为的例子)来演示不正确的方差如何破坏应该工作的代码:
use std::marker::PhantomData;
pub struct Foo<TFn, TArg, TReturn>
{
func: TFn,
// this makes `Foo` covariant over `TArg`
_marker: PhantomData<(TArg, TReturn)>
}
impl<TFn, TArg, TReturn> Bar<TFn, TArg, TReturn>
where
TFn: Fn(TArg) -> TReturn,
{
// presumably this is how one might use a `Foo`
fn call(&self, arg: TArg) -> TReturn {
(self.func)(arg)
}
}
// `foo_factory` will return a `Foo` that is covariant over the lifetime `'a`
// of its argument
fn foo_factory<'a>(_: &'a str) -> Foo<fn(&'a str) -> String, &'a str, String> {
// We only care about the type signatures here
panic!()
}
fn main() {
let long_lifetime: &'static str = "hello";
// we make a `Foo` that is covariant over the `'static` lifetime
let mut foo = foo_factory(long_lifetime);
foo.call("world");
{
let short_lifetime = String::from("world");
// and because it's covariant, it can't accept a shorter lifetime
// than `'static`
// even though this should be perfectly fine, it won't compile
foo = foo_factory(&short_lifetime);
}
foo.call("world");
}
但是,如果我们修复方差:
pub struct Foo<TFn, TArg, TReturn> {
func: TFn,
// `Foo` is now _contravariant_ over `TArg` and covariant over `TReturn`
_marker: PhantomData<fn(TArg) -> TReturn>,
}
上面的函数现在将像人们预期的那样编译得很好。main
有关 Rust 中的方差以及它与数据结构和丢弃检查的关系的更多信息,我建议查看关于它的 'nomicon 章节和 PhantomData
章节。
评论