Type that captures all ranges (of `usize`) while retaining range syntax

I need to store values with any of the range expression types in the same data structure (with their generic type fixed as T = usize). However, in creating the values that go into said data structure, I want to retain the language's range expression syntax. I'm specifically thinking of:

  • Range<usize>
  • RangeFrom<usize>
  • RangeFull
  • RangeInclusive<usize>
  • RangeTo<usize>
  • RangeToInclusive<usize>

and their associated double dot syntax (e.g. 2..5 as a way of creating a Range<usize>).

I'm OK with using dynamic dispatch for "anything that implements RangeBounds<usize>" here, but that doesn't work since RangeBounds<usize> is not object safe.

I'm also OK with a new enum type with all of the above as variants, but I don't see how to retain the range syntax in that case.

I would guess this is also easily doable with macros, but my macro skills are so bad that I'd really appreciate some hints if that is the best way.

Any suggestions?

That's pretty much the only way to do it. The way to retain the range syntax is to write a From<X> for RangeWrapper impl for each of the range types X.
That would allow you to write things like RangeWrapper::from(10..=42).

2 Likes

This is one way you could implement this via macros which resolve custom range-like syntax into different enum variants, but the From implementation @jjpe suggested is probably more interpretable.

1 Like

You could also convert the RangeBounds into (Bound<usize>, Bound<usize>) which holds the same information and also implements RangeBounds

5 Likes

This is likely the best option, no need to write your own enum. The std already provides one!

But wait, how is RangeBounds not object safe?

It is not object safe because the contains() method is generic.

1 Like

Oh, right. That's too bad :frowning:

You can make your own dyn compatible trait and implement some subset of the functionality with a blanket implementation.

1 Like

May I dare ask why? I.e. what's your use case? Why is it not sufficient to accept anything that implements RangeBounds<usize> at the respective places?

This works if you eg have a function that takes a single range-like parameter. But if you need to store a multitude of range-like values, and be able to have them be heterogeneous in terms of their type (eg a Vec that contains a Range<usize> as well as a RangeFrom<usize>) then using the RangeBound trait won't work due to the dyn-unsafety.

1 Like

What if you define an object-safe wrapper?

trait DynRangeBounds<T> {
    fn start(&self) -> Bound<&T>;
    fn end(&self) -> Bound<&T>;
    fn contains(&self, x: &T) -> bool;
}

impl<U, T: RangeBounds<U>> DynRangeBounds<U> for T
where
    U: PartialOrd,
{
    fn start(&self) -> Bound<&U> {
        self.start_bound()
    }
    fn end(&self) -> Bound<&U> {
        self.end_bound()
    }
    fn contains(&self, x: &U) -> bool {
        self.contains(x)
    }
}

#[test]
pub fn test_rb() {
    let bounds: Vec<&dyn DynRangeBounds<_>> = vec![&(0..3), &(0..), &..];
    assert_eq!(
        bounds.iter().map(|x| x.contains(&4)).collect::<Vec<_>>(),
        vec![false, true, true]
    );
}
4 Likes

This is a fantastic solution in the spirit of what I had imagined when asking the question. Thanks!!

FWIW: I went for jjpe's solution wrapped in a macro for building vectors or arrays of said values. That was also nice, but the one presented here by undefined.behavior is spot on what I had imagined when asking the question, so I'll definitely move to it.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.