【问题标题】:Generic parameter bounded by another parameter由另一个参数限制的通用参数
【发布时间】:2019-02-10 22:51:38
【问题描述】:

我正在编写一个模拟框架。为此,我需要接受一个可用作替代另一个函数的函数并将其存储。我当前的设计通过强制相同的输入和输出类型来做到这一点,但它在强制正确的生命周期方面完全失败了。

相反,我需要编写一个通用函数,它接受一个基函数及其替代:

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

Playground

“替代”是指一个函数,它至少接受 base 接受的每个参数,并且每个参数都返回至少在每个地方都可用的值,其中从 base 返回的值是。例如:

fn foo(_: &str) -> &'static str {
    "foo"
}
fn bar(s: &str) -> &str {
    s
}
fn baz(_: &str) -> &'static str {
    "baz"
}

bazfoobar 的替代品,因为它返回的字符串既可以用来代替'static,也可以依赖于借用。 bar不是替代foo,因为借用值不能用来代替'static

我想创建这样的东西,但它无法编译:

//                            FAILURE
//                               V
fn selector<U, V, F: Fn(U) -> V, G: F>(base: F, subs: G) {...}

问题是我无法表达FG 之间的关系。 Rust 似乎没有具体类型的超类型的概念。

【问题讨论】:

    标签: generics rust traits


    【解决方案1】:

    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));
    }
    

    为什么会这样? outerinner 显然有不同的生命周期!因为在调用函数的时候,Rust 会考虑子类型化,看函数是否可以被调用。特别是,&amp;'outer i32 类型是&amp;'inner i32 的子类型,因此可以毫无问题地将其转换为&amp;'inner i32,并且可以调用该函数。


    将此想法应用于您的问题意味着您的函数只有一种函数类型,并且两个参数(basesubs)都具有该类型:

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

    如果我们这样尝试,不幸的是,我们仍然会收到错误:“expected fn item, found a different fn item”。这与“函数项与函数指针”问题有关。您可以了解更多关于 in this answerin 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); 
    

    (Playground)

    这实际上按预期工作:前两个调用很好,但第三个错误:

    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`
    

    但是,在调用站点显式转换函数类型仍然有点难看。不幸的是,我还没有找到隐藏它的方法。我尝试编写一个强制强制执行的宏,但是当函数指针具有不同的类型(包括第二个示例)时,这不起作用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-07-11
      • 2017-11-04
      • 2019-07-22
      • 1970-01-01
      • 1970-01-01
      • 2015-03-30
      • 2011-03-05
      • 2012-10-26
      相关资源
      最近更新 更多