Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A Rust closure that takes a trait as argument

Tags:

rust

I want my function to take a closure as an argument, which takes a PartialOrd (or any other trait, this is just an example) as an argument and I want to be able to call that closure with a any type that implements PartialOrd in my function. Something along the lines of this:

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

But of course this does not work because impl Trait is not valid in closure signatures. (I don't see why but I guess it not implemented yet.) So I tried something like this:

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

This also doesn't compile because (I guess) T is determined by the caller and I'm just passing i32 and it might be different that what the caller is intended to call.

So, how do I go about this? My main purpose is to do something like this:

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

Both type1 and type2 implements PartialEq.

To make it more concerete, I'm using chrono library and want to write something like this:

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)

Note: DateTime::date() does not return DateTime, it returns a different Date type.

like image 341
isamert Avatar asked Oct 24 '25 19:10

isamert


1 Answers

The issue with having a single cmp is that while it might be generic in the function declaration, it becomes specialized when the function is called. Thereby it cannot be used for both DateTime and Date. (Assuming you want to avoid something even more complicated involving Any.)

You can workaround this by having having two cmp paramters. However you can still have a single definition of your actual cmp function.

// 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())
    }
}

Now you can define a single generic cmp function and use it for both.

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

You can also just pass 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,
);

If you want to avoid the repeated argument, then you can define a compare_dates! macro as well.

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

Which you then call like this:

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,
);
like image 65
vallentin Avatar answered Oct 26 '25 19:10

vallentin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!