Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a struct in Rust where the last element is an array of variable length?

I'm trying to create a dynamic LOGPALETTE struct in Rust. The last field of this struct is nominally declared as an array of 1 element, but in fact it can be an array of any number of elements. We specify the number of elements as we alloc the struct in the heap.

This is how I do it in C:

PALETTEENTRY entry = {0};

LOGPALETTE* pLogPal = (LOGPALETTE*)malloc(
    sizeof(LOGPALETTE) + 2 * sizeof(PALETTEENTRY) // room for 2 elements
);
pLogPal->palNumEntries = 2;      // inform we have 2 elements
pLogPal->palPalEntry[0] = entry; // fill the 2 elements
pLogPal->palPalEntry[1] = entry;

// use pLogPal...

free(pLogPal);

How can I write this in Rust, considering LOGPALETTE and PALETTEENTRY declarations?

EDIT:

I implemented the answer in WinSafe library here. If you want to discuss it, feel free to open an issue at the repo.

like image 405
rodrigocfd Avatar asked Sep 15 '25 19:09

rodrigocfd


2 Answers

Unfortunately, there is no direct support in Rust for VLAs. Therefore, you need to do that manually (not worse than C, but in C you can use most the language's facilities to work with pointers while in Rust you must use raw pointers and cannot use references). You also need to be very careful not to create references, because references can only be used to read the data within the size of the type they are pointing to. You may also not create references to uninitialized memory.

Here's an example of this being done:

unsafe {
    // Allocate space for 2 elements (one is already there).
    let layout = std::alloc::Layout::new::<tagLOGPALETTE>()
        .extend(std::alloc::Layout::array::<tagPALETTEENTRY>(1).unwrap())
        .unwrap()
        .0;
    let log_pal = std::alloc::alloc(layout).cast::<tagLOGPALETTE>();
    if log_pal.is_null() {
        std::alloc::handle_alloc_error(layout);
    }
    // Don't use `*` or `=`, it will create a reference!
    std::ptr::addr_of_mut!((*log_pal).palNumEntries).write(2);
    let entry = std::mem::zeroed::<tagPALETTEENTRY>();
    std::ptr::addr_of_mut!((*log_pal).palPalEntry[0])
        .add(0)
        .write(entry);
    std::ptr::addr_of_mut!((*log_pal).palPalEntry[0])
        .add(1)
        .write(entry);

    // Here, after you initialized them, you can create a slice of the entries:
    let entries = std::slice::from_raw_parts(std::ptr::addr_of!((*log_pal).palPalEntry[0]), 2);
    // But you can't create a reference to the whole type, even without accessing the entires
    // (only other fields), because you didn't initialize `palVersion`!
}
like image 189
Chayim Friedman Avatar answered Sep 17 '25 10:09

Chayim Friedman


Adding to ChayimFriedman's answer, here is how I would tackle this problem:

use std::{alloc::Layout, error::Error, fmt::Debug};

use windows::Win32::Graphics::Gdi::{LOGPALETTE, PALETTEENTRY};

pub struct LogPalette {
    data: *mut LOGPALETTE,
    layout: Layout,
}

impl Debug for LogPalette {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("LogPalette")
            .field("version", &self.get_version())
            .field("entries", &self.get_entries())
            .finish()
    }
}

impl LogPalette {
    pub fn new(
        version: u16,
        data: impl ExactSizeIterator<Item = PALETTEENTRY>,
    ) -> Result<LogPalette, Box<dyn Error>> {
        let num_entries: u16 = data.len().try_into()?;

        unsafe {
            // Compute required allocation size and alignment
            let layout = Layout::new::<LOGPALETTE>()
                .extend(Layout::array::<PALETTEENTRY>(
                    num_entries.saturating_sub(1).into(),
                )?)?
                .0
                .pad_to_align();

            // Allocate
            let logpalette = std::alloc::alloc(layout).cast::<LOGPALETTE>();
            if logpalette.is_null() {
                std::alloc::handle_alloc_error(layout);
            }

            // Fill initial data
            // Don't use `*` or `=`, it will create a reference!
            std::ptr::addr_of_mut!((*logpalette).palVersion).write(version);
            std::ptr::addr_of_mut!((*logpalette).palNumEntries).write(num_entries);

            let entries = std::ptr::addr_of_mut!((*logpalette).palPalEntry).cast::<PALETTEENTRY>();

            for (index, entry) in data.enumerate().take(num_entries.into()) {
                entries.add(index).write(entry);
            }

            // This is the point where all elements are initialized; now we are allowed to create references.

            Ok(Self {
                data: logpalette,
                layout,
            })
        }
    }

    pub fn get_raw(&self) -> *const LOGPALETTE {
        self.data
    }

    pub fn get_raw_mut(&mut self) -> *mut LOGPALETTE {
        self.data
    }

    pub fn get_entries(&self) -> &[PALETTEENTRY] {
        unsafe {
            let size = self.get_num_entries();
            let pal_ptr = std::ptr::addr_of!((*self.data).palPalEntry);
            std::slice::from_raw_parts(pal_ptr.cast(), size.into())
        }
    }

    pub fn get_entries_mut(&mut self) -> &mut [PALETTEENTRY] {
        unsafe {
            let size = self.get_num_entries();
            let pal_ptr = std::ptr::addr_of_mut!((*self.data).palPalEntry);
            std::slice::from_raw_parts_mut(pal_ptr.cast(), size.into())
        }
    }

    pub fn get_version(&self) -> u16 {
        unsafe { (*self.data).palVersion }
    }

    pub fn get_num_entries(&self) -> u16 {
        unsafe { (*self.data).palNumEntries }
    }
}

impl Drop for LogPalette {
    fn drop(&mut self) {
        unsafe {
            std::alloc::dealloc(self.data.cast(), self.layout);
        }
    }
}

fn main() {
    let entries = [
        PALETTEENTRY {
            peRed: 255,
            peGreen: 128,
            peBlue: 0,
            peFlags: 0,
        },
        PALETTEENTRY {
            peRed: 255,
            peGreen: 128,
            peBlue: 255,
            peFlags: 0,
        },
        PALETTEENTRY {
            peRed: 0,
            peGreen: 0,
            peBlue: 255,
            peFlags: 0,
        },
    ];

    let mut palette = LogPalette::new(1, entries.into_iter()).unwrap();
    println!("{:#?}", palette);

    println!();
    println!("Setting red value of entry 2 to 127 ...");
    println!();
    palette.get_entries_mut()[2].peRed = 127;

    println!("{:#?}", palette);
}
LogPalette {
    version: 1,
    entries: [
        PALETTEENTRY {
            peRed: 255,
            peGreen: 128,
            peBlue: 0,
            peFlags: 0,
        },
        PALETTEENTRY {
            peRed: 255,
            peGreen: 128,
            peBlue: 255,
            peFlags: 0,
        },
        PALETTEENTRY {
            peRed: 0,
            peGreen: 0,
            peBlue: 255,
            peFlags: 0,
        },
    ],
}

Setting red value of entry 2 to 127 ...

LogPalette {
    version: 1,
    entries: [
        PALETTEENTRY {
            peRed: 255,
            peGreen: 128,
            peBlue: 0,
            peFlags: 0,
        },
        PALETTEENTRY {
            peRed: 255,
            peGreen: 128,
            peBlue: 255,
            peFlags: 0,
        },
        PALETTEENTRY {
            peRed: 127,
            peGreen: 0,
            peBlue: 255,
            peFlags: 0,
        },
    ],
}
like image 43
Finomnis Avatar answered Sep 17 '25 09:09

Finomnis