Implementing Range operator/syntax (`x..y`) for my own type

Since std::ops::Range isn't Copy, and I need a Copyable Range, I'm essentially reimplementing the bits of std::ops::Range that I need. One bit of that is the bounded range syntax (x..y).

How do I implement the syntax/operator for my own type?

Thank you!

You can't. This operator always produces the standard Range type.

You can allow convenient conversion from a standard Range easily though, by implementing the std::convert::From trait for your own type.

3 Likes

This isn't a direct answer to your question, but implicit Copy was disallowed on Range because a range is often used as an iterator, leading to confusion as to which copy of the iterator you are advancing.

One workaround is to explicitly clone() the Range instead.

You can read various discussions related to this on GitHub:

Yeah, I have read and sort-of understood the rationale.

However, in my case, I'm using Range to indicate the location of a token in the source code. I'm aliasing it as Span. Therefore, the confusion with an iterator is not an issue for me.

Is there an RFC to accommodate this, or is it simply not desired?

I don't think it's reasonable. An operator's output type is conceptually determined by the type(s) of its input(s). i32..i32 will always be a Range<i32>, and this is the correct behavior.

(It would technically be possible to allow this, but it would screw up type inference big time, because every range construction would then have to be annotated with an explicit type when such an overriding range type exists in any dependency of one's code.)

There are some very preliminary discussions about adding something like a copyable Span type to std and changing the .. operator to produce the new type. It’s not even a pre-RFC yet, though, so it’s unlikely to happen soon.

2 Likes

When I used "ranges" in my code, I usually refrained from using anything from std::ops in that matter and just implemented my own struct.

There is std::ops::RangeBounds though, which, I guess, could be implemented for such structs.

It should also be possible to make functions generic over T: RangeBounds (instead of using one of the range types in std::ops or your own struct). But working with RangeBounds is a bit tedious as you have to support all three possible Bounds in your function.

1 Like

I've done this a bunch of times when implementing parsers for text formats or programming languages.

The easiest thing to do is just make a Span struct and #[derive(Copy)] on it.

You almost always construct spans programmatically, so the ergonomic benefits of using start..end instead of Span::new(start, end) aren't really there.

2 Likes

I've been in exactly your situation. While the source code isn't public at this time, what I did is to create a new Span type, slap a bunch of derives on it (Clone, Copy, comparison traits etc), implement conversion traits between it and Range<usize>, as well as convenience construction methods on Span.

The syntax point is unfortunate but like @Michael-F-Bryan says, it's not really a problem in practice.

1 Like

Having .. be context-sensitive would break lots of reasonable code -- even for i in 0..10 would fail because there's nothing there to contextualize which type it should use, just like how for i in Default::default() doesn't compile.

So a..b might change to be a different specific type -- one that's Copy, for example, as mentioned above -- but I suspect it will always be a specific concrete library type.

2 Likes

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.