Min_update/max_update?

I usually write Rust code like:

my_data[index] = min(my_data[index], x);

Now with #![feature(ord_max_min)] I can write:

my_data[index] = my_data[index].min(x);

That operation is common and it's essentially a (there are languages that allow a syntax like this):

my_data[index] min= x;

So is it a good idea to also add ord::min_update/ord::max_update?

my_data[index].min_update(x);

In my code probably more than half usages of min/max are for updates like that.

(Now I remember that I suggested something like that time ago: On learning specialization and UFCS - #16 by leonardo - Rust Internals ).

3 Likes

I find the name of this min_update quite misleading. I would suggest more something like clip or bound.

First there's the decision if this function is desired, and later there's the decision of the name.

Regarding your name suggestions, how do you like to call the other function that I've temporarily named max_update?

I would guess my immediate thought would be to have a clip_left/right or bound_left/right or have a clip/bound with Option args.
Otherwise, I personally most prefer min(a,b) as it is very clear and mathematically standard way of writing things.

This isn't a good comparison because in Rust (unlike mathematics and functional languages) you also often need to update variables.

Leaving the naming bikeshed aside, I suppose the real question is just how common is this use case for others? Then, can the compiler generate better code with the min_update(x) "in-place" update? Or is it able to optimize the seemingly 2 step process (compute min/max, move/copy the value into storage) all on its own, and do so "reliably". This latter part is akin to the various in-place update std::ops traits.

how common is this use case for others?

For me it's common because I'm using mutation a lot. If your Rust code is mostly functional, then it's much less common.

In all or most cases LLVM in release mode is probably able to produce the same code, so it's not an optimization. The purpose of such pair of functions is mostly to make the code more DRY and less bug-prone, reducing the number of tokens of your code, and making your intents more explicit.

Unless you are chaining things in a similar ways to iterators, I don't see how this will reduce significantly in anyway the number of tokens, which is literally 2 tokens less. Also, updating variables with a single function call rather than assignment I don't think it will be more clear to the wider audience. I thought the initial intention was exactpy as vitalyd said for optimising the explicit assignment.

The number of tokens of difference is not fixed, see:

my_data[a - b] = min(my_data[a - b], x);

I hear you, but I tend to agree with @Botev that this doesn't appear to be a significant enough win (and some may not even agree it's a win at all). The bar for making this an API in std has to be pretty high, or else everyone's favorite helper functionality is a candidate for inclusion. It seems that you could define your own helper fn/macro that does it for you. Unless this is a widely used pattern by others, I don't think it carries its weight. Just my $.02 of course.

It's easy to implement as an extension trait:

trait OrdUpdate: Sized + Ord {
    fn min_update(&mut self, other: Self) -> &mut Self {
        if other < *self {
            *self = other;
        }
        self
    }
    
    fn max_update(&mut self, other: Self) -> &mut Self {
        if other > *self {
            *self = other;
        }
        self
    }
}

impl<T: Ord> OrdUpdate for T {}
6 Likes