Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust bitfields and enumerations C++ style

I'm a Rust beginner which comes from C/C++. To start off I tried to create a simple "Hello-World" like program for Microsoft Windows using user32.MessageBox where I stumbled upon a problem related to bitfields. Disclaimer: All code snippets were written in the SO editor and might contain errors.

MessageBox "Hello-World" in C

The consolidated C declarations needed to call the UTF-16LE version of the function are:

enum MessageBoxResult {
    IDFAILED,
    IDOK,
    IDCANCEL,
    IDABORT,
    IDRETRY,
    IDIGNORE,
    IDYES,
    IDNO,
    IDTRYAGAIN = 10,
    IDCONTINUE
};

enum MessageBoxType {
    // Normal enumeration values.
    MB_OK,
    MB_OKCANCEL,
    MB_ABORTRETRYIGNORE,
    MB_YESNOCANCEL,
    MB_YESNO,
    MB_RETRYCANCEL,
    MB_CANCELTRYCONTINUE,

    MB_ICONERROR            = 0x10UL,
    MB_ICONQUESTION         = 0x20UL,
    MB_ICONEXCLAMATION      = 0x30UL,
    MB_ICONINFORMATION      = 0x40UL,

    MB_DEFBUTTON1           = 0x000UL,
    MB_DEFBUTTON2           = 0x100UL,
    MB_DEFBUTTON3           = 0x200UL,
    MB_DEFBUTTON4           = 0x300UL,

    MB_APPLMODAL            = 0x0000UL,
    MB_SYSTEMMODAL          = 0x1000UL,
    MB_TASKMODAL            = 0x2000UL,

    // Flag values.
    MB_HELP                 = 1UL << 14,

    MB_SETFOREGROUND        = 1UL << 16,
    MB_DEFAULT_DESKTOP_ONLY = 1UL << 17,
    MB_TOPMOST              = 1UL << 18,
    MB_RIGHT                = 1UL << 19,
    MB_RTLREADING           = 1UL << 20,
    MB_SERVICE_NOTIFICATION = 1UL << 21
};

MessageBoxResult __stdcall MessageBoxW(
    HWND            hWnd,
    const wchar_t * lpText,
    const wchar_t * lpCaption,
    MessageBoxType  uType
);

Usage:

MessageBoxType mbType = MB_YESNO | MB_ICONEXCLAMATION | MB_DEFBUTTON3 | MB_TOPMOST;
if ((mbType & 0x0F /* All bits for buttons */ == MB_YESNO) && (mbType & 0xF0 /* All bits for icons */ == MB_ICONEXCLAMATION) && (mbType & 0xF00 /* All bits for default buttons */ == MB_DEFBUTTON3) && (mbType & MB_TOPMOST != 0)) {
    MessageBoxW(NULL, L"Text", L"Title", mbType);
}

The MessageBoxType enumeration contains enumeration values and flag values. The problem with that is that MB_DEFBUTTON2 and MB_DEFBUTTON3 can be used together and "unexpectedly" result in MB_DEFBUTTON4. Also the access is quite error prone and ugly, I have to |, & and shift everything manually when checking for flags in the value.

MessageBox "Hello-World" in C++

In C++ the same enumeration can be cleverly put into a structure, which has the same size as the enumeration and makes the access way easier, safer and prettier. It makes use of bitfields - the layout of bitfields not defined by the C standard, but since I only want to use it for x86-Windows it is always the same, so I can rely on it.

enum class MessageBoxResult : std::uint32_t {
    Failed,
    Ok,
    Cancel,
    Abort,
    Retry,
    Ignore,
    Yes,
    No,
    TryAgain = 10,
    Continue
};

enum class MessageBoxButton : std::uint32_t {
    Ok,
    OkCancel,
    AbortRetryIgnore,
    YesNoCancel,
    YesNo,
    RetryCancel,
    CancelTryContinue
};

enum class MessageBoxDefaultButton : std::uint32_t {
    One,
    Two,
    Three,
    Four
};

// Union so one can access all flags as a value and all boolean values separately.
union MessageBoxFlags {
    enum class Flags : std::uint32_t {
        None,
        Help                = 1UL << 0,
        SetForeground       = 1UL << 2,
        DefaultDesktopOnly  = 1UL << 3,
        TopMost             = 1UL << 4,
        Right               = 1UL << 5,
        RtlReading          = 1UL << 6,
        ServiceNotification = 1UL << 7
    };

    // Flags::operator|, Flags::operator&, etc. omitted here.

    Flags flags;
    struct {
        bool help                   : 1;
        char _padding0              : 1;
        bool setForeground          : 1;
        bool defaultDesktopOnly     : 1;
        bool topMost                : 1;
        bool right                  : 1;
        bool rtlReading             : 1;
        bool serviceNotification    : 1;
        char _padding1              : 8;
        char _padding2              : 8;
        char _padding3              : 8;
    };

    constexpr MessageBoxFlags(const Flags flags = Flags::None)
        : flags(flags) {}
};

enum class MessageBoxIcon : std::uint32_t {
    None,
    Stop,
    Question,
    Exclamation,
    Information
};

enum class MessageBoxModality : std::uint32_t {
    Application,
    System,
    Task
};

union MessageBoxType {
    std::uint32_t value;
    struct {                                          // Used bits                                   Minimum (Base 2)                          Maximum (Base 2)                          Min (Base 16) Max (Base 16)
        MessageBoxButton button                 :  4; // 0000.0000.0000.0000|0000.0000.0000.XXXX     0000.0000.0000.0000|0000.0000.0000.0000 - 0000.0000.0000.0000|0000.0000.0000.0110 : 0x0000.0000 - 0x0000.0006
        MessageBoxIcon icon                     :  4; // 0000.0000.0000.0000|0000.0000.XXXX.0000     0000.0000.0000.0000|0000.0000.0001.0000 - 0000.0000.0000.0000|0000.0000.0100.0000 : 0x0000.0010 - 0x0000.0040
        MessageBoxDefaultButton defaultButton   :  4; // 0000.0000.0000.0000|0000.XXXX.0000.0000     0000.0000.0000.0000|0000.0001.0000.0000 - 0000.0000.0000.0000|0000.0011.0000.0000 : 0x0000.0100 - 0x0000.0300
        MessageBoxModality modality             :  2; // 0000.0000.0000.0000|00XX.0000.0000.0000     0000.0000.0000.0000|0001.0000.0000.0000 - 0000.0000.0000.0000|0010.0000.0000.0000 : 0x0000.1000 - 0x0000.2000
        MessageBoxFlags::Flags flags            :  8; // 0000.0000.00XX.XXXX|XX00.0000.0000.0000     0000.0000.0000.0000|0100.0000.0000.0000 - 0000.0000.0010.0000|0000.0000.0000.0000 : 0x0000.4000 - 0x0020.0000
        std::uint32_t _padding0                 : 10; // XXXX.XXXX.XX00.0000|0000.0000.0000.0000     
    };

    MessageBoxType(
        const MessageBoxButton button,
        const MessageBoxIcon icon = MessageBoxIcon::None,
        const MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton::One,
        const MessageBoxModality modality = MessageBoxModality::Application,
        const MessageBoxFlags::Flags flags = MessageBoxFlags::Flags::None
    ) : button(button), icon(icon), defaultButton(defaultButton), modality(modality), flags(flags), _padding0(0) {}

    MessageBoxType() : value(0) {}
};

MessageBoxResult __stdcall MessageBoxW(
    HWND            parentWindow,
    const wchar_t * text,
    const wchar_t * caption,
    MessageBoxType  type
);

Usage:

auto mbType = MessageBoxType(MessageBoxButton::YesNo, MessageBoxIcon::Exclamation, MessageBoxDefaultButton::Three, MessageBoxModality::Application, MessageBoxFlags::Flags::TopMost);
if (mbType.button == MessageBoxButton::YesNo && mbType.icon == MessageBoxIcon::Exclamation && mbType.defaultButton == MessageBoxDefaultButton::Three && mbType.flags.topMost) {
    MessageBoxW(nullptr, L"Text", L"Title", mbType);
}

With this C++ version I can access flags as boolean values and have enumeration classes for the other types, all while it still being a simple std::uint32_t in memory. Now I struggled to implement this in Rust.

MessageBox "Hello-World" in Rust

#[repr(u32)]
enum MessageBoxResult {
    Failed,
    Ok,
    Cancel,
    Abort,
    Retry,
    Ignore,
    Yes,
    No,
    TryAgain = 10,
    Continue
}

#[repr(u32)]
enum MessageBoxButton {
    Ok,
    OkCancel,
    AbortRetryIgnore,
    YesNoCancel,
    YesNo,
    RetryCancel,
    CancelTryContinue
}

#[repr(u32)]
enum MessageBoxDefaultButton {
    One,
    Two,
    Three,
    Four
}

#[repr(u32)]
enum MessageBoxIcon {
    None,
    Stop,
    Question,
    Exclamation,
    Information
}

#[repr(u32)]
enum MessageBoxModality {
    Application,
    System,
    Task
}

// MessageBoxFlags and MessageBoxType ?

I know about the WinApi crate which to my understanding is generated automatically from VC++-header files which doesn't help, because I will have the same problems as in C. I also saw the bitflags macro but it seems to me it doesn't handle this kind of "complexity".

How would I implement MessageBoxFlags and MessageBoxType in Rust, so I can access it in a nice (not necessarily the same) way as in my C++ implementation?

like image 928
Maurice Kayser Avatar asked Apr 10 '18 15:04

Maurice Kayser


People also ask

Does rust have Bitfields?

rust-bitfieldThis crate provides macros to generate bitfield-like struct. This a complete rewrite of the bitfield crate. You can find the previous version in the rust-bitfield-legacy repository. This version works on the stable version of rustc and use a different syntax with different possibility.

What is bit field with example?

Bit fields can be used to reduce memory consumption when a program requires a number of integer variables which always will have low values. For example, in many systems storing an integer value requires two bytes (16-bits) of memory; sometimes the values to be stored actually need only one or two bits.


1 Answers

The bitfield crate @Boiethios mentioned is kind of what I wanted. I created my own first macro crate bitfield which allows me to write the following:

#[bitfield::bitfield(32)]
struct Styles {
    #[field(size = 4)] button: Button,
    #[field(size = 4)] icon: Icon,
    #[field(size = 4)] default_button: DefaultButton,
    #[field(size = 2)] modality: Modality,
    style: Style
}

#[derive(Copy, Clone, bitfield::Flags)]
#[repr(u8)]
enum Style {
    Help = 14,
    Foreground = 16,
    DefaultDesktopOnly,
    TopMost,
    Right,
    RightToLeftReading,
    ServiceNotification
}

#[derive(Clone, Copy, bitfield::Field)]
#[repr(u8)]
enum Button {
    Ok,
    OkCancel,
    AbortRetryIgnore,
    YesNoCancel,
    YesNo,
    RetryCancel,
    CancelTryContinue
}

#[derive(Clone, Copy, bitfield::Field)]
#[repr(u8)]
enum DefaultButton {
    One,
    Two,
    Three,
    Four
}

#[derive(Clone, Copy, bitfield::Field)]
#[repr(u8)]
enum Icon {
    None,
    Stop,
    Question,
    Exclamation,
    Information
}

#[derive(Clone, Copy, bitfield::Field)]
#[repr(u8)]
enum Modality {
    Application,
    System,
    Task
}

I can then use the code like this:

let styles = Styles::new()
    .set_button(Button::CancelTryContinue)
    .set_icon(Icon::Exclamation)
    .set_style(Style::Foreground, true)
    .set_style(Style::TopMost, true);

let result = user32::MessageBoxW(/* ... */, styles);
like image 146
Maurice Kayser Avatar answered Oct 18 '22 09:10

Maurice Kayser