Runtime Crash on using std::mem::uninitialized


#1

Say I have a type like this:

struct A([u8; 64]);

i know that i will be filling this in as soon as it is defined, so i don’t want to waste cycles initializing it:

let mut a = A([0u8; 64]);
for i in 0..64 {
    a.0[i] = rand::random::<u8>();
}

so i tried 2 approaches:

<1>
let mut a: A = unsafe { std::mem::uninitialized() };
for i in 0..64 {
    a.0[i] = rand::random::<u8>();
}

<2>
let mut a = A(unsafe { std::mem::uninitialized() });
for i in 0..64 {
    a.0[i] = rand::random::<u8>();
}

<1> crashes for me at runtime (in both debug and release) while <2> is fine in both.

So the questions (that will help me understand things deeper) are:
<i> what is the difference between <1> and <2> above ?
<ii> If you know you are soon going to initialize it and not use it without initializing it, is it a common practice to use std::mem::uninitialized() or ppl usually don’t care of all these and leave it to the compiler to optimize if possible?


#2

What is the exact code that’s crashing for you? My attempt to make your <1> compile:

fn main() {
    struct A([u8; 64]);
    let mut a: A = unsafe { std::mem::uninitialized() };
    for i in 0..64 {
        a.0[i] = 4; // cba to mess with cargo to get rand
    }
    println!("{}", a.0[5]);
}

runs fine with and without optimization. (And there is no reason it shouldn’t.)


#3

Ok … maybe it’s undefined behavior then. Here is what i get in debug mode:

Process didn't exit successfully: `/home/spandan/virtualization/coding/rust-maidsafe/maidsafe-client/maidsafe_client/target/debug/maidsafe_client-b676cf89b5b9fc72 hybrid_encryption_decryption` (signal: 5)

Maybe the bigger the program gets more the chances of the crash etc.
If you want to see the exact code it’s here:
https://github.com/ustulation/maidsafe_client/blob/runtime-crash/src/client/mod.rs#L360

so in src/client/mod.rs you can find function hybrid_decrypt where you will see the lines:

 // let mut key = ::sodiumoxide::crypto::secretbox::Key(unsafe { ::std::mem::uninitialized() });
 // let mut iv  = ::sodiumoxide::crypto::secretbox::Nonce(unsafe { ::std::mem::uninitialized() });
 let mut key : ::sodiumoxide::crypto::secretbox::Key = unsafe { ::std::mem::uninitialized() };
 let mut iv  : ::sodiumoxide::crypto::secretbox::Nonce = unsafe { ::std::mem::uninitialized() };

so this crashes but if i uncomment 1st two and comment the last two everything’s fine.
run cargo test hybrid_encryption_decryption to see it.

Anyway, without actually referring to the code and purely going by what the standard says, what is the expected outcome of the program in my original question:
is my extrapolation fine ? is it undefined behavior in both cases ? are both correct and something wrong with the rustc codegen (using rustc 1.2.0-nightly (fbb13543f 2015-06-11)) ? Something else ? I am not really concerned if it runs fine on someone’s machine - want to know if there is a formal standard that says something about it.


#4

Sorry i need this - so … Bump !

Can someone tell from standard point of view if this is undefined or correct ?


#5

Your type A from the first post, does it have a destuctor? That would make the difference.

If you don’t want to waste cycles, then don’t use this, because rand::random::<u8>() is very inefficient, fetching from thread local storage for each call. When working with rand, get a reference or ownership to something that impls the Rng trait, and use that in the loop.

let mut a = A([0u8; 64]);
for i in 0..64 {
    a.0[i] = rand::random::<u8>();
}

And maybe it’s better to not use mem::uninitialized() until later. It’s an optimization you can do carefully when your code is in a working state.


#6

Ah ok, ya i did read on that in the rand crate itself i think. I’ll keep a note of that, but here my concern is to understand about what is the difference between the two std::mem::unitialized() usages. Of-course it is not something terrible for me to not use that and initialize it in the definition itself, but just to understand what’s happening.

So you say a dtor would make a difference between the two usages. Can you please explain this ? What exactly are the differences between <1> and <2> (in the original question) with and without dtor ?


#7

The representation of the type changes if it has a destructor, and using mem::uninitialized() in that way is then completely broken. There’s an inaccessible field (the drop flag) that needs to be initialized too.


#8

Ah brilliant ! thanks, So do you have something pointing to memory layouts etc for Rust - esp under the hood. Something like this had helped me reason about my code a lot in c++ long long time back:

Of-course many things there are compiler dependent (like the aggregate structure of virtual function pointer etc) but that’s ok; it makes for a very good grip over the language.

Would love it if something like that is already there for Rust.


#9

We don’t have this stuff documented because as you say, it’s very dependent on the compiler and not guaranteed to succeed. For example, @pnkfelix is currently working on eliminating the very flag you’re talking about, so if you depend on that memory layout, your code will subtly break in the future…