Background
While working on Rust bindings for KConfig as a part of Season of KDE 2022, I came across a few problems while trying to represent QFlags
in Rust:
- Most
QFlags
are defined as C++ enums in which multiple members can have the same value. This is not possible in Rust enum. - It is possible to enable multiple flags using BitwiseOr. Rust enums cannot do bitwise operations.
This post will guide you through the various implementations I came up with and their tradeoffs.
The C++ enum
The enum I was trying to implement was KConfig::OpenFlags
. The enum is given below:
enum OpenFlag { IncludeGlobals = 0x01, ///< Blend kdeglobals into the config object. CascadeConfig = 0x02, ///< Cascade to system-wide config files. SimpleConfig = 0x00, ///< Just a single config file. NoCascade = IncludeGlobals, ///< Include user's globals, but omit system settings. NoGlobals = CascadeConfig, ///< Cascade to system settings, but omit user's globals. FullConfig = IncludeGlobals | CascadeConfig, ///< Fully-fledged config, including globals and cascading to system settings };
Implementation 1: Using Rust modules
This method uses a combination of Rust modules and consants. The sample implementation is as follow:
pub mod OpenFlags { type E = u32; const INCLUDE_GLOBALS: Self::E = 0x01; const CASCADE_CONFIG: Self::E = 0x02; const SIMPLE_CONFIG: Self::E = 0x00; const NO_CASCASE: Self::E = Self::INCLUDE_GLOBALS; const NO_GLOBALS: Self::E = Self::CASCADE_CONFIG; const FULL_CONFIG: Self::E = Self::INCLUDE_GLOBALS | Self::CASCADE_CONFIG; } fn something(flag: OpenFlags::E) {}
Advantages
Const is replaced at compile time, so no performance cost.
All values can be documented in the same way using Rust comments.
Multiple flags can be activated.
Drawbacks
- Not an enum. Just a collection of constants.
Implementation 2: Using const in Impl
This method defines the problematic members as const
in impl
. The sample implementation is as follows:
#[repr(C)] pub enum OpenFlags { IncludeGlobals = 0x01, CascadeConfig = 0x02, SimpleConfig = 0x00, FullConfig = 0x01 | 0x02, } #[allow(non_upper_case_globals)] impl OpenFlags { const NoCascade: Self = Self::IncludeGlobals; const NoGlobals: Self = Self::CascadeConfig; } fn something(flag: OpenFlags) {}
Advantages
- Enum, for the most part.
Drawbacks
- Inconsistent documentation. The constants don’t show up as enum variants.
- Multiple flags cannot be activated
Implementation 3: Converting standard Rust enums when passing to C++
This method uses standard rust enums. The sample implementation is as follows:
pub enum OpenFlags { IncludeGlobals, CascadeConfig, SimpleConfig, NoCascade, NoGlobals, FullConfig } impl OpenFlags { type E = u32; const INCLUDE_GLOBALS: Self::E = 0x01; const CASCADE_CONFIG: Self::E = 0x02; const SIMPLE_CONFIG: Self::E = 0x00; pub fn to_cpp(&self) -> Self::E { match self { Self::IncludeGlobals => Self::INCLUDE_GLOBALS, Self::CascadeConfig => Self::CASCADE_CONFIG, Self::SimpleConfig => Self::SIMPLE_CONFIG, Self::NoCascade => Self::INCLUDE_GLOBALS, Self::NoGlobals => Self::CASCADE_CONFIG, Self::FullConfig => Self::INCLUDE_GLOBALS | Self::CASCADE_CONFIG, } } } fn something(flag: OpenFlags) { let flag = flag.to_cpp(); ... }
Advantages
Completely Enum.
Documentation works as expected.
Drawbacks
Function call every time passing from Rust to C++. I don’t think this will have much performance penalty, but still worth mentioning.
Cannot set multiple flags at once. Eg
OpenFlag::IncludeGlobal | OpenFlag::CascadeConfig
not possible
Implementation 4: use bitflags crate
This is the implementation that I finally settled on. The implementation is as follows:
use bitflags::bitflags bitflags! { /// Determines how the system-wide and user's global settings will affect the reading of the configuration. /// This is a bitfag. Thus it is possible to pass options like `OpenFlags::INCLUDE_GLOBALS | /// OpenFlags::CASCADE_CONFIG` #[repr(C)] pub struct OpenFlags: u32 { /// Blend kdeglobals into the config object. const INCLUDE_GLOBALS = 0x01; /// Cascade to system-wide config files. const CASCADE_CONFIG = 0x02; /// Just a single config file. const SIMPLE_CONFIG = 0x00; /// Include user's globals, but omit system settings. const NO_CASCADE = Self::INCLUDE_GLOBALS.bits; /// Cascade to system settings, but omit user's globals. const NO_GLOBALS = Self::CASCADE_CONFIG.bits; /// Fully-fledged config, including globals and cascading to system settings. const FULL_CONFIG = Self::INCLUDE_GLOBALS.bits | Self::CASCADE_CONFIG.bits; } } fn something(flag: OpenFlags) {}
Advantages
Multiple flags can be used together.
Documentation is consistent.
Drawbacks
- Not enum. Shows up as
struct
in docs.
Documentation Screenshot
Conclusion
I think I will be using bitflags for representing all QFlags
in kconfig for the foreseeable future.
Top comments (0)