Implementing an iterator over a raw memory region

I have a memory region which I need to read some values from. These values are not necessarily contiguous in memory but are always some stride bytes apart. I need to take these values and push them into some Vec<T>.

I was doing it in a straightforward way like this and it worked fine:

// cursor is a *const u8
// values is a Vec<T>
for _ in 0..count {
    let ptr = cursor as *const T;
    values.push(*ptr);
    cursor = cursor.add(stride);
}

As an exercise I wanted to add some syntactic sugar and implement an iterator so I could do something like this instead:

values.extend(strided_iter(start, count, stride));

So I went ahead and wrote this:

pub struct StridedRange<T: Sized> {
    cursor: *const u8,
    end: *const u8,
    stride: usize,

    _marker: PhantomData<T>,
}

impl<T> Iterator for StridedRange<T> {
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.cursor < self.end {
            let value = unsafe { *(self.cursor as *const Self::Item) };
            self.cursor = unsafe { self.cursor.add(self.stride) };
            Some(value)
        } else {
            None
        }
    }
}

pub unsafe fn strided_iter<T>(start: *const u8, count: usize, stride: usize) -> StridedRange<T> {
    StridedRange {
        cursor: start,
        end: start.add(count * stride),
        stride,
        _marker: PhantomData,
    }
}

(I know it won't work for .extend() yet because it needs an IntoIterator but that's a different problem)

Now if I try to use it for any T that's not Copy it won't compile with a message cannot move out of a raw pointer. I don't understand why it happens because essentially I'm doing the same thing as my original code. Of course this is unsafe, but I thought that by using unsafe I take responsibility for memory management and the compiler shouldn't care.

I would really appreciate if someone could explain why this is wrong and how one would go about implementing an iterator like this (and an IntoIterator - in an idiomatic way).

Are you sure you had a generic T before and not a specific T that was Copy?

fn f<T>(mut values: Vec<T>, cursor: *const u8) {
    let ptr = cursor as *const T;
    // error[E0507]: cannot move out of `*ptr` which is behind a raw pointer
    unsafe { values.push(*ptr) };
}

Playground.

That said, you may be looking for ptr.read(). If you're not careful, you'll end up with aliasing or double-drops or whatnot.

2 Likes

Are you sure you had a generic T before and not a specific T that was Copy ?

You are right! The type in question was a Vec3 which was Copy - for some reason I assumed it wasn't.

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.