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)