Skip to content

Reference

Rust SDK

Add the fluxgate crate to evaluate flags from async Rust, keep a user's flags in memory with a snapshot, or use the blocking client.

Before you start

  • Rust 1.85 or newer. The async client runs on Tokio.
  • The edge server is running and express-checkout is deployed to Development. See Clients and edge server.
  • The SDK key of a Backend client, in the story checkout-service, for example in the FLUXGATE_SDK_KEY environment variable.

Crate

CrateLinks
fluxgatecrates.io, API docs on docs.rs, source

The current version is 1.0.0. Optional parts are cargo features:

FeatureDefaultAdds
rustlsyesTLS through rustls.
native-tlsnoTLS through the operating system's library. Use it with default-features = false.
blockingnofluxgate::blocking, a synchronous client for code without an async runtime.
openfeaturenofluxgate::openfeature::FluxGateProvider, a provider for the open-feature crate.

Evaluate a flag

Cargo.toml
[dependencies]
fluxgate = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
src/main.rs
use fluxgate::{EvaluationContext, FluxGateClient};

#[tokio::main]
async fn main() -> Result<(), fluxgate::ConfigError> {
    let fluxgate = FluxGateClient::new(
        "http://localhost:8081",
        &std::env::var("FLUXGATE_SDK_KEY").expect("set FLUXGATE_SDK_KEY"), // <clientId>.<apiKey>
    )?;

    let user = EvaluationContext::new("user-1")
        .with("country", "CA")
        .with("user_tier", "plus");

    let variant = fluxgate.get_string("express-checkout", "classic", &user).await;
    println!("checkout: {variant}");

    let details = fluxgate.evaluate("express-checkout", "classic".to_owned(), &user).await;
    println!("{:?} {} {:?}", details.variant, details.reason, details.error_code);
    Ok(())
}
Output
checkout: express
Some("express") TARGETING_MATCH None

FluxGateClient::new fails only on a malformed base URL or SDK key. The client is cheap to clone, and clones share the connection pool and the cache, so create one and pass it around.

MethodReturns
is_enabled(key, &ctx)true only when a boolean flag is on
get_bool, get_string, get_i64, get_f64, get_value (JSON)The value, or your default
evaluate(key, default, &ctx)FlagEvaluation<T>: value, variant, reason, error_code, error_message, metadata
evaluate_all(&ctx, version)Every flag in one call, as Result<BulkEvaluation, Error>
watch(ctx)A FlagSnapshot with every flag for one context, polled every 30 seconds
is_healthy()true when the edge answers GET /health with 2xx

To change the defaults, use the builder:

Custom settings
let fluxgate = FluxGateClient::builder("http://localhost:8081", sdk_key)
    .timeout(Duration::from_secs(2))          // default 5 s
    .max_attempts(2)                          // default 3
    .initial_backoff(Duration::from_millis(50)) // default 100 ms
    .cache_ttl(Duration::from_secs(10))       // default 30 s
    .build()?;

Keep a user's flags in memory

A FlagSnapshot loads every flag for one context in one bulk call and polls with If-None-Match. Reads are synchronous and make no network call, which suits code that reads many flags for the same user:

Snapshot
let flags = fluxgate.watch(EvaluationContext::new("user-1").with("country", "CA"));
flags.ready().await;

let checkout = flags.get_string("express-checkout", "classic");
let banner = flags.is_enabled("holiday-banner");

// Switch to another user, for example after sign-in.
flags.set_context(EvaluationContext::new("user-2").with("country", "US")).await;

flags.subscribe() returns a tokio::sync::watch::Receiver that changes whenever a flag or the status changes. Polling stops when the last clone of the snapshot is dropped, or when you call close(). Use watch_with_interval to poll at another interval; Duration::ZERO turns polling off.

Without an async runtime

Cargo.toml
[dependencies]
fluxgate = { version = "1", features = ["blocking"] }
src/main.rs
use fluxgate::EvaluationContext;
use fluxgate::blocking::FluxGateClient;

fn main() {
    let fluxgate = FluxGateClient::new("http://localhost:8081", &std::env::var("FLUXGATE_SDK_KEY").unwrap()).unwrap();
    let user = EvaluationContext::new("user-1").with("country", "CA").with("user_tier", "plus");
    println!("checkout: {}", fluxgate.get_string("express-checkout", "classic", &user));
}

The blocking client runs the async client on its own runtime with one worker thread, which also polls snapshots. Do not call it from inside an async runtime: Tokio panics on blocking calls there.

OpenFeature

Cargo.toml
[dependencies]
fluxgate = { version = "1", features = ["openfeature"] }
open-feature = "0.3"
OpenFeature setup
use fluxgate::openfeature::FluxGateProvider;
use open_feature::{EvaluationContext, OpenFeature};

let mut api = OpenFeature::singleton_mut().await;
api.set_provider(FluxGateProvider::new(fluxgate)).await;
let client = api.create_client();
drop(api);

let user = EvaluationContext::default()
    .with_targeting_key("user-1")
    .with_custom_field("country", "CA")
    .with_custom_field("user_tier", "plus");
let variant = client
    .get_string_value("express-checkout", Some(&user), None)
    .await
    .unwrap_or_else(|_| "classic".to_owned());

Date-time context fields are sent as RFC 3339 strings. OpenFeature providers in Rust receive no default value, so a flag that is off resolves to an error for every type except bool, and OpenFeature returns your default.

Good to know

  • Evaluations never fail. error_code is one of FlagNotFound, TypeMismatch, TargetingKeyMissing, InvalidContext, ProviderNotReady, ParseError, Unauthorized and General.
  • The default sets the expected type: bool, String, i64, f64 or serde_json::Value. Value::Null accepts any flag. An i64 read accepts only integers; an f64 read accepts any number.
  • A flag that is off returns your default, with the edge's reason and no error.
  • Debug output of the client and its builder never includes the SDK key.

Try the samples

The repository has a CLI sample, a blocking sample and an axum web service. samples/docker-compose.yml starts a separate FluxGate stack on ports 38000 (admin UI), 38080 (backend) and 38081 (edge), and samples/setup.sh creates a Backend client, a boolean flag and a contextual flag. Follow samples/README.md.

Next

For the other languages, see Java and Spring Boot and TypeScript, React and Angular. The HTTP calls behind every SDK are in the Edge server API.