Add `.add_isize()` to the standard library

Array indices are usize (and they need to be, because an array could be bigger than what an isize can represent).

It’s not uncommon to have an offset as an isize that you want to add to your current array index (imagine a CPU emulator that wants to do relative jumps). Doing this correctly is not as trivial as it first seems and the standard library doesn’t help you with this. I thought about writing an RFC for adding methods like 8usize.add_isize(-4isize) to libcore, but I wanted to hear your opinion first:

trait AddExt {
    type Output;

    fn add_isize(self, rhs: isize) -> Self::Output;
}

impl AddExt for usize {
    type Output = usize;

    fn add_isize(self, rhs: isize) -> usize {
        if rhs < 0 {
            if rhs == std::isize::MIN {
                self - ((std::isize::MAX as usize) + 1)
            } else {
                self - ((-rhs) as usize)
            }
        } else {
            self + (rhs as usize)
        }
    }
}

Also: Why do the operations 255u8 + 1u8 and -(-128i8) panic in debug mode, but 255u8 as i8 not? There should at least be an .to_u8() that behaves the same way.

2 Likes

I would use 2's complement negation for this to avoid the branch. (although the optimizer might figure that out anyway)

let rhs = 1 + !(rhs as usize);
self - rhs

edit: probably even better

self - (rhs.wrapping_neg() as usize)
1 Like

I absolutely see that working with offsets for array indices can be a little bit painful. But imo that is good, because I don‘t think that there is a legitimate usecase for doing that.

If you can use iterators, do that. If you are super low level like the emulator from your example, you dont want to do checked indexing anyway. What you are doing in that case is pointer arithmetic, and imo it would be better to do that with pointers. And pointers have an offset method that accept isize.

TryInto and TryFrom (currently experimental) provide checked conversions between integer types.

What you are doing in that case is pointer arithmetic, and imo it would be better to do that with pointers. And pointers have an offset method that accept isize.

I don't really like this notion. It's already easy to do using unsafe, so let's not add a safe API equivalent?

This is definitely something I have wished for in the past. Just a few days ago I wanted some code to me generic over array iteration direction (and with logic more complicated than easily / cleanly fits in an iterator), so I wrote something along these lines:

trait Direction {
  fn modify(x: &mut usize);
}
struct Forward;
struct Backward;
impl Direction for Forward { fn modify(x: &mut usize) { *x += 1 } }
impl Direction for Backward { fn modify(x: &mut usize) { *x -= 1 } }
fn complicated_scan<T, D: Direction>(array: &[T], mut i: usize) {
  while condition(i) {
    D::modify(i);
  }
}

Even taking the direction as a 1 or -1isize and casting the index back and forth would have been easier.

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