Beginner: Sort a vector with one of many closures, depending on a setting

Basically, I want to get this code to compile:

I know why this does not compile. But I don't know how I can solve it instead. I tried a couple of different things, including wrapping the closures in Box::new(), but I couldn't get it to work.

At first, I wanted to ignore the problem and just move all the sorting logic, including the conditionals, inside a single closure. But then I had to add a reverse sorting option. Because I really would like to use *_by_key for simplicity, I tried with std::cmp::Reverse, and this happened:

Again, I understand why the error occurs, but I am not sure how to fix it.

Any help appreciated.

1 Like

The key functions are returning different types, and I can't think of a way to unify that. With some traits you might be able to use a trait object, but you can't use -> &Ord because Ord is not object safe.

You can use sort_by instead, because then the closures all return Ordering.

For example:

    let sorter: fn(&Person, &Person) -> Ordering;
    if sort_by == "age" {
        sorter = |p1, p2| p1.age.cmp(&p2.age);
    }
    else if sort_by == "name" {
        sorter = |p1, p2| p1.name.cmp(&p2.name);
    }
    x.sort_by(sorter);

Next, rust will complain that my sorter is not initialized on all paths. This will need an else case to implement some default behavior -- return early, panic, whatever.

2 Likes

I'd go with @cuviper's solution. But, just wanted to throw out one type-based way to do this (still using sort_by): play

This is overkill for a simple case like this (field + whether to reverse). But, I kind of like the symmetry in the match statement between the dynamic args and the type used to do the sorting:

match (sort_by, reverse) {
        ("name", true) => x.sort_by(Sort::<Name, True>::sort),
        ("name", false) => x.sort_by(Sort::<Name, False>::sort),
        ("age", true) => x.sort_by(Sort::<Age, True>::sort),
        ("age", false) => x.sort_by(Sort::<Age, False>::sort),
        _ => panic!(),
    }

There's an in-between way where you can build up a struct that contains all the parameters of how sorting should be done, and then use it inside the sort_by closure. Without using types like the above, however, it'll involve dynamic checks on each comparison, which will likely have an impact on performance. But, it'll involve more straightforward code and fewer types.

Or maybe the above is insane, irrespective of how complex a real sorting scenario might be - you be the judge :crazy_face:

Another potential solution would involve wrapping something like a Box<Fn(&Person) -> u32> in a newtype, and implementing the sorting logic for that type.

It occurred to me that you can create a trait that encapsulates the knowledge of how to call sort_by_key, and blanket-impl it for all closure types that return things that are Ord. I didn't like it enough to put it on Stack Overflow, but I'll post it here (playground):

trait KeyFunc {
    fn sort(&mut self, slice: &mut [Person]);
}

impl<F, O> KeyFunc for F
where
    F: for<'a> FnMut(&'a Person) -> O,
    O: Ord,
{
    fn sort(&mut self, slice: &mut [Person]) {
        slice.sort_by_key(self);
    }
}

fn main() {
    // ...
    let mut key_func;

    if sort_by == "age" {
        key_func = Box::new(|item: &Person| {
            return &item.age;
        }) as Box<KeyFunc>;
    } else if sort_by == "name" {
        key_func = Box::new(|item: &Person| {
            return &item.name;
        }) as Box<KeyFunc>;
    } else {
        panic!();
    }

    key_func.sort(&mut x);
}

You could definitely find a way to make reversed sorting work; the part I found interesting was erasing the return type of the closure so that you can stuff all the different closures into the same type.

But don't do this; just use sort_by instead. Simpler is better.

1 Like

Thanks every one. I accepted that I will not be able to use sort_by_key for this directly, but there are some really cool creative solutions here - at times are still a bit over my head, so there is much to learn!