Eh. I do a lot of 2D image processing and my take on this is basically "usize indexing not a big deal here". The pix
crate uses i32
for pixel range coordinates because you really don't need images with either dimension larger than 2**31 - 1
but you do want to handle negative coordinates. Likewise, the range dimensions themselves are u32
because you do not want negative dimensions. Even u32
is not a perfect representation for dimensions because 0
and >= 2**31
are invalid.
This crate itself handles negative coordinates by clipping them into the image range, so they end up as unsigned anyway! The icing on the cake is that x as usize
is infallible[1] when x
is in [0, 2**31)
, without resorting to x.try_into().unwrap()
. And the fact that the cast really only needs to be done in a very limited number of places, e.g. when creating an iterator, really begs the question, why would you want to change the representation of a bitmap to use usize
dimensions when it is completely unnecessary?
"Lots of casting and boilerplate" is an exceptional situation, perhaps driven by other factors like choosing to write classic for-loops with integers over creating iterators. In practice, crates like pix
show that this statement is inaccurate at best.
Speaking of abstraction, referring to your claim that it's purely religious, I argue that you're mixing things up and choosing only what is convenient for you at the time. Let's revisit what you said earlier about a performance optimization with isize
indices:
But ... this is abstraction! You are literally hiding a cast in this situation saying it is fine to do so, and then you turn around and decry how awful it is to have "Lots of casting and boilerplate" as if abstraction does not exist at all. And yet, the Iterator<Item = usize>
is the abstraction that you are looking for to hide the casting!
Abstraction cannot be both good and bad at the same time. A more plausible explanation is that you are not taking full advantage of it. Saying "I don't want to use the iterator abstraction because I prefer a for-loop that increments integers" is a problem you are going to have to deal with on your own. The iterator will help you hide the details that you dislike, but it's up to you to take advantage of that property of abstraction.
I would go as far to suggest that your 2D image processing experience is probably not representative of the common situation. The situation where a user will just pull in a crate for image processing, and it will do the right thing without having to worry about details that don't matter for processing images (like that pesky usize
index).
Case in point, my little game that uses pix
doesn't have a single coordinate casted to usize
for indexing, because doing so at this level of abstraction is unnecessary. In full disclosure, I do go the other way though, casting a dimension (u32
) into a coordinate (i32
). But that's for creating an iterator. Far from "Lots of casting and boilerplate", FWIW.
I cannot imagine a world where isize
index is much more useful than usize
for image processing. If anything, it will break even. Net zero either way. You pretty much have to cast between coordinates and dimensions at some point. Even if you choose to use signed dimensions to avoid a cast (don't do this lol) you still have to protect against <= 0
dimensions when creating the image and when converting from a coordinate.
The ultimate type for an image dimension is probably something like NonZeroU31
, but it comes with its own thorns. First because this type does not exist in the standard library and second because you still have to use try_into()
or unwrap an Option
to safely get one from an arbitrary primitive type like u32
or i32
.
That said, I also do a lot of audio processing! We have buffers (e.g. VecDeque<Sample>
or RingBuffer<Sample>
and the like) but very little need for indexing them with isize
. Even with a ring buffer you don't want to cross the boundary (i.e. index 0) while going backwards for echo effects or whatever. Yeah, I just don't buy the idea that isize
is somehow objectively superior for indexing in these moderately common cases.
This is true on 32-bit and 64-bit systems. The
unwrap()
can panic on 16-bit systems with very large images, so you're already doing error handling incorrectly regardless. ↩︎