Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between [T; N] and U if U is always set to [T; N]?

I was trying to implement IntoIterator for [T; N]. I wrote a completely safe version using Default and swap (PlayPen). Then I ported it to use uninitialized, ptr::copy, Drop and forget (PlayPen). My Iterator struct looks like this:

struct IntoIter<T> {
    inner: Option<[T; N]>,
    i: usize,
}
impl<T> Iterator for IntoIter<T> { ... }

Since I did not want to create an Iterator struct per value of N, I changed the struct to

struct IntoIter<U> {
    inner: Option<U>,
    i: usize,
}
impl<T> Iterator for IntoIter<[T; N]> { ... }

Obviously I had to adjust the Iterator and Drop implementations (PlayPen).

But now I somehow introduced undefined behavior. Panics happen or not depending on printlns, optimization levels or zodiacal signs.

thread '<main>' panicked at 'index out of bounds: the len is 5 but the index is 139924442675478', <anon>:25
thread '<main>' panicked at 'index out of bounds: the len is 5 but the index is 140451355506257', <anon>:25
application terminated abnormally with signal 4 (Illegal instruction)

Either my second implementation already exhibits undefined behavior, or there is a difference between the second and third implementations. Looking at the generated (not optimized) LLVM-IR, I found that the only fundamental differences happen in the third version which ends up with [[Box<i32>; 5]; 5] types. I can see how I might accidentally create such a type, but I specifically checked the third version for such a mistake and cannot find it.

like image 852
oli_obk Avatar asked Oct 31 '22 08:10

oli_obk


1 Answers

I believe that you are hitting some bug with #[unsafe_destructor]. I reduced your code to this:

#![feature(unsafe_destructor)]

struct IntoIter<U> {
    inner: Option<U>,
}

impl<T> Iterator for IntoIter<[T; 8]> {
    type Item = T;
    fn next(&mut self) -> Option<T> { None }
}

#[unsafe_destructor]
impl<T> Drop for IntoIter<[T; 8]> {
    fn drop(&mut self) {
        // destroy the remaining elements
        for _ in self.by_ref() {}

        unsafe { std::intrinsics::forget(self.inner.take()) }
    }
}

fn main() {
    let arr = [1; 8];
    IntoIter { inner: Some(arr) };
}

I then compiled (rustc -g unsafe.rs) and ran it in rust-lldb. I set a breakpoint on the drop implementation and printed out self:

(lldb) p self
(unsafe::IntoIter<[[i32; 8]; 8]> *) $0 = &0x7fff5fbff568

You can see that it thinks that the type parameter is an array of arrays, just like you noticed. At this point, we are going to trash memory if we actually drop. I believe that Rust still zeroes memory on drop, so we could possibly be writing zeroes all over some arbitrary chunk of memory.

For good measure:

rustc --verbose --version
rustc 1.0.0-dev (cfea8ec41 2015-03-10) (built 2015-03-10)
binary: rustc
commit-hash: cfea8ec41699e25c8fb524d625190f0cb860dc71
commit-date: 2015-03-10
build-date: 2015-03-10
host: x86_64-apple-darwin
release: 1.0.0-dev
like image 62
Shepmaster Avatar answered Dec 16 '22 13:12

Shepmaster