我正在编写一个模拟框架。为此,我需要接受一个可以替代另一个功能并存储它的功能。我当前的设计通过强制使用相同的输入和输出类型来做到这一点,但是它在强制正确的生存期内完全失败。
相反,我需要编写一个通用函数,该函数接受基本函数及其替代物:
fn main() {
selector(foo, baz, "local", false);
selector(bar, baz, "local", false);
selector(foo, bar, "local", false); // SHOULD FAIL, bar is NOT substitute of foo
}
fn foo(_: &str) -> &'static str {
"foo"
}
fn bar(s: &str) -> &str {
s
}
fn baz(_: &str) -> &'static str {
"baz"
}
// DOES NOT COMPILE
// fn selector<U, V, F: Fn(U) -> V, G: F>(base: F, subs: G, arg: U, use_base: bool) -> V {
// match use_base {
// true => base(arg),
// false => subs(arg),
// }
// }
// COMPILES, but is too weak
fn selector<U, V, F: Fn(U) -> V, G: Fn(U) -> V>(base: F, subs: G, arg: U, use_base: bool) -> V {
match use_base {
true => base(arg),
false => subs(arg),
}
}
“代用”是指一个函数,该函数至少接受base接受的每个参数,并且对于每个参数,返回的值至少在每个地方都可用(从base返回的值所在)。例如:
fn foo(_: &str) -> &'static str {
"foo"
}
fn bar(s: &str) -> &str {
s
}
fn baz(_: &str) -> &'static str {
"baz"
}
baz
可以替代foo
和bar
,因为它的返回字符串可以代替'static
使用,也可以依赖于借用。 bar
不是替代foo
,因为不能用借来的值代替'static
。
我想创建类似这样的东西,但是不能编译:
// FAILURE
// V
fn selector<U, V, F: Fn(U) -> V, G: F>(base: F, subs: G) {...}
问题是我无法表达F
和G
之间的关系。 Rust似乎没有混凝土类型的超类型的概念。
答案 0 :(得分:4)
Rust还了解这些类型的“兼容性”:全部与subtyping and variance有关。技巧是简单地使两个参数具有相同的类型,至少就函数而言而言。
让我们先尝试一些简单的事情:
// Both arguments have the same type (including the same lifetime)
fn foo<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {
x
}
let outer = 3;
{
let inner = 27;
println!("{}", foo(&outer, &inner));
}
为什么这样做? outer
和inner
显然有不同的生存期!因为在调用函数时,Rust考虑子类型化以查看是否可以调用该函数。特别地,类型&'outer i32
是&'inner i32
的子类型,因此可以毫无问题地将其转换为&'inner i32
并可以调用该函数。
将此想法应用于您的问题意味着您的函数只有一种函数类型,并且两个参数(base
和subs
)都具有该类型:
fn selector<U, V, F: Fn(U) -> V>(base: F, subs: F, arg: U, use_base: bool) -> V {
match use_base {
true => base(arg),
false => subs(arg),
}
}
如果我们这样尝试,很不幸,我们仍然会收到错误消息:“ 预期的fn项目,找到了另一个fn项目”。这与“功能项与功能指针”问题有关。您可以了解有关in this answer或in the Rust reference的更多信息。可悲的是,在这种情况下不会强制使用函数指针,因此一种方法是显式转换函数:
type FnStatic = fn(&str) -> &'static str;
type FnWeaker = fn(&str) -> &str;
selector(foo as FnStatic, baz as FnStatic, "local", false);
selector(bar as FnWeaker, baz as FnStatic, "local", false);
selector(foo as FnStatic, bar as FnWeaker, "local", false);
这实际上可以按预期工作:前两个调用很好,但是第三个错误是:
error[E0308]: mismatched types
--> src/main.rs:7:31
|
7 | selector(foo as FnStatic, bar as FnWeaker, "local", false);
| ^^^^^^^^^^^^^^^ expected concrete lifetime, found bound lifetime parameter
|
= note: expected type `for<'r> fn(&'r str) -> &str`
found type `for<'r> fn(&'r str) -> &'r str`
但是,在调用站点显式地转换函数类型仍然有些丑陋。不幸的是,我还没有找到隐藏它的方法。我尝试编写一个强制强制的宏,但是当函数指针具有不同的类型(包括第二个示例)时,该宏不起作用。