pointersruststructenumsunsafe

How to init a Struct within a Enum in unsafe codes


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()
        }
    }
}

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=a6f780637a195cce4a5c4aabff131bfc

I tried to cast the node to Node::LeafNode using node as Node::LeafNode but rust doesn't allow me to do it. Maybe I did it in a wrong.


Solution

  • Initializing is simple enough:

    this.write(Self::LeafNode {
        size: 0,
        keys: MaybeUninit::uninit_array(),
        vals: MaybeUninit::uninit_array(),
        prev: None,
        next: None,
    });
    this.write(Self::InternalNode {
        size: 0,
        keys: MaybeUninit::uninit_array(),
        vals: MaybeUninit::uninit_array(),
    });
    

    MaybeUninit::uninit_array() is unstable, but it can be easily emulated via MaybeUninit::uninit().assume_init() (it is safe to assume_init() an array of MaybeUninits).

    For accessing and mutating you will need a reference. You cannot (on stable) manually get the offset of fields within enum.

    This is not that bad, though: if you've initialized the variant, you can usually create a reference to it. If you have problems with aliasing, consider UnsafeCell.

    If you really need access to the fields, you can use a #[repr(int)] or #[repr(C)] enums, that their layout is defined and you can calculate it.

    On nightly you can do what you want: there is offset_of!() (the macro is stable, but parts of its behaviors are still unstable):

    this.byte_add(std::mem::offset_of!(Self, LeafNode.size))
        .cast::<u16>()
        .write(0);
    this.byte_add(std::mem::offset_of!(Self, LeafNode.prev))
        .cast::<Option<NonNull<Node<K, V>>>>()
        .write(None);
    this.byte_add(std::mem::offset_of!(Self, LeafNode.next))
        .cast::<Option<NonNull<Node<K, V>>>>()
        .write(None);
    

    There should be no advantage for most cases for this method over the simple approach (except it is more verbose and makes you look smarter, of course). Worse, even using offset_of!() you cannot initialize the discriminant, so you will never be able to create a reference to the enum or pattern-match it.