Rust의 비트 플래그

5857 단어 qtrustkde

배경



Season of KDE 2022의 일부로 KConfig에 대한 Rust 바인딩 작업을 하는 동안 Rust에서 QFlags 을 표현하려고 하는 동안 몇 가지 문제를 발견했습니다.
  • 대부분QFlags은 여러 멤버가 동일한 값을 가질 수 있는 C++ 열거형으로 정의됩니다. 이것은 Rust 열거형에서는 불가능합니다.
  • BitwiseOr를 사용하여 여러 플래그를 활성화할 수 있습니다. Rust 열거형은 비트 연산을 수행할 수 없습니다.

  • 이 게시물은 내가 생각해낸 다양한 구현과 그 절충안을 안내할 것입니다.

    C++ 열거형



    구현하려고 했던 열거형은 KConfig::OpenFlags 입니다. 열거형은 아래와 같습니다.

    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
    };
    
    


    구현 1: Rust 모듈 사용



    이 방법은 Rust 모듈과 상수의 조합을 사용합니다. 샘플 구현은 다음과 같습니다.

    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) {}
    
    


    장점


  • Const는 컴파일 시간에 대체되므로 성능 비용이 발생하지 않습니다.
  • 모든 값은 Rust 주석을 사용하여 동일한 방식으로 문서화할 수 있습니다.
  • 여러 플래그를 활성화할 수 있습니다.

  • 단점


  • 열거형이 아닙니다. 상수의 모음일 뿐입니다.

  • 구현 2: Impl에서 const 사용



    이 메서드는 문제가 있는 멤버를 constimpl 로 정의합니다. 샘플 구현은 다음과 같습니다.

    #[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) {}
    
    


    장점


  • 대부분의 경우 열거형입니다.

  • 단점


  • 문서가 일관되지 않습니다. 상수는 열거형 변형으로 표시되지 않습니다.
  • 여러 플래그를 활성화할 수 없음

  • 구현 3: C++로 전달할 때 표준 Rust 열거형 변환



    이 방법은 표준 녹 열거형을 사용합니다. 샘플 구현은 다음과 같습니다.

    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();
        ...
    }
    
    


    장점


  • 완전히 열거합니다.
  • 문서가 예상대로 작동합니다.

  • 단점


  • Rust에서 C++로 전달할 때마다 함수가 호출됩니다. 나는 이것이 많은 성능 저하를 가져오지 않을 것이라고 생각하지만 여전히 언급할 가치가 있습니다.
  • 한 번에 여러 플래그를 설정할 수 없습니다. 예: OpenFlag::IncludeGlobal | OpenFlag::CascadeConfig 불가능

  • 구현 4: bitflags 크레이트 사용



    이것이 내가 마침내 결정한 구현입니다. 구현은 다음과 같습니다.

    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) {}
    
    


    장점


  • 여러 플래그를 함께 사용할 수 있습니다.
  • 문서가 일관성이 있습니다.

  • 단점


  • 열거형이 아닙니다. 문서에서 struct로 표시됩니다.

  • 문서 스크린샷





    결론



    나는 가까운 장래에 bitflags의 모든 QFlags를 나타내기 위해 kconfig을 사용할 것이라고 생각합니다.

    좋은 웹페이지 즐겨찾기