Is std::ops::Index just a historical artefact?

Disclaimer: I know that nothing actionable can come of this, but i’d like to understand some design decisions.

My impression is that direct indexing via vector[index] is seldom what I want. If it’s not guaranteed that the index is in bounds, get is better. If I’m sure that the index is in bounds, I tend to use get with expect to communicate that and get better errors if I was wrong. If I’m totally sure, I use get_unchecked to circumvent the bound check.

When I look at other peoples code I have a similar impression. The only places I see uses are: Taking slices (imo rare enough to do that with a method) and in direct translations from C code (which leads often to slower code in comparison to a solution with iterators)

It seems peculiar to me to spend syntactic sugar on that.

So, do we have the index operator just because we inherited it from C/C++ or is my impression wrong?

Pretty much, yeah. Keeping things similar to C/C++ is important though considering Rust’s aim is to replace C/C++ and win over all the C/C++ programmers.

1 Like

Imo it does more harm than good to that group. The implicit message is: use this like you would do in C, which leads to suboptimal and unidiomatic code and frustration in the learning process.

I've never done this. If I am so certain that the index should be in bounds, it being out of bounds proves that I've erred in writing the code or reasoning about it. When I'm that mistaken about my code, I'm also unable to guess a priori what the root cause of my mistake might be, or which information would be most useful for me to identify the root cause. Therefore, whatever extra information I might attach to the panic is likely useless for actually tracking down the bug (especially considering that the default panic message for OOB indexing already includes the index and slice length, which I might forget to include in my custom panic message). Moreover, even if there was a little bit of extra information to be gained, it likely isn't worth the much more verbose code.

13 Likes

I would also contest this. get_unchecked isn't a stronger claim about how sure you are. It's an escape hatch for when you can and need to gain extra performance at the expense of risking safety. You do need to be sure that the index is in bounds to be able to (responsibly) use it, but it's never more idiomatic to use it instead of safe indexing. It's just the idiomatic fallback for when safe indexing is insufficient. So I can't see how it counts against how frequently safe indexing should be used -- safe indexing should be preferred over get_unchecked whenever possible!

12 Likes

There is nothing unidiomatic about using indexing, and using .get(...).expect(...) for every single index operation would be too onerous for my own personal taste. Its value would also be questionable. Sure, I use expect when I can because it’s nice to give a friendly message, but “use it everywhere” is too extreme IMO.

The code I write uses lots of indexing and slicing, so I’d say it’s definitely not a historical artifact.

18 Likes

I agree with @burntsushi .

I’d also like in std a set of unsafe functions similar to get_unchecked/get_unchecked_mut that contain a debug_assert!() that gives a out of bounds error only in debug mode.

4 Likes

I’d also like in std a set of unsafe functions similar to get_unchecked/get_unchecked_mut that contain a debug_assert!() that gives a out of bounds error only in debug mode.

This, and I recommend we call them get_unchecked and get_unchecked_mut.

:duck:

4 Likes

Except that there’s code (at least in the standard library) that sets a Vec’s capacity, leaves the Vec’s size set to 0, and uses get_unchecked_mut to put stuff in the vector.

Adding the debug_asserts changes a bit the semantics of function, that’s why I have suggested new functions.

I have, on occasion, used a Vector as a standin for a HashMap when I want lookups to be faster because I need to do a lot of lookups because I need to jump my edits back and forth between two or more elements of the collection during some operation. You can’t do that with iterators. You could technically do that with get_unchecked, but I’m comforted by the safety. Safe indexing is exactly what I want.

I use unchecked-index now for debug-checked unchecked indexing. It’s something that’s recurring a lot for me, so I had to factor out that common code.

That crate has two features, the regular indexing syntax wrapper, but I think the regular free functions get_unchecked etc are the most versatile ones. They are debug-checked. And yes, it would be great to find a function name that captures the whole essence of that, but I haven’t found it yet.

Making debug checked unchecked indexing available both has the downside of enabling more unsafe code unfortunately, but I prefer to focus on the positive side: it allows using unchecked indexing without leaving it completely unguarded; instead it’s checked by yours and others’ test suites exercising the code with debug assertions on.

3 Likes

I disagree, I don’t think expect normally gives you a better panic message: The index out of bounds panic message includes the used index value and the length of the vector or slice, which is a pretty good message for debugging and requires some extra boilerplate to reproduce in the .get() version.

1 Like

From the unchecked-index docs page:

Debug checking does not make your code safe, but it helps finding bugs in unsafe code.

In future I hope to see more (easy to use) things to help Rust programmers find bugs in unsafe code :slight_smile: (and to prove in simple enough ways that small parts of unsafe code are correct).

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.