How can I initialize a struct within an enum in unsafe?

Hi guys:

I'm trying to initialize an enum variant, which is a struct in my case, using Box::new_uninit_in and ptr::addr_of_mut!. However, I'm struggling to access the right field. Can someone help me with this problem?

#![feature(allocator_api)]
use std::alloc::Allocator;
use std::mem::MaybeUninit;
use core::ptr::{self, NonNull};

fn main() {}

enum Node<K, V> {
    LeafNode {
        size: u16,
        keys: [MaybeUninit<K>; 10],
        vals: [MaybeUninit<V>; 10],
        prev: Option<NonNull<Node<K, V>>>,
        next: Option<NonNull<Node<K, V>>>,
    },
    InternalNode {
        size: u16,
        keys: [MaybeUninit<K>; 10],
        vals: [MaybeUninit<NonNull<Node<K, V>>>; 11],
    },
}

impl<K, V> Node<K, V> {
    unsafe fn init(this: *mut Self) {
        unsafe {
            // 1. How do I access the fields of the struct within the enum?
            // 2. How can I initialize the enum as either the LeafNode variant or the InternalNode variant?
            ptr::addr_of_mut!((*this).size).write(0);
        }
    } 
    fn new<A: Allocator + Clone>(alloc: A) -> Box<Self, A> {
        unsafe {
            let mut node = Box::new_uninit_in(alloc);
            Node::init(node.as_mut_ptr());
            node.assume_init()
        }
    }
}

I think you need to use a manually tagged union

You can probably optimize this a bit further.

1 Like

I don't know the answer to your question; hopefully someone more knowledgeable will come along shortly.


My first impression looking at your code, though, is that there's not a lot of reason to do field-by-field initialization here unless you expect K or V to be huge— The largest variant only contains a 1-bit discriminant, 10 Ks, 10 Vs, and two pointers.

What's wrong with something like this, for example?

#![feature(allocator_api)]
#![feature(maybe_uninit_uninit_array)]
impl<K, V> Node<K, V> {
    fn init()->Self {
        Node::LeafNode {
            size: 0,
            keys: MaybeUninit::uninit_array(),
            vals: MaybeUninit::uninit_array(),
            prev: None,
            next: None
        }
    } 
    pub fn new<A: Allocator + Clone>(alloc: A) -> Box<Self, A> {
        Box::new_in(Self::init(), alloc)
    }
}
2 Likes

If you're going to go that route, you can match it up with a #[repr(C,u8)] enum so that you don't have to deal with the raw union except during construction.

1 Like

@2e71828 @Bruecki

Thank you very much guys. These are the solutions I was looking for.

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.