Introduce a way to construct Range from start + length

It still does, just via things like 200_u8 ..+ 100.

Unfortunately it works very poorly with general ranges in other places. "a".."b" works fine, and is even used in things like BTreeMap methods.

But if you had something like "a" ..+ 10, what does that mean? It clearly can't implement RangeBounds in std::ops - Rust, for example.

I think if you want the "counted" part, separating it out is useful. my_btree.range(start..).take(count), for example, works well.

How about just a function? Range::from_start_and_len(start, length) would be fine, no new type.

2 Likes

I'll ask again: what is the type signature of from_start_and_len? Are both T? If so, what of the signed types and negative length? Perhaps T and Into<T::Unsigned> to at least remove the negative-ness concern? Should there be a try_ variant that catches that and overflow? If it is "just" Add-gated, what about floating point types? What is f32::Unsigned if that path is chosen? Or perhaps this only makes sense for usize at all?

1 Like

I think it'd be reasonable for the length to be a usize and the start to be anything implementing Step, with a panicking version and an Option version.

That actually wouldn't work. To utilize Step, you'd have to pass by ownership internally, as the methods take Self by value. That's the only way you'd know what the end value is, but now you no longer have the start value to store.

Step has Clone as a supertrait.

True, but it at the least seems surprising that Clone would be called in such a situation.

Clone is called in all kinds of situations involving Step, so it's not really surprising

1 Like

Thank you for the enlightenment, I wasn't aware of the ACP process.

I understand this is an example and don’t want to get too deep into bikeshedding but do think it’s worth noting that Range::from_start_and_len is so absurdly verbose that I suspect nobody will ever use it, which somewhat defeats the point of something which is purely a convenience method.

If a ..+ b was implemented as syntax sugar for a .. (a + b) I think it would work exactly the way people would expect it to, and in those cases.

1 Like

It can't be exactly this, because people will expect a to be evaluated only once. It has to be something like { let $temp = a; $temp .. ($temp + b) } instead, and that runs us into the issues discussed up above with what happens when a isn't Copy.

Personally I'd be fine with a requirement for a to be Copy though. My reaction to the "what about "a".."z"?" example up above is "I really don't want people spelling that "a" ..+ 26" (not least because they probably should have written "a" ..+ 25, but +26 is the one that looks right if someone's not reading carefully).

1 Like

I think it'd be reasonable to have Range::from_start_and_len and Range::try_from_start_and_len as full functions you can write out (one panicking/wrapping and the other returning an Option), and then we can use ..+ or whatever other operator we want as a shorthand, like how ints have long method names for me to specify that I want .wrapping_add or .saturating_add when it's important to explicitly handle overflow, or I can just use + when I don't need to.

I sometimes write code where Range::try_from_start_and_len(start, len).unwrap_or(/* whatever */) would be important to show that the case where len doesn't fit has been thought about and handled appropriately, and the verbosity isn't that bad when you have LSP-backed completion.

Note that I might be able to agree about 'a'..+25 (a Range<char>), but what string comes "25th after" "a"? If one limits themselves to lowercase ASCII values, it would be "aaaaaaaaaaaaaaaaaaaaaaaaaa". But then "ab" is unreachable via ..+ syntax.

nitpick: "a".."z" would really be "a"..+24 which was not the proposal, because here we(?) would want Range::from_start_and_len("a", 26) which looks totally reasonable to me; whereas "a"..+x doesn't tell me if x is the total len or the offset from the start.

You’re right about the issues with it needing to be Copy and I was sloppy not including the placeholder variable, but I’m not sure I follow why it should be "a"..+25 rather than "a"..+26. If it’s meant to be short for "a"..("a" + 26) then aside from the fact that

  • this should probably be characters rather than strings (as one of the other responses points out), and
  • this doesn’t mean anything at all under the syntax sugar proposal, which was kinda the point of suggesting it (namely: keep it limited to the things which make unambiguous sense with the syntax),

I think it should be +26 and not +25 since .. is an exclusive range so the number is the length of the range and 'a' + 26 is the character after 'z' as it should be.

1 Like

There is a natural way to define steps on strings which is used frequently in theoretical computer science for algorithms/proofs which enumerate all strings: you simply change the ordering such that all shorter strings appear before all longer strings (equivalently, left pad the strings with “-1 characters” to the same length before comparison). Unfortunately, this would be mismatched with Rust’s Ord implementation for strings/vectors so cannot be used to implement Step for strings.

Either way, however, I’m hard pressed to imagine a case where what you want is a range of specific length over strings rather than characters since even under this definition, between "z" and "aa" you have an annoying to compute number of hundreds/thousands of strings including non-alphabetic and, more broadly, non-printable characters that you’d need to account for.

You're right, I forgot that .. is exclusive of the endpoint -- which means the original example of 'a' .. 'z' is probably incorrect, and I think that's actually an argument in favor of adding ..+.

1 Like