【问题标题】:A Rust closure that takes a trait as argument一个以 trait 作为参数的 Rust 闭包
【发布时间】:2020-12-27 20:55:35
【问题描述】:

我希望我的函数将闭包作为参数,它将PartialOrd(或任何其他特征,这只是一个示例)作为参数,并且我希望能够以任何类型调用该闭包在我的函数中实现PartialOrd。类似这样的东西:

fn my_func(cmp: fn(impl PartialOrd, impl PartialOrd) -> bool) {
    cmp(3, 5);
}

但这当然不起作用,因为impl Trait 在闭包签名中无效。 (我不明白为什么,但我猜它还没有实现。)所以我尝试了这样的事情:

fn my_func<T: PartialOrd>(cmp: fn(T, T) -> bool) {
    cmp(3, 5);
}

这也无法编译,因为(我猜)T 是由调用者确定的,而我只是传递了 i32,它可能与调用者打算调用的内容不同。

那么,我该怎么做呢?我的主要目的是做这样的事情:

fn my_func(cmp: fn(impl PartialOrd, impl PartialOrd) -> bool) {
    if some_condition {
        cmp(type1_instance, type1_instance_2);
    } else {
        cmp(type2_instance, type2_instance_2);
    }
}

type1type2 都实现了 PartialEq

为了让它更具体,我正在使用chrono 库并想写这样的东西:

fn compare_dates(d1: DateTime, d2: DateTime, cmp: fn(impl PartialOrd, impl PartialOrd) -> bool) -> bool {
    if some_condition {
        cmp(d1, d2)
    } else {
        cmp(d1.date(), d2.date())
    }
}

compare_dates(
    Utc.date(2000 10, 11).and_hour(0,0,0),
    Utc.date(2000 10, 12).and_hour(0,0,0),
    PartialOrd::lt)

注意:DateTime::date() 不返回 DateTime,它返回不同的 Date 类型。

【问题讨论】:

  • 这听起来像是XY problem。既然知道类型是i32,为什么还要使用泛型类型参数?你有点试图做与类型参数相反的事情。 my_func fn 不决定 T 将是什么,调用者决定。
  • @vallentin 类型不是i32,我在底部描述了我真正的问题是不属于XY。该参数可能是实现给定特征的两种不同类型之一。根据条件,我想使用 type1type2 实例调用闭包。
  • 我注意到type*_instance*,但在那个例子中它们是什么类型?我说的是 XY 问题,因为您并没有真正给出上下文和案例的具体示例。
  • @vallentin 我进行了编辑并使用了一个更具体的示例。让我知道这是否有效。
  • 你不能那样做。因为cmp 只会在你的函数声明中是通用的。因此,当您最终调用 compare_dates 时,T 将是一个具体类型。您可能可以用Any 做一些疯狂的事情来实现这一点。但是,您可能无法简单地执行compare_dates(..., PartialOrd::lt)。最简单的可能有 2 个cmp fns。你有没有一个例子说明cmp 会做的不仅仅是&lt;

标签: rust


【解决方案1】:

使用单个cmp 的问题在于,虽然它在函数声明中可能是通用的,但在调用函数时它会变得专门化。因此它不能同时用于DateTimeDate(假设您想避免涉及Any 的更复杂的事情。)

您可以通过设置两个 cmp 参数来解决此问题。但是,您仍然可以对实际的 cmp 函数进行单一定义。

// chrono = "0.4.19"
use chrono::{Date, DateTime, TimeZone, Utc};

fn compare_dates<F, G>(
    d1: DateTime<Utc>,
    d2: DateTime<Utc>,
    cmp_date_times: F,
    cmp_dates: G,
) -> bool
where
    F: FnOnce(&DateTime<Utc>, &DateTime<Utc>) -> bool,
    G: FnOnce(&Date<Utc>, &Date<Utc>) -> bool,
{
    let some_condition = ...;
    if some_condition {
        cmp_date_times(&d1, &d2)
    } else {
        cmp_dates(&d1.date(), &d2.date())
    }
}

现在您可以定义一个通用的cmp 函数并将其用于两者。

fn cmp<T: PartialOrd>(lhs: &T, rhs: &T) -> bool {
    lhs < rhs
}

compare_dates(
    Utc.ymd(2000, 10, 11).and_hms(0, 0, 0),
    Utc.ymd(2000, 10, 12).and_hms(0, 0, 0),
    cmp,
    cmp,
    // or
    // cmp::<DateTime<Utc>>,
    // cmp::<Date<Utc>>,
);

你也可以直接传PartialOrd::lt

compare_dates(
    Utc.ymd(2000, 10, 11).and_hms(0, 0, 0),
    Utc.ymd(2000, 10, 12).and_hms(0, 0, 0),
    PartialOrd::lt,
    PartialOrd::lt,
    // or
    // <DateTime<Utc> as PartialOrd>::lt,
    // <Date<Utc> as PartialOrd>::lt,
    // or
    // DateTime::<Utc>::lt,
    // Date::<Utc>::lt,
);

如果你想避免重复参数,那么你也可以定义一个compare_dates!宏。

macro_rules! compare_dates {
    ($d1:expr, $d2:expr, $cmp:expr $(,)?) => {
        compare_dates($d1, $d2, $cmp, $cmp)
    };
}

然后你这样称呼它:

compare_dates!(
    Utc.ymd(2000, 10, 11).and_hms(0, 0, 0),
    Utc.ymd(2000, 10, 12).and_hms(0, 0, 0),
    PartialOrd::lt,
);

compare_dates!(
    Utc.ymd(2000, 10, 11).and_hms(0, 0, 0),
    Utc.ymd(2000, 10, 12).and_hms(0, 0, 0),
    cmp,
);

【讨论】:

    【解决方案2】:

    但这当然行不通,因为 impl Trait 在闭包签名中无效。 (我不明白为什么,但我猜它还没有实现。)

    我认为不可能;这会像

    这样的东西脱糖
    fn my_func(cmp: for<T1: PartialOrd, T2: PartialOrd> fn(T1, T2) -> bool) {
        cmp(3, 5);
    }
    

    这不是合法类型(尽管我刚刚了解到在该位置使用生命周期参数is legal,例如for&lt;'a&gt; fn(&amp;'a i32) -&gt; &amp;'a i32)。还要注意T1T2 是不同的类型,所以PartialOrd::lt 不会有这种类型。如果一开始就支持它,您可以在没有impl 的情况下明确编写它:

    fn my_func(cmp: for<T: PartialOrd> fn(T, T) -> bool) {
        cmp(3, 5);
    }
    

    但是,另一种解决方法是将闭包类型转换为特征,并将您想要的特定闭包转换为结构的此特征的实现。在这种情况下是一个单元结构,因为闭包没有捕获任何东西:

    trait Cmp {
        fn compare<T: PartialOrd>(&self, x: T, y: T) -> bool;
    }
    
    struct Lt;
    
    impl Cmp for Lt {
        fn compare<T: PartialOrd>(&self, x: T, y: T) -> bool {
            x < y
        }
    }
    
    fn compare_dates(d1: DateTime, d2: DateTime, cmp: impl Cmp) -> bool {
        if some_condition {
            cmp.compare(d1, d2)
        } else {
            cmp.compare(d1.date(), d2.date())
        }
    }
    

    如您所见,您只有一个 cmp 参数,它起到闭包的作用,甚至可以在本地定义:

    fn main() {
        struct Lt;
        
        impl Cmp for Lt {
            fn cmp<T: PartialOrd>(&self, x: T, y: T) -> bool {
                x < y
            }
        }
    
        println!("{}", my_func(Lt));
    }
    

    但仍然不如闭包方便。与 vallentin 的答案相比,这是否是一个好的权衡取决于具体情况。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-01
      • 1970-01-01
      • 2016-04-14
      • 2022-11-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多