Implementing Feature Flags in Rust
Feature flags are a powerful technique in software development to enable or disable specific features in your application without redeploying code. This allows for gradual rollouts, A/B testing, and quick disabling of problematic features. Your task is to implement a basic feature flagging system in Rust.
Problem Description
You need to create a FeatureFlag enum and a FeatureManager struct that can manage the state of these feature flags. The FeatureManager should allow you to:
- Define a set of features: These features will be represented by variants of the
FeatureFlagenum. - Enable/Disable features: Programmatically change the state of individual feature flags.
- Check feature status: Determine if a given feature flag is currently enabled or disabled.
- Persist/Load configuration (optional but recommended): While not strictly required for the core challenge, a robust system would allow saving and loading flag states, perhaps from a file or environment variables. For this challenge, we'll focus on in-memory management.
Key Requirements:
- The
FeatureFlagenum should be extensible, meaning new features can be added by simply adding new variants. - The
FeatureManagershould maintain the state of eachFeatureFlag(enabled or disabled). - The
FeatureManagermust provide methods toenableanddisablespecific flags. - The
FeatureManagermust provide a method tois_enabledfor a given flag.
Expected Behavior:
When a feature is created, it should be disabled by default. Enabling a feature should make is_enabled return true for that feature. Disabling an enabled feature should make is_enabled return false.
Edge Cases:
- Attempting to enable or disable a feature that doesn't exist should be handled gracefully (e.g., by doing nothing or returning an error, though for simplicity, doing nothing is acceptable).
- Calling
is_enabledon a non-existent feature should also be handled gracefully (e.g., returningfalse).
Examples
Example 1:
// Initial setup
let mut manager = FeatureManager::new();
// Check a new feature
assert_eq!(manager.is_enabled(FeatureFlag::NewDashboard), false);
// Enable the feature
manager.enable(FeatureFlag::NewDashboard);
// Check again
assert_eq!(manager.is_enabled(FeatureFlag::NewDashboard), true);
// Disable the feature
manager.disable(FeatureFlag::NewDashboard);
// Check one last time
assert_eq!(manager.is_enabled(FeatureFlag::NewDashboard), false);
Explanation:
This example demonstrates the basic lifecycle of a feature flag: checking its default state (disabled), enabling it, checking again, disabling it, and checking one last time.
Example 2:
let mut manager = FeatureManager::new();
// Feature does not exist in our initial definition, but we can still try to check
assert_eq!(manager.is_enabled(FeatureFlag::ExperimentalSearch), false);
// Trying to enable/disable a feature not explicitly managed yet
manager.enable(FeatureFlag::ExperimentalSearch);
assert_eq!(manager.is_enabled(FeatureFlag::ExperimentalSearch), false); // Still false as it wasn't 'registered' in a way the manager knows about yet.
// Now, let's say we add FeatureFlag::ExperimentalSearch to our enum definition.
// If the FeatureManager was initialized to manage all known flags, this would work.
// For this exercise, we assume the manager internally tracks flags it's been asked to manipulate.
// A more advanced approach would be to explicitly register flags.
Explanation:
This example highlights how the system should handle features that might not have been explicitly configured or are not part of the initial set of flags managed by the FeatureManager. The default behavior should be that unknown flags are considered disabled.
Constraints
- The
FeatureFlagenum can have any number of variants. - The
FeatureManagershould use standard Rust data structures (e.g.,HashMap) for efficient state management. - The implementation should be thread-safe if concurrent access is a concern (though for this basic challenge, it's not a strict requirement unless specified). We'll aim for a simple, non-concurrent implementation first.
Notes
Consider how you will store the state of each feature flag. A HashMap mapping FeatureFlag to a boolean (true for enabled, false for disabled) is a common and efficient approach. Remember that enums in Rust can be used as keys in a HashMap if they derive the Eq and Hash traits.
Think about how the FeatureManager will know which features could potentially exist. You might want to initialize the FeatureManager with a known set of flags, or have it dynamically learn about flags as they are enabled/disabled. The latter is simpler for this challenge.