Rust 位域和枚举 C++ 风格
Rust bitfields and enumerations C++ style
我是来自 C/C++ 的 Rust 初学者。首先,我尝试使用 user32.MessageBox 为 Microsoft Windows 创建一个简单的“Hello-World”程序,但我偶然发现了一个与位域相关的问题。 免责声明:所有代码片段都是在 SO 编辑器中编写的,可能包含错误。
C
中的 MessageBox“Hello-World”
调用函数的 UTF-16LE 版本所需的统一 C 声明是:
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
);
用法:
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);
}
MessageBoxType
枚举包含枚举值和标志值。问题是 MB_DEFBUTTON2
和 MB_DEFBUTTON3
可以一起使用,并且“意外地”导致 MB_DEFBUTTON4
。此外,访问非常容易出错且难看,我必须 |
、&
并在检查值中的标志时手动移动所有内容。
C++ 中的消息框“Hello-World”
在C++中,可以巧妙地将相同的枚举放入一个结构中,结构与枚举大小相同,访问方式更简单、更安全、更漂亮。它使用了位域 - the layout of bitfields not defined by the C standard,但由于我只想将它用于 x86-Windows,它总是一样的,所以我可以依赖它。
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
);
用法:
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);
}
使用此 C++ 版本,我可以将标志作为布尔值访问,并为其他类型提供枚举 类,同时它在内存中仍然是一个简单的 std::uint32_t
。现在我很难在 Rust 中实现它。
Rust 中的消息框“Hello-World”
#[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 ?
我知道 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 但在我看来它无法处理这种“复杂性”。
我如何在 Rust 中实现 MessageBoxFlags
和 MessageBoxType
,以便我可以像在我的 C++ 实现中那样以一种很好的(不一定相同)的方式访问它?
bitfield crate @Boiethios mentioned is kind of what I wanted. I created my own first macro crate bitfield 允许我编写以下内容:
#[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
}
然后我可以使用这样的代码:
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);
我是来自 C/C++ 的 Rust 初学者。首先,我尝试使用 user32.MessageBox 为 Microsoft Windows 创建一个简单的“Hello-World”程序,但我偶然发现了一个与位域相关的问题。 免责声明:所有代码片段都是在 SO 编辑器中编写的,可能包含错误。
C
中的 MessageBox“Hello-World”调用函数的 UTF-16LE 版本所需的统一 C 声明是:
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
);
用法:
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);
}
MessageBoxType
枚举包含枚举值和标志值。问题是 MB_DEFBUTTON2
和 MB_DEFBUTTON3
可以一起使用,并且“意外地”导致 MB_DEFBUTTON4
。此外,访问非常容易出错且难看,我必须 |
、&
并在检查值中的标志时手动移动所有内容。
C++ 中的消息框“Hello-World”
在C++中,可以巧妙地将相同的枚举放入一个结构中,结构与枚举大小相同,访问方式更简单、更安全、更漂亮。它使用了位域 - the layout of bitfields not defined by the C standard,但由于我只想将它用于 x86-Windows,它总是一样的,所以我可以依赖它。
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
);
用法:
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);
}
使用此 C++ 版本,我可以将标志作为布尔值访问,并为其他类型提供枚举 类,同时它在内存中仍然是一个简单的 std::uint32_t
。现在我很难在 Rust 中实现它。
Rust 中的消息框“Hello-World”
#[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 ?
我知道 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 但在我看来它无法处理这种“复杂性”。
我如何在 Rust 中实现 MessageBoxFlags
和 MessageBoxType
,以便我可以像在我的 C++ 实现中那样以一种很好的(不一定相同)的方式访问它?
bitfield crate @Boiethios mentioned is kind of what I wanted. I created my own first macro crate bitfield 允许我编写以下内容:
#[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
}
然后我可以使用这样的代码:
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);