Initializing struct field by field

Is a struct considered initialized if all its fields have been initialized? Does it matter if the struct has padding? Consider this example:

#[derive(Copy,Clone)]
struct S{
  a:u16,
  b:u8,
}

union U{
  s:S,
  u:(),
}

fn main() {
    let mut u=U{u:()};
    u.s.a=0;
    u.s.b=0;
    let _s=unsafe{u.s};
}
1 Like

If this was in C++ I'd be able to say no: accessing a structure field before the structure's lifetime begins is UB. I don't know if Rust has a similar rule or not.

You can't have uninitialized structs. This code doesn't compile.

But that code does compile:

After adding println!("{:?}", _s); at the end:

✗ cargo run  
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/junk`
S { a: 0, b: 0 }

But not if the unsafe is removed of course:

✗ cargo clippy   
    Checking unk v0.1.0 (/Users/michaael/conveqs/unk)
error[E0133]: access to union field is unsafe and requires unsafe function or block
  --> src/main.rs:16:12
   |
16 |     let _s=u.s;
   |            ^^^ access to union field
   |
   = note: the field may not be properly initialized: using uninitialized data will cause undefined behavior
1 Like

At least miri is happy.

Reading the relevant reference page it sounds like a read is sound, as long as the union field has a valid bit pattern. Which for your example it does.

EDIT
But see that this is union specific. Since you can combine any two types with a union, e.g. i32 and bool it has to be possible for the union to have a bit pattern that is valid for one of its variants, eg 0x4 for i32, but invalid for its other variant.

1 Like

Note that if you're tempted to do this, you should probably be using

which has lots of methods and documentation talking about which things are sound.

3 Likes

The code you posted compiles and runs even if u.s.a and u.s.b are not initialized. This is due to the unsafe use of the union, it is not related to struct initialization.

1 Like

There is another scenario where you can kind of deinitialize a struct by moving some of its fields out. If you then reinitialize the fields the struct is fine.

Uncomment the assignement to f.a and the code will compile.

struct Foo {
    a: Vec<i32>,
    b: Vec<u32>,
}

fn main() {
    let mut f = Foo {a: vec![], b: vec![]};
    let y = f.a;
    //f.a = vec![];
    drop(f);
}
error[E0382]: use of partially moved value: `f`
  --> src/main.rs:10:10
   |
8  |     let y = f.a;
   |             --- value partially moved here
9  |     //f.a = vec![];
10 |     drop(f);
   |          ^ value used here after partial move
   |
   = note: partial move occurs because `f.a` has type `Vec<i32>`, which does not implement the `Copy` trait

Playground

So yes, as long as you initialize all fields of a struct in a union, then the read access as that struct should be sound.

1 Like

Interesting, thanks!

I was wondering why this code shouldn't be accepted, since partial deinitialization is supported:

#[derive(Copy, Clone)]
struct S {
    a: u16,
    b: u8,
}

fn foo() -> S {
    let s: S;
    s.a = 1;
    s.b = 2;
    s
}

And the answer is it isn't implemented yet, but there are maybe also semantic concerns with having partially usable instances of structures that can't be fully initialized — I'm not clear on the details. The current error:

error[E0381]: partially assigned binding `s` isn't fully initialized
 --> src/lib.rs:9:5
  |
8 |     let s: S;
  |         - binding declared here but left uninitialized
9 |     s.a = 1;
  |     ^^^^^^^ `s` partially assigned here but it isn't fully initialized
  |
  = help: partial initialization isn't supported, fully initialize the binding with a default value and mutate it, or use `std::mem::MaybeUninit`
2 Likes

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.