# Add a \`within\` method to \`PartialOrd\`

**URL:** <https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654>\
**Category:** libs\
**Created:** [October 6, 2023, 11:34am UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654 "2023-10-06T11:34:02Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![matthieum](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matthieum/32/4023_2.png) [@matthieum](https://internals.rust-lang.org/u/matthieum)\
**Post date:** [October 6, 2023, 11:34am UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/1 "2023-10-06T11:34:02Z")

</div>

The `RangeBounds` trait offers a `contains` method which... I am not much a fan of.

I typically favor not getting hung up on syntax, but I just find it hard to read:

```rust
!(0.0..=1.0).contains(&x)

```

In descending order of importance:

- Yoda notation: I need to reach the end of the expression to learn which variable is being concerned (`x`) when that's the most important part, really.
- Blending: that `!` next to `(` really blends in, making it harder to notice.
- Noise: an extra set of parentheses (around the range) and an extra `&`, just boilerplate that needs to be skipped.

I was glad to learn with the 1.73 release that [I am not the only one](https://www.reddit.com/r/rust/comments/170oy2v/comment/k3mwt67/?utm_source=share&utm_medium=web2x&context=3) who is not a fan. Though I had no solution to offer.

/u/kibwen did, however, [offer a fairly compelling alternative](https://www.reddit.com/r/rust/comments/170oy2v/comment/k3p78j3/?utm_source=share&utm_medium=web2x&context=3):

```rust
!x.within(0.0..=1.0)

```

This actually solves all the issues I had above, which I honestly didn't think was possible:

- `x` is first, immediately denoting which variable we're interested in.
- `!` stands out quite a bit more.
- No extra set of parentheses, no extra `&`.

And here I am, thinking that it would be a worthwhile addition to the `PartialOrd` trait:

```rust
fn within<R>(&self, range: R) -> bool
where
    // Somehow reproduce the bounds of RangeBounds::contains
    R: RangeBounds<Rhs>,
    Rhs: PartialOrd<Self>,
{
    range.contains(self)
}

```

Pure syntactic sugar, really, yet oh so sweet.

* * *

Comparison of various syntaxes, side to side:

```rust
if scale < 0.0 || scale > 1.0

if scale < 0.0 || 1.0 < scale

if !(0.0 <= scale && scale <= 1.0)

if !RangeBounds::contains(0.0..=1.0, &scale)

if !(0.0..=1.0).contains(&scale)

if !scale.within(0.0..=1.0)

```

---

<div class="post-metadata">

**Author:** ![mathstuf](https://avatars.discourse-cdn.com/v4/letter/m/958977/32.png) [@mathstuf](https://internals.rust-lang.org/u/mathstuf)\
**Post date:** [October 6, 2023, 12:16pm UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/2 "2023-10-06T12:16:39Z")

</div>

I think some careful thought should be put on `PartialOrd` here. If it were `Ord`, I'd have no reservations, but there are questions that `PartialOrd` raises:

- With floating point, what is the behavior around NaN?
- If one has a `type Point2d(T, T); impl<T: PartialOrd> PartialOrd for Point2d<T>`, the behavior of `within` depends on which coordinate is compared first.

Then again, I suppose that `contains` already has these problems…so maybe my concerns are already "handled" by existing behavior.

---

<div class="post-metadata">

**Author:** ![blume](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/blume/32/10860_2.png) [@blume](https://internals.rust-lang.org/u/blume)\
**Post date:** [October 6, 2023, 12:22pm UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/3 "2023-10-06T12:22:40Z")

</div>

I support this idea. Ranges as arguments look a bit weird at first, but are necessary to ensure all possibilities by comparison operators are also possible. Apart from that, this method looks very similar to [`Ord::clamp`](https://doc.rust-lang.org/std/cmp/trait.Ord.html#method.clamp) - still, it should not be limited to types that implement `Ord` imo as all floats, on which these operation seem to have the most usefulness, could not benefit from it.

---

<div class="post-metadata">

**Author:** ![matthieum](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matthieum/32/4023_2.png) [@matthieum](https://internals.rust-lang.org/u/matthieum)\
**Post date:** [October 6, 2023, 2:34pm UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/4 "2023-10-06T14:34:13Z")

</div>

> [@mathstuf](#):
>
> I think some careful thought should be put on `PartialOrd` here.

I went with `PartialOrd` because `contains` is already predicated on `PartialOrd`, so this decision has already been made in a way.

Do note that the motivating example uses floats, which don't implement `Ord`, and also note that `Ord` does not allow mixed-comparisons, such as checking whether a `T` is in a range of `U`.

---

<div class="post-metadata">

**Author:** ![pitaj](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/pitaj/32/11262_2.png) [@pitaj](https://internals.rust-lang.org/u/pitaj)\
**Post date:** [October 6, 2023, 3:55pm UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/5 "2023-10-06T15:55:20Z")

</div>

You can always do `!RangeBounds::contains(0.0..=1.0, x)` instead

---

<div class="post-metadata">

**Author:** ![newpavlov](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/newpavlov/32/3290_2.png) [@newpavlov](https://internals.rust-lang.org/u/newpavlov)\
**Post date:** [October 6, 2023, 4:18pm UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/6 "2023-10-06T16:18:22Z")

</div>

I like the idea. The `contains` Clippy lint is relatively annoying and I would be happy to replace it with such method.

A bit of bikeshedding: I think `is_within` could be a better name, since it clearly indicates that return result is `bool`.

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [October 6, 2023, 6:33pm UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/7 "2023-10-06T18:33:30Z")

</div>

Beware that `PartialOrd` is pervasive, so this will add the method to all kinds of things, potentially being (_de jure_ "minor") breaking for method resolution. (This was a problem with `Ord::clamp`, for example.)

So a particularly-strong proposal for this will look at other uses of `within` in the ecosystem to see if this conflict would be bad. Though of course a check-only crater run can also assess impact of a PR.

---

<div class="post-metadata">

**Author:** ![Vorpal](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vorpal](https://internals.rust-lang.org/u/Vorpal)\
**Post date:** [October 6, 2023, 10:14pm UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/8 "2023-10-06T22:14:22Z")

</div>

What exactly is the semantics for contains/within wrt floats? I would expect that NaN is never contained in anything and +/-inf is only contained if they are the upper/lower boundary respectively. But I can't find any documentation on this.

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [October 6, 2023, 10:39pm UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/9 "2023-10-06T22:39:06Z")

</div>

> [@Vorpal](#):
>
> What exactly is the semantics for contains/within wrt floats?

[https://doc.rust-lang.org/std/ops/struct.Range.html#method.contains](https://doc.rust-lang.org/std/ops/struct.Range.html#method.contains) has a bunch of examples to show how _it_ behaves, at least.

`clamp` wants `Ord`, because it needs to know higher _or_ lower to know which end to clamp to, but `contains` can just return false when it's not relatively ordered to both ends.

---

<div class="post-metadata">

**Author:** ![Vorpal](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vorpal](https://internals.rust-lang.org/u/Vorpal)\
**Post date:** [October 6, 2023, 10:41pm UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/10 "2023-10-06T22:41:48Z")

</div>

My bad, missed the examples. Still, a couple of examples is not _really_ a proper specification of behaviour. And inf isn't mentioned at all.

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [October 6, 2023, 11:14pm UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/11 "2023-10-06T23:14:56Z")

</div>

> [@Vorpal](#):
>
> And inf isn't mentioned at all.

Inf isn't a problem, since the infinities are still properly ordered against everything that's not a NAN.

---

<div class="post-metadata">

**Author:** ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)\
**Post date:** [October 6, 2023, 11:38pm UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/12 "2023-10-06T23:38:18Z")

</div>

Isn't the behavior of NaN here fixed by the principle that any comparison involving NaN is false? Therefore, NaN is not within any range, and a range where either end is NaN contains nothing. Which is, I think, what that documentation is saying is already the case.

---

<div class="post-metadata">

**Author:** ![DanB](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/danb/32/11394_2.png) [@DanB](https://internals.rust-lang.org/u/DanB)\
**Post date:** [October 6, 2023, 11:56pm UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/13 "2023-10-06T23:56:22Z")

</div>

There's a few types representing [mathematical sets](https://en.wikipedia.org/wiki/Set-builder_notation) that already have a contains(&T) function:

```rust
{1,7,3} => HashSet.contains
{1,3,7} => BTreeSet.contains

{x| -∞ < x < ∞ } => .. => RangeFull.contains
{x| -∞ < x < 7 } => ..7 => RangeTo.contains
{x| -∞ < x <= 7 } => ..=7 => RangeToInclusive.contains
{x| 1 <= x <= 7 } => 1..=7 => RangeInclusive.contains
{x| 1 <= x < 7 } => 1..7 => Range.contains
{x| 1 <= x < ∞ } => 1.. => RangeFrom.contains

```

And a few sets that can't be represented so easily:

```rust
{x| 1 < x < ∞ } => (Bound::Excluded(1), Bound::Unbounded)
{x| 1 < x <= 7 } => (Bound::Excluded(1), Bound::Inclusive(7))
{x| 1 < x < 7 } => (Bound::Excluded(1), Bound::Exclusive(7))  

{x| x % 2 == 0} => PredicateSet (doesn't exist)

```

If theses types could all implement a common `Set<T>` trait, such as:

```rust
pub trait Set<T>
{
    /// query whether the set contains a value
    fn contains(&self, val: &T) -> bool;
}

```

Then an opposite blanket trait `ContainedIn<T>` (or trait `In<T>` with fn `in(...)`) could be implemented for the reverse:

```rust
pub trait ContainedIn<T> {
    fn contained_in(&self, set: &impl Set<T>) -> bool;
}

impl <T> ContainedIn<T> for T {
    fn contained_in(&self, set: &impl Set<T>) -> bool {
        set.contains(self)
    }
}

let set1 = HashSet::from([1,4,7,8]);
let set2 = (15..30); //or RangeSet::from(15..30) or RangeSet::from((Bound::Excluded(4), Bound::Excluded(7));
let set3 = PredicateSet::new(|x| x % 2 == 0);
// implement union, intersection, difference, symmetric difference
// all of which also implement Set<T>. The cartesian product could implement Set<(T1,T2)>
let combined_set = (set1 | set2) & set3;

if 7.contained_in(&combined_set) {
    ...
}

if combined_set.contains(&7) {
    ...
}

```

The name "within" wouldn't make much sense for a set of enumerated values eg. `{1, 4, 7}` or if you had a set defined by a predicate, because the value might be within the range of min and max values, but not contained in the set. Using "in", or "contained\_in" (for the reverse of "contains") might make more sense.

Aside: Sets can also define relationships (such as the binary relationships \<, \<=, \>, \>=) between 2 or more ordered values. The GreaterThanSet could contain all pairs (a, b) where a \> b, eg. (2,1), (20,7), (123,94), but not (4, 7).

If there was an `In<T>` trait defined, then perhaps this would indicate that the "in" keyword could be reused:

```rust
pub trait In<T> {
    fn in(&self, set: &impl Set<T>) -> bool;
}

let hashset = HashSet::from([1,4,6]);
if 2 in hashset { ... };
if 7 in 4..9 { ... };

```

instead of:

```rust
if 2.in(hashset) { ... };
if 7.in(4..9) { ... }
```

---

<div class="post-metadata">

**Author:** ![mathstuf](https://avatars.discourse-cdn.com/v4/letter/m/958977/32.png) [@mathstuf](https://internals.rust-lang.org/u/mathstuf)\
**Post date:** [October 7, 2023, 12:38am UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/14 "2023-10-07T00:38:33Z")

</div>

> [@zackw](#):
>
> Isn't the behavior of NaN here fixed by the principle that any comparison involving NaN is false? Therefore, NaN is not within any range, and a range where either end is NaN contains nothing.

It depends on whether you implement it as `low <= x && x < high` or `!(x < low || high <= x)`. `NaN.within(low..high)` will be `false` and `true` for these two implementations.

---

<div class="post-metadata">

**Author:** ![matthieum](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matthieum/32/4023_2.png) [@matthieum](https://internals.rust-lang.org/u/matthieum)\
**Post date:** [October 7, 2023, 11:12am UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/15 "2023-10-07T11:12:50Z")

</div>

> [@DanB](#):
>
> The name "within" wouldn't make much sense for a set of enumerated values eg. `{1, 4, 7}` or if you had a set defined by a predicate, because the value might be within the range of min and max values, but not contained in the set. Using "in", or "contained\_in" (for the reverse of "contains") might make more sense.

I'm open to different names. `within` was an off-the-cuff suggestion, though I do disagree that it doesn't suit `{1, 4, 7}`, there's nothing in `within` which suggests it only considers bounds.

I do find the consideration of sets (or collections, really) interesting, and indeed it may be worth extending beyond `PartialOrd` and ranges...

> [@DanB](#):
>
> If there was an `In<T>` trait defined, then perhaps this would indicate that the "in" keyword could be reused

I find keyword reuse problematic, as in making approaching and learning a language more difficult. It hampers discoverability, by making it more difficult for a newcomer to immediately be guided to the _one_ concept expressed by the keyword.

* * *

I'm not that invested in names, in general. I'd much rather focus on _semantics_ first, _then_ we can figure out what's the most appropriate name for the agreed upon semantics.

Discussing names first is just a waste of time, if the semantics change, we may need to discuss _again_.

---

<div class="post-metadata">

**Author:** ![matthieum](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matthieum/32/4023_2.png) [@matthieum](https://internals.rust-lang.org/u/matthieum)\
**Post date:** [October 7, 2023, 11:13am UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/16 "2023-10-07T11:13:56Z")

</div>

> [@mathstuf](#):
>
> It depends on whether you implement it as `low <= x && x < high` or `!(x < low || high <= x)`. `NaN.within(low..high)` will be `false` and `true` for these two implementations.

The suggestion here is to implement it as `range.contains(&x)` hence the behavior of `NaN` is fixed by the implementation of `contains`, which is itself fixed for backwards compatibility reasons by now.

---

<div class="post-metadata">

**Author:** ![matthieum](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matthieum/32/4023_2.png) [@matthieum](https://internals.rust-lang.org/u/matthieum)\
**Post date:** [October 7, 2023, 11:21am UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/17 "2023-10-07T11:21:30Z")

</div>

Following @DanB's suggestion, I had a look at the various `contains` methods in `std`:

- `RangeBounds`: heterogeneous single element.
- Collections: homogeneous single element.
- `str`: heterogeneous pattern.

It seems it would be possible to implement a more generic `within` method, though I'm not clear how useful it would be.

I'm curious what people think, and why:

_Poll ([view on site](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/17))_

---

<div class="post-metadata">

**Author:** ![bert](https://avatars.discourse-cdn.com/v4/letter/b/9e8a1a/32.png) [@bert](https://internals.rust-lang.org/u/bert)\
**Post date:** [October 7, 2023, 3:05pm UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/18 "2023-10-07T15:05:12Z")

</div>

I think `is_in` would be the best name for this. Short and sweet, makes clear the result is a `bool`, and is a general inverse of `contains` not specific to ranges. There's also some precedent, like Python’s `in` keyword as a predicate.

---

<div class="post-metadata">

**Author:** ![newpavlov](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/newpavlov/32/3290_2.png) [@newpavlov](https://internals.rust-lang.org/u/newpavlov)\
**Post date:** [October 7, 2023, 3:46pm UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/19 "2023-10-07T15:46:21Z")

</div>

Adding support for types like `HashSet` probably would require addition of a new trait, since `HashSet::contains` is an inherent method and the `RangeBounds` trait does not look like a good fit for these types.

As for the name, `is_in` sounds like a good and concise option.

---

<div class="post-metadata">

**Author:** ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)\
**Post date:** [October 8, 2023, 1:47am UTC](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654/20 "2023-10-08T01:47:34Z")

</div>

> [@mathstuf](#):
>
> It depends on whether you implement it as `low <= x && x < high` or `!(x < low || high <= x)`

I believe what I'm saying is that the latter is wrong.

[Next page](https://internals.rust-lang.org/t/add-a-within-method-to-partialord/19654.md?page=2)
