Migrating the Rust SDK for Featurevisor v4
- Minimal migration path
- Breaking changes
- Root condition reasonBreaking
- Datafile update resultBreaking
- Module callbacksBreaking
- Sticky state in module optionsBreaking
- Evaluation details and requirement typesBreaking
- Language differences
- OpenFeature
- Migration checklist
Upgrade Rust applications to SDK 2.0.0 for Featurevisor v4 support.
Follow the shared migration order: prepare canonical prerequisite datafiles, upgrade consumers, then upgrade the project CLI. Before publishing new runtime fields, follow Adopt new syntax safely.
Convert Groups before upgrading
This SDK silently ignores legacy feature ranges, with no compatibility handling or warning. Convert Groups to flat list requirements while still on the previous compatible SDK and v3 CLI, then publish all datafiles, caches and bundled copies before upgrading. Unconverted files can change cohorts or enable features without a variation. Old cohorts are not guaranteed. Follow Groups removed.
Minimal migration path#
Most Rust application calls do not change. create_featurevisor, is_enabled, get_variation, feature variable getters, global variable getters, child instances, sticky configuration, sticky setters, and the specific sticky events were already available in the previous SDK line.
If your modules already use before_evaluation and after_evaluation, and your code does not inspect the renamed evaluation or requirement types, the source migration can be small:
- Follow the shared migration order and prepare legacy prerequisite datafiles first.
- Upgrade the Rust crate to 2.0.0.
- Update the removed APIs below, then deploy every Rust consumer.
- Enable each new datafile capability only after every consumer of that datafile supports it.
For staggered deployments, follow mixed version deployments. V4 builds emit schemaVersion: "4", so the schema number does not advertise these new runtime capabilities.
Breaking changes#
Root condition reason Breaking#
An unmet root gate reports conditions_unmet for feature flags and global variables, replacing the v3 required reason for feature flags and required_features_unmet for global variables. This applies to attribute, segment, feature and variable leaves in root conditions. Update callbacks, analytics and test expectations. Variation and feature variable evaluations retain their disabled reasons. See the shared migration guidance.
let unmet_requirement = evaluation.reason == EvaluationReason::Required;let unmet_requirement = evaluation.reason == EvaluationReason::ConditionsUnmet;Datafile update result Breaking#
set_datafile now returns bool instead of (): true for accepted updates, including unchanged content, and false for rejected updates or closed instances. The previous valid datafile remains available after rejection. Existing statements with a semicolon can ignore the result. Add a semicolon to a setter call used as the final expression of a function or closure that must return ().
let update = || f.set_datafile(datafile, false);let () = update();let update = || { f.set_datafile(datafile, false);};let () = update();Merging remains the default behaviour of the example's false replacement argument. Use the returned boolean when the caller needs to handle rejection.
Module callbacks Breaking#
The previous SDK trait contained both callback families. After migration, the SDK removes before and after. Modules that already implement the canonical before_evaluation and after_evaluation methods need no rename.
impl FeaturevisorModule for AuditModule { fn before(&self, mut options: EvaluateOptions) -> EvaluateOptions { options.context.insert("country".into(), "nl".into()); options } fn after(&self, evaluation: Evaluation, _: &EvaluateOptions) -> Evaluation { evaluation }}impl FeaturevisorModule for AuditModule { fn before_evaluation(&self, mut options: EvaluateOptions) -> EvaluateOptions { options.context.insert("country".into(), "nl".into()); options } fn after_evaluation(&self, evaluation: Evaluation, _: &EvaluateOptions) -> Evaluation { evaluation }}The canonical callbacks run for feature and global variable evaluations, including prerequisite evaluations. If the old callback was intended only for features, check options.feature_key before applying its transformation. Accumulating override steps remain one evaluation and do not invoke the callbacks separately.
See Rust SDK modules.
Sticky state in module options Breaking#
The public EvaluateOptions.sticky field is removed. Sticky state is instance or child state, not a per evaluation module override. Configure it through APIs that were already available in the previous SDK line:
fn before(&self, mut options: EvaluateOptions) -> EvaluateOptions { options.sticky = Some(sticky_features()); options}let f = create_featurevisor(FeaturevisorOptions { sticky_features: Some(sticky_features()), ..Default::default()});f.set_sticky_features(next_features, true);Use sticky_variables and set_sticky_variables for global variables. SpawnOptions continues to accept separate sticky_features and sticky_variables. A child without explicit sticky values starts with isolated empty sticky state rather than inheriting the parent's sticky maps.
Evaluation details and requirement types Breaking#
Evaluation.sticky becomes Evaluation.sticky_feature. The failed root gate expression is now Evaluation.conditions, replacing separate prerequisite metadata:
let requirements: Option<Vec<Required>> = evaluation.required_features;let sticky = evaluation.sticky;let conditions = evaluation.conditions;let sticky = evaluation.sticky_feature;Detailed accumulated evaluations also expose variable_override_paths, containing every matched override path in execution order.
Applications that construct DatafileContent directly must replace Required with RequiredFeature. The legacy Required::LegacyVariation { key, variation } variant is removed. Use the canonical object shape represented by RequiredFeature::Details:
let required = Required::LegacyVariation { key: "checkout".into(), variation: "treatment".into(),};let required = RequiredFeature::Details { feature: "checkout".into(), enabled: None, variation: Some("treatment".into()),};Rebuild raw legacy { "key": ..., "variation": ... } entries as { "feature": ..., "variation": ... } before upgrading. Schema required lists inside object variable schemas are unrelated and remain supported.
Language differences#
Rust modules must use instance or child sticky state rather than changing sticky state through evaluation options. See Sticky state in module options.
OpenFeature #
The provider is a separate featurevisor-openfeature crate. Upgrade it and the featurevisor core crate to 2.0.0 together.
Provider key grammar and normal resolution calls remain unchanged. A Featurevisor result with reason conditions_unmet maps to the OpenFeature disabled reason and retains conditions_unmet in Featurevisor metadata. Test guarded flags and global variables through the OpenFeature client before publishing variable requirements.
Test explicit empty and omitted targeting keys after client context merging. An empty key must not be mistaken for an absent key when overriding an existing targeting field.
See Rust SDK OpenFeature integration and the OpenFeature provider guide.
Migration checklist#
Required for the migration#
- Prepare and deploy canonical prerequisite datafiles before upgrading the runtime.
- Replace module
beforeandaftermethods when used. - Move module changes to
EvaluateOptions.stickyinto instance or child sticky state. - Replace
Evaluation.stickyand the removedRequiredtype when used. - Keep ordinary getters, sticky configuration, setters, and specific sticky events unchanged.
- Upgrade the separate OpenFeature provider together with its compatible core crate.
- Review mutation order and date and regex comparisons before rebuilding, and keep a prepared v3 datafile for rollback.
Only if adopting optional features#
Follow Adopt new syntax safely before publishing new datafile syntax. Upgrade every CLI runner before adding checks or scenarios.

