Skip to content

feat: Enable Flag Dependency in the Rust flag engine - #42

Open
linear-code[bot] wants to merge 7 commits into
mainfrom
linear/sdk-11-enable-flag-dependency-in-the-rust-flag-engine-5ab4
Open

linear-code[bot] wants to merge 7 commits into
mainfrom
linear/sdk-11-enable-flag-dependency-in-the-rust-flag-engine-5ab4

Conversation

@linear-code

@linear-code linear-code Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Flags can't depend on other flags in the Rust engine yet, so the Rust SDK can't evaluate Flag Dependency locally. This ports flagsmith-engine #343 and brings in two smaller engine changes that the current engine-test-data (v3.12.1) also expects. Each change is a separate commit.

Changes

  • Segment conditions can read another flag's enabled, value or variant through $.flags.<feature>…, including chains of dependencies.
  • Flags caught in a dependency cycle return their environment default with reason ERROR; code=CIRCULAR_DEPENDENCY, and segments that run into a cycle don't match.
  • Flag results report which multivariate variant was selected (test data v3.10.0).
  • Flags left at their default report STATIC when no segment targets them and DEFAULT when segments target them but none matched, as in flagsmith-engine #347.
  • All 220 engine-test-data v3.12.1 cases pass.
  • Evaluations whose segments read JSONPath properties are faster than before, with or without flag dependencies.
  • Evaluations that read no JSONPath property are still slower than before in some workloads (see Performance).
  • Release 0.7.0 (a human does this through release-please).

For reviewers:

  • Only paths that pick out a single flag by name count as dependencies, e.g. $.flags.a.enabled or $.flags['my feature'].enabled. Wildcards and filters read no flag. engine-test-data doesn't pin this case. serde_json_path (0.7.2, the latest) keeps its parsed AST private, so the flag name is pulled from the path text after JsonPath::parse has validated it.
  • The engine test now compares matched segments without regard to order. The context stores segments in a HashMap, and the result schema defines no order.
  • FlagResult gains a public variant field and FeatureValue gains a public key field. Code that builds these structs field by field needs updating, which is why this ships as a minor release. flagsmith-rust-client builds and passes its tests against this branch with no changes.

Performance

benches/engine.rs (criterion 0.8) measures:

  • representative: the five engine-test-data contexts that flagsmith-engine benchmarks.
  • corpus_without_flag_dependencies: the other v3.12.1 contexts (202 of them). The 18 contexts that read $.flags are left out because the baseline evaluates them differently.
  • large_environment/*: a synthetic environment with 200 features (20 multivariate) and 50 segments, plus an identity with 20 traits. The flag_chain variant adds a 49-step dependency chain and flag_fan_out has 50 segments reading five flags. The baseline can't resolve these dependencies, so for them it measures the same contexts without the dependency work.

Baseline is main at ba209c6, with the same bench file and the same v3.12.1 test data. Median times, from interleaved criterion runs on a 1 vCPU AMD EPYC 9655P VM with Rust 1.99.1:

Benchmark main this PR change
representative 39.4 µs 26.2 µs −34%
corpus_without_flag_dependencies 2.14 ms 2.35 ms +10%
large_environment/environment 39.3 µs 50.4 µs +28%
large_environment/identity 4.91 ms 0.43 ms −91%
large_environment/identity_with_flag_chain 8.71 ms 0.54 ms −94%
large_environment/identity_with_flag_fan_out 8.87 ms 0.51 ms −94%

Before the perf: commit, flag dependency made every workload slower except fan-out (representative +23%, identity +20%, environment +51%). That commit serializes the context to JSON once per evaluation instead of once per JSONPath condition, which was the dominant cost on main too. It also builds the dependency index only once a condition reads a flag.

Limitations:

  • large_environment/environment (+28%) reads no JSONPath property. Its extra cost is recording overrides of unmatched segments, which the STATIC/DEFAULT reasons from #347 need. Removing that recording puts it 11% faster than main, but the reasons are then wrong. With foldhash hashing for that one map it measured +15%. I left that out because it would add a runtime dependency.
  • On this branch corpus_without_flag_dependencies measured between 2.05 and 2.35 ms across runs, while main stayed between 2.11 and 2.14 ms. I haven't found the cause.
  • Before the perf: commit, about 0.7 ms of the identity regression couldn't be explained: both trees serialized the context the same number of times, at the same cost per call. No profiler was available.
  • All numbers come from one shared VM.

To reproduce:

git worktree add ../baseline ba209c6
cp -r benches Cargo.toml ../baseline/   # Cargo.toml only adds criterion and the [[bench]] entry
rmdir ../baseline/tests/engine_tests/engine-test-data
ln -s "$PWD/tests/engine_tests/engine-test-data" ../baseline/tests/engine_tests/engine-test-data
(cd ../baseline && CRITERION_HOME=/tmp/criterion cargo bench --bench engine -- --save-baseline main)
CRITERION_HOME=/tmp/criterion cargo bench --bench engine -- --baseline main

Contributes to SDK-11

Review effort: 4/5

linear-code Bot added 4 commits October 21, 2026 17:40
Bumps engine-test-data to v3.10.0.

Generated with [Linear](https://linear.app/flagsmith/agent-session/32b03921-b061-45c1-8951-e6bc08ade1a8)

Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
Segment conditions can read $.flags.<feature>.enabled|value|variant.
Flags in a dependency cycle get reason ERROR; code=CIRCULAR_DEPENDENCY.
Bumps engine-test-data to v3.11.0.

Generated with [Linear](https://linear.app/flagsmith/agent-session/32b03921-b061-45c1-8951-e6bc08ade1a8)

Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
STATIC if no segment overrides the feature, DEFAULT if overrides exist but none matched.
Bumps engine-test-data to v3.12.1.

Generated with [Linear](https://linear.app/flagsmith/agent-session/32b03921-b061-45c1-8951-e6bc08ade1a8)

Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
RFC 9535 allows blank space before segments and inside bracketed selections.

Generated with [Linear](https://linear.app/flagsmith/agent-session/32b03921-b061-45c1-8951-e6bc08ade1a8)

Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
emyller and others added 3 commits October 21, 2026 18:22
Covers the engine-test-data contexts flagsmith-engine benchmarks, the rest of the
corpus, and a large environment with and without flag dependencies.

Generated with [Linear](https://linear.app/flagsmith/agent-session/32b03921-b061-45c1-8951-e6bc08ade1a8)

Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
JSONPath conditions read one serialisation of the context instead of each making
their own, and evaluations reading no flag skip building the dependency index.

Generated with [Linear](https://linear.app/flagsmith/agent-session/32b03921-b061-45c1-8951-e6bc08ade1a8)

Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
Generated with [Linear](https://linear.app/flagsmith/agent-session/32b03921-b061-45c1-8951-e6bc08ade1a8)

Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant