| // Copyright 2026 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! **To define a base::Feature in Rust, use the `base_feature` macro.** |
| //! (Detailed information on usage can be found on the comment for the |
| //! macro itself, which is defined at the bottom of this file.) |
| |
| use std::ffi::c_char; |
| use std::sync::atomic::AtomicU32; |
| |
| #[doc(hidden)] |
| pub mod internal { |
| /// Secret handshake to (try to) ensure all places that construct a |
| /// base::Feature go through the helper `BASE_FEATURE()` macro. |
| pub enum FeatureMacroHandshake { |
| Secret, |
| } |
| } |
| |
| /// Specifies whether a given feature is enabled or disabled by default. |
| /// |
| /// Note: The country-restricted states (`FEATURE_DISABLED_FOR_COUNTRIES` and |
| // `FEATURE_ENABLED_FOR_COUNTRIES`) defined in C++ are not supported for |
| /// Rust-defined features and are omitted here. |
| #[repr(i32)] |
| #[derive(Clone, Copy)] |
| pub enum FeatureState { |
| Disabled = 0, |
| Enabled = 1, |
| } |
| |
| /// Rust equivalent of the C++ `base::Feature` type. |
| /// |
| /// This struct must maintain an identical memory layout to the C++ version |
| /// for FFI compatibility. Detailed documentation for feature flags can be |
| /// found in `base/feature.h`. |
| #[repr(C)] |
| pub struct Feature { |
| /// The name of the feature. Private to enforce null-termination via the |
| /// constructor. |
| name: &'static c_char, |
| /// The default state of the feature. |
| pub default_state: FeatureState, |
| /// Cached override state, used by C++ FeatureList::IsEnabled. |
| /// Initialized to 0. |
| cached_value: AtomicU32, |
| } |
| |
| // LINT.IfChange(FeatureStruct) |
| const _: () = { |
| assert!( |
| std::mem::size_of::<Feature>() |
| == if std::mem::size_of::<*const ()>() == 8 { 16 } else { 12 } |
| ); |
| assert!(std::mem::align_of::<Feature>() == std::mem::size_of::<*const ()>()); |
| |
| assert!(std::mem::offset_of!(Feature, name) == 0); |
| assert!(std::mem::offset_of!(Feature, default_state) == std::mem::size_of::<*const ()>()); |
| assert!(std::mem::offset_of!(Feature, cached_value) == std::mem::size_of::<*const ()>() + 4); |
| }; |
| // LINT.ThenChange(feature_list_unittest.cc:FeatureStruct) |
| |
| impl Feature { |
| /// Create a new Feature definition where the name is derived from the Rust |
| /// identifier. Internal use only via the `base_feature!` macro. |
| /// |
| /// # Safety |
| /// `name` must be a null-terminated string. |
| #[doc(hidden)] |
| pub const unsafe fn from_id( |
| name: &'static str, |
| default_state: FeatureState, |
| _handshake: internal::FeatureMacroHandshake, |
| ) -> Self { |
| Self { |
| // Safety: name is guaranteed to be null-terminated. |
| // We take a pointer here. |
| name: unsafe { &*(name.as_ptr() as *const c_char) }, |
| default_state, |
| cached_value: AtomicU32::new(0), |
| } |
| } |
| |
| /// Check if the feature is enabled. |
| pub fn is_enabled(&self) -> bool { |
| ffi::FeatureList::IsEnabled(self.into()) |
| } |
| } |
| |
| impl<'a> From<&'a Feature> for &'a ffi::Feature { |
| fn from(feature: &'a Feature) -> Self { |
| // Safety: Feature is ABI-compatible with ffi::Feature (checked by |
| // static asserts above). |
| unsafe { std::mem::transmute(feature) } |
| } |
| } |
| |
| // Safety: Feature is intended to be used as a global static and is thread-safe |
| // thanks to the atomic cached_value. |
| unsafe impl Sync for Feature {} |
| |
| /// A parameter for a `base::Feature`. |
| /// |
| /// Feature parameters allow for tuning experiments from the server side via |
| /// Finch. |
| pub struct FeatureParam<T: 'static> { |
| pub feature: &'static Feature, |
| pub name: &'static str, |
| pub default_value: T, |
| } |
| |
| impl<T: 'static> FeatureParam<T> { |
| /// Creates a new `FeatureParam<T>`. |
| pub const fn new(feature: &'static Feature, name: &'static str, default_value: T) -> Self { |
| Self { feature, name, default_value } |
| } |
| } |
| |
| impl FeatureParam<bool> { |
| /// Returns the value of the parameter. |
| pub fn get(&self) -> bool { |
| ffi::get_bool_param(self.feature.into(), self.name, self.default_value) |
| } |
| } |
| |
| impl FeatureParam<i32> { |
| /// Returns the value of the parameter. |
| pub fn get(&self) -> i32 { |
| ffi::get_int_param(self.feature.into(), self.name, self.default_value) |
| } |
| } |
| |
| impl FeatureParam<f64> { |
| /// Returns the value of the parameter. |
| pub fn get(&self) -> f64 { |
| ffi::get_double_param(self.feature.into(), self.name, self.default_value) |
| } |
| } |
| |
| impl FeatureParam<&'static str> { |
| /// Returns the value of the parameter. |
| pub fn get(&self) -> String { |
| ffi::get_string_param(self.feature.into(), self.name, self.default_value) |
| } |
| } |
| |
| impl FeatureParam<String> { |
| /// Returns the value of the parameter. |
| pub fn get(&self) -> String { |
| ffi::get_string_param(self.feature.into(), self.name, &self.default_value) |
| } |
| } |
| |
| /// TimeDelta in Rust is an alias to `std::time::Duration`. |
| pub type TimeDelta = std::time::Duration; |
| |
| impl FeatureParam<std::time::Duration> { |
| /// Returns the value of the parameter. |
| pub fn get(&self) -> std::time::Duration { |
| let default_micros: i64 = self.default_value.as_micros().try_into().unwrap_or(i64::MAX); |
| let result_micros = |
| ffi::get_time_delta_param(self.feature.into(), self.name, default_micros); |
| if result_micros <= 0 { |
| std::time::Duration::ZERO |
| } else { |
| std::time::Duration::from_micros(result_micros as u64) |
| } |
| } |
| } |
| |
| #[cxx::bridge(namespace = "base")] |
| // Public so other crates can use the Feature type in their own cxx bridges |
| #[doc(hidden)] |
| pub mod ffi { |
| unsafe extern "C++" { |
| include!("base/feature.h"); |
| include!("base/feature_list.h"); |
| include!("base/feature_rust_shim.h"); |
| type Feature; |
| |
| #[namespace = "base"] |
| type FeatureList; |
| |
| #[Self = "FeatureList"] |
| fn IsEnabled(feature: &Feature) -> bool; |
| |
| #[rust_name = "get_bool_param"] |
| fn GetFieldTrialParamByFeatureAsBoolShim( |
| feature: &Feature, |
| param_name: &str, |
| default_value: bool, |
| ) -> bool; |
| |
| #[rust_name = "get_int_param"] |
| fn GetFieldTrialParamByFeatureAsIntShim( |
| feature: &Feature, |
| param_name: &str, |
| default_value: i32, |
| ) -> i32; |
| |
| #[rust_name = "get_double_param"] |
| fn GetFieldTrialParamByFeatureAsDoubleShim( |
| feature: &Feature, |
| param_name: &str, |
| default_value: f64, |
| ) -> f64; |
| |
| #[rust_name = "get_string_param"] |
| fn GetFieldTrialParamByFeatureAsStringShim( |
| feature: &Feature, |
| param_name: &str, |
| default_value: &str, |
| ) -> String; |
| |
| #[rust_name = "get_time_delta_param"] |
| fn GetFieldTrialParamByFeatureAsTimeDeltaInMicrosecondsShim( |
| feature: &Feature, |
| param_name: &str, |
| default_value_micros: i64, |
| ) -> i64; |
| } |
| } |
| |
| /// The macro for defining base::Features in Rust is `base_feature!`. |
| /// |
| /// -`$id`is the Rust identifier that will be used for the Feature. |
| /// - `$default` is the default state to use for the feature. The options are |
| /// `feature::FeatureState::Disabled` or `feature::FeatureState::Enabled`. |
| /// |
| /// # Usage: |
| /// |
| /// use feature::{base_feature, FeatureState}; |
| /// |
| /// base_feature!(MyFeature, FeatureState::Disabled); |
| /// |
| /// if MyFeature.is_enabled() { |
| /// // ... |
| /// } |
| /// |
| /// Feature names are derived from the `$id` passed to the macro. |
| /// They should use CamelCase-style naming, e.g. "FooFeature". |
| /// |
| /// Feature names must be globally unique. |
| /// |
| /// (Note that features defined in C++ use the `k` prefix as an identifier, |
| /// meaning kFooFeature is the identifier in C++ code for what is processed |
| /// by Finch as FooFeature. Rust does not have this `k`-prefix requirement, |
| /// so a Feature declared in C++ with `kFooFeature` is referenced in Rust as |
| /// `FooFeature`, and both map to the same underlying Feature.) |
| #[macro_export] |
| macro_rules! base_feature { |
| // 2-argument version: Derive the name from the identifier. |
| ($id:ident, $default:expr) => { |
| #[unsafe(no_mangle)] |
| #[allow(non_upper_case_globals)] |
| #[cfg_attr( |
| any( |
| target_os = "android", |
| target_os = "linux", |
| target_os = "chromeos", |
| target_os = "fuchsia" |
| ), |
| unsafe(link_section = ".data..cr_features") |
| )] |
| pub static $id: $crate::Feature = unsafe { |
| // Safety: The string constructed here is explicitly null-terminated. |
| $crate::Feature::from_id( |
| concat!(stringify!($id), "\0"), |
| $default, |
| $crate::internal::FeatureMacroHandshake::Secret, |
| ) |
| }; |
| }; |
| } |
| |
| /// The macro for defining base::FeatureParams in Rust is `base_feature_param!`. |
| /// |
| /// - `$id` is the parameter's identifier. |
| /// - `$name` is the string name of the parameter in Finch configurations. |
| /// - `$type` is the type of the parameter (`bool`, `i32`, `f64`, `&'static |
| /// str`, `String`, or `TimeDelta` / `std::time::Duration`). |
| /// - `$feature` is a reference to the associated `Feature` (e.g. `&MyFeature`). |
| /// - `$default` is the default value to return when the parameter is not set. |
| /// |
| /// # Usage: |
| /// |
| /// ```rust |
| /// use feature::{base_feature, base_feature_param, FeatureState}; |
| /// |
| /// base_feature!(MyFeature, FeatureState::Disabled); |
| /// base_feature_param!(MyTimeoutParam, i32, &MyFeature, "timeout_ms", 100); |
| /// |
| /// if MyFeature.is_enabled() { |
| /// let timeout = MyTimeoutParam.get(); |
| /// } |
| /// ``` |
| #[macro_export] |
| macro_rules! base_feature_param { |
| ($id:ident, $type:ty, $feature:expr, $name:expr, $default:expr) => { |
| #[allow(non_upper_case_globals)] |
| pub static $id: $crate::FeatureParam<$type> = |
| $crate::FeatureParam::new($feature, $name, $default); |
| }; |
| } |