Pre-pre-RFC: Extrema traits

Hello all. I hope you're having a good day, and if not, that it gets better.

The state of getting the greatest and least values for some type is a bit limited at the moment (associated MAX/MIN constants on primitive types and whatnot). I would like to write an RFC to propose introducing traits with associated constants for these values to the standard library, along with a few new features to the Range types that are enabled by this, but I have some questions first.

I've tried to find a previous RFC for this feature and haven't found any. Does anyone know if one exists, or if/where there has been any prior discussion about this feature?

There are a number of forms of extrema for partially ordered types. This is relevant to consider because floating-point types aren't totally ordered, and it would be a real shame if those traits couldn't be implemented for f32/f64. Given this:

Should the proposal include a trait for getting the maxima or minima of a type? Minima and maxima are the values of a type for which there are no other values that are less or greater (respectively) than them. Partially ordered types may have multiple of each.

In order for a greatest/least value trait to be implemented for floating-point types, it'd have to be restricted to the set of finite values of those floating-point types. Should there additionally be a trait that doesn't have this restriction? What about a trait to identify values that are part of the ordering between the "finite greatest" and "finite least" values?

After adding these traits, some of the aforementioned associated constants would be redundant. It would be nice to suggest a course of action to take with those constants for an edition after the inclusion of this feature. Should they be kept indefinitely, replaced with corresponding traits being added to the prelude, or just removed with no corresponding prelude additions?

Perhaps you're interested in num_traits::bounds::Bounded - Rust?

The answers for traits in core is "generally no", as was recently discussed in more detail in Missing traits for checked arithmetic - #2 by scottmcm

2 Likes

Wow, thank you for the prompt reply.

The answers for traits in core is "generally no"

So, about that. Do you think there's a possibility of convincing the team to make an exception in this case? A few possible arguments in favor of doing so, in no particular order:

  • num_traits is described as "Numeric traits for generic mathematics", and Bounded is described as a trait of "Numbers". Extrema are more general than that. For instance, many enums that implement Ord (especially fieldless enums) have logical least finite and greatest finite values, but aren't necessarily numeric. An example would be log::Level. Admittedly this is an argument more in favor ot having a de-facto standard bounds trait outside of num_traits than necessarily having one in core.
  • Bounded is for types that have both a minimum and a maximum. There are types that have one but not the other. For instance, a sequence of Ord values that itself is Ord based on a lexicographical comparison of its elements (e.g. strings) has a minimum, but not a maximum.
  • Bounded::min_value and Bounded::max_value are not const. AFAIK they couldn't be at the time the split happened, which is fair enough, but perhaps more const-friendly versions of those traits are in order?
  • Some From implementations for core::ops::RangeInclusive become possible, for conversion from ranges of values whose type implements the appropriate trait to provide the "missing" bound.
  • Similarly, IntoIterator can be implemented for core::ops::{RangeTo, RangeToInclusive, RangeFull} given the right trait implementations, which can be a minor ergonomics improvement.
  • There is a discoverability argument to make, but that kind of applies to everything that could potentially get added to core/std.

technically, the extrema of floating point types are infinity and negative infinity (not any finite values), because they are valid floating point values that are greater/less than all other floating point values. If you ignore the NaNs and the sign of zeros, floating point numbers are totally ordered, and that includes the infinities.

3 Likes

For the fully-general one? Almost certainly not. Yes, String has a minimum of String::new(), but it's unclear to me that anything in the standard library would ever care about that. Even outside, what useful generic methods could take a String as their impl BoundedBelow? (Obligatory link to Code Smell: Concrete Abstraction)

Many of your arguments here are not arguments for having it in core, but are perhaps arguments for a PR to num_traits. Indeed, some are arguments for why it's not in core already -- being able to make breaking changes (perhaps to be more const-convenient, perhaps to split the upper and lower bounds, etc) is a great reason for these things to be in a crate.

For the const one specifically, there's work ongoing to have impl const Trait or similar so that we don't need to add ConstDefault and ConstAdd and etc, which will solve the general problem.

Now, maybe the team would be interested in some of the range-related things you mention. But you'd want to start with justifying those in isolation, then see where the implementation would go. Perhaps it would end up somewhere related to Step in std::iter - Rust, as that's what's currently used for the Iterator implemention for ranges.

1 Like

Caveat: Make ptr range iterable by dtolnay · Pull Request #91000 · rust-lang/rust · GitHub

1 Like

Very interesting link; thank you.

Also a good example that ..(100 as *const i32) not being iterable is probably best, even though (sized) pointers do have a minimum of null()...

To all those who celebrated Thanksgiving: I hope it was good!

True, I didn't communicate my thoughts well earlier. To me, it seems overly specific to define a pair of those traits in a way that excludes some of the special values of floats just to be able to return the infinities.

Perhaps I wasn't clear here. The example of strings as a type with a least value but no greatest value was an example chosen in hopes of being easy to understand, and further was in reference to strings in general, not specifically the owned String type. It was not chosen because it's the most practical. :grinning_face_with_smiling_eyes: A more practical example would be a type with multiple maxima (e.g. some enums that end with a variant with a field does not participate in the ordering relationship at all).

As for subjectively useful operations that require information of only one of the least or greatest values of a type, here are a few in no particular order:

  • The above range-bounding enhancements.
  • Generic percentage-of-greatest-value calculations.
  • Simplifying implementations of overflow/underflow checks on some newtypes.
  • For types (thinking of small enums in particular) that are Ord + Step, we can get a usize integer id for each value of that type. These ids start at 0 for the lowest value and are continuous. This has a few applications that would be worse to do with something like num_traits::AsPrimitve<usize>, which does not have those guarantees:
    • Vec-based flat maps with such types as key types.
    • Sets of such types that are implemented as bit sets.
    • This index itself can be a more compact representation that's worth using when every byte matters, but honestly the types that would benefit the most from this optimization often aren't and shouldn't be Ord IME.
  • I'm adding this one fully acknowledging that it's probably too general and that I'm mostly adding it as a (somewhat contrived) example of where the least value of strings could be useful, but iterating over (a well-defined subset of) a type's full range in ascending/descending order given a monotonic user-defined stepper function. If one of the bounds is missing, this is an infinite iterator. There might be a better example for strings, but I can't think of one at the moment.

Oh, nice to know. That's definitely a feature to look forward to and plan for.

Fair. I think extrema traits can help with additional problems, but there's definitely stuff to propose wrt. additions to the core range structs to make them more ergonomic. I'll look for prior work on this and if I can't find anything active, I'll make a new thread to iterate on some ideas.

Absolutely. Despite pointers having least and greatest values in practice, I can't think of any sane uses for those values. This would be a case where I'd favor not implementing the trait.


One thing I didn't address was the "concrete abstractions as a code smell" blog post. It's an interesting perspective, though I disagree with some of the post's points. In the interest of focus, I'll refrain from discussing my reasons why in this reply (heck, it's long enough already), but going forward I'll consider that this opinion may be shared by members of the various Rust teams, and thus influence how proposals are evaluated.

FWIW, this is what enum_map::Enum provides.