- Executive Summary
- Part A — Where Rust has advantages over Go and C# (for platform code)
- A.1 Errors are part of the type system
- A.2 Memory data races are a compile error, not a
-raceflag - A.3 No
nilinterfaces, no exceptions-from-anywhere - A.4 Sum types make illegal states unrepresentable
- A.5 Exhaustive
matchmakes state evolution safer - A.6 Zero-cost abstractions and predictable performance
- A.7 Tooling and static analysis as a first-class citizen
- A.8 Newtypes make identity mix-ups a compile error
- A.9 RAII, lifetimes, and scoped resources
- A.10 Traits and generics: zero-cost polymorphism
- A.11 Macros move framework rules into compile time
- Part B — Why "just Rust" is not enough: what Gears adds
- B.1 A pre-integrated configurable XaaS backbone
- B.2 Spec-driven development with Studio
- B.3 Tenant isolation by default
- B.4 Authentication & authorization, built in (NIST SP 800-162 PDP/PEP)
- B.5 Prewritten architecture lints (
dylint) - B.6 Runtime Gears capabilities
- B.7 One consistent API dialect:
OperationBuilder+ OpenAPI + OData - B.8 Composable gears: one codebase, many deployment shapes
- B.9 Extensible domain model via the Global Type System (GTS)
- B.10 Canonical errors
- B.11 Observability and operational defaults
- B.12 FIPS 140-3 support
- B.13 Supply-chain policy as code
- B.14 Preconfigured build-gated safety
- B.15 Local-first, shift-left development
- When Gears is (and isn't) the right choice
- Get started
A guide for Go developers (and C# developers) evaluating Constructor Fabric Gears — a secure, modular XaaS development framework & middleware written in Rust.
Public links
- Gears (Rust) monorepo — https://github.com/constructorfabric/gears-rust
- Architecture Manifest —
docs/ARCHITECTURE_MANIFEST.md - Overview slides —
docs/slides/1_OVERVIEW.md - Gears inventory —
docs/GEARS.md - Toolkit guide —
docs/toolkit_unified_system/README.md - Global Type System (GTS) — https://github.com/globaltypesystem/gts-spec
- Constructor Fabric Foundation — https://www.constructorfabric.org
If you build multi-tenant XaaS / SaaS backends, you are repeatedly solving the same problems in every service: tenant isolation, authentication/authorization, licensing & quota, usage metering, consistent REST APIs, pagination/filtering, observability, and safe DB access. Popular programming languages like Go and C# can solve these problems well, often with excellent libraries, analyzers, and mature team conventions.
Gears combines many of these best practices into one structured Rust middleware: shared platform contracts, reusable libraries ("gears"), consistent API patterns, and build-gated checks that make the preferred path explicit. Those checks help both human developers and AI-generated code follow established patterns, backed by compile-time guarantees and validation.
Gears takes a different position, in two layers:
-
Rust as the language moves some important classes of bugs — memory data races, use-after-free, null dereferences, many unhandled errors — from runtime incidents to compile errors or explicit types.
-
Gears as the middleware provides the platform layer around Rust: tenant-scoped data access, authentication/authorization contracts, consistent API construction, composable business capabilities, local-first testing, deployment options, supply-chain policy, and additional build-gated safety checks.
The result is a solid technology stack for long-living, comprehensive XaaS systems: product teams get a common foundation for security, tenancy, APIs, observability, lifecycle, and deployment instead of assembling those pieces differently in every service. This structure is especially useful for AI-driven development: coding agents work better when correctness rules are not only written in prose, but also expressed as types, generated schemas, lints, tests, and CI checks that provide deterministic feedback.
For a long-living XaaS platform, Gears can provide advantages that are less visible in small standalone services: shared runtime capabilities, stronger static guarantees, consistent APIs, and platform rules enforced by the build.
A similar middleware could exist or be built for Go or C#, and mature teams often build parts of it successfully. However, this investment would still not close every Rust-specific advantage: ownership-based memory safety without a GC, Send/Sync data-race checks, exhaustive enums, zero-cost newtypes, and macro-generated code that remains type-checked by the compiler.
| # | Concern | Go (typical) | C# / .NET | Rust (plain) | Rust + Gears |
|---|---|---|---|---|---|
| 1 | Runtime footprint | small binary, GC pauses | larger runtime, GC | small, no GC | small, no GC |
| 2 | Deployment shapes | per-service choices | per-service | per-service | One code → edge / bare-metal / K8s |
| 3 | Memory & data-race safety | GC; memory races possible, detected only at runtime (-race) |
GC; memory races possible | Compile-time ownership & Send/Sync |
Compile-time, same as Rust |
| 4 | Error handling | if err != nil, lint-gated in mature shops |
exceptions / analyzer policy | Result<T, E>, ? — must handle |
Result + canonical error taxonomy (RFC-9457) |
| 5 | Null safety | nil panics |
NRT helps, policy-dependent | Option<T> — no null |
Option<T> everywhere |
| 6 | Panics / unsafe shortcuts | runtime panics possible | runtime exceptions possible | possible, but lintable | unwrap, panic, unsafe patterns prohibited at build time |
| 7 | State evolution | switch may miss new constants |
switch often needs analyzer support |
exhaustive match over enums |
exhaustive match + architecture lints |
| 8 | ID / domain mix-ups | named types catch swaps, but literals/conversions remain easy; opaque structs add boilerplate | record structs / value objects work, but require conventions and serializers | zero-cost newtypes; no implicit conversion; private fields by default | tenant/user/resource IDs can be distinct validated types |
| 9 | Scoped resource cleanup | defer discipline |
using / IDisposable discipline |
Drop + lifetimes |
scoped transactions, guards, spans, connections |
| 10 | Polymorphism | interfaces + newer generics; less expressive type relationships | rich generics + interfaces; runtime framework costs vary | traits, associated types, monomorphization | typed SDKs and ClientHub across transports |
| 11 | Compile-time code generation | generators / reflection / tags | source generators / attributes / reflection | procedural and declarative macros expand into type-checked Rust | derives and builders generate schemas, scopes, APIs, route metadata, and validation glue |
| 12 | Tenant isolation | manual WHERE tenant_id = ? |
manual / EF global filters | manual | Standardized via SecureConn + AccessScope |
| 13 | AuthN / AuthZ | per-service middleware, bespoke | ASP.NET policies | bespoke | Built-in PDP/PEP (NIST SP 800-162) |
| 14 | API consistency | per-team router conventions | attributes + filters | bespoke | OperationBuilder → uniform REST + OpenAPI |
| 15 | Pagination / filtering | hand-rolled | OData libs | hand-rolled | Built-in OData $filter/$select/$orderby |
| 16 | Architecture policy | go vet / golangci-lint / custom checks |
Roslyn analyzers / custom checks | Clippy / custom lints | Prewritten Clippy + dylint rules for Gears conventions |
| 17 | Multi-tenancy / licensing / quota / usage | build it yourself | build it yourself | build it yourself | Pre-integrated, replaceable gears |
| 18 | Extensible API domain data types | manual | manual | manual | GTS — versioned, schema-validated, autogenerated JSON schemas from Rust code |
Those benefits are not free. Rust and framework-heavy stacks have real adoption costs; Gears tries to make them visible, document where they matter, and reduce them with structure, tooling, templates, and automation rather than hiding them.
| # | Concern | Go (typical) | C# / .NET | Rust (plain) | Rust + Gears |
|---|---|---|---|---|---|
| 1 | Learning curve | low; deliberately small language | moderate; familiar OO + large docs | high; ownership, lifetimes, async, traits | high plus framework concepts (*) |
| 2 | Code readability for newcomers | usually straightforward | usually familiar to enterprise teams | can be dense; types/macros/async add load | can be denser because framework types encode policy (**) |
| 3 | Compile speed / iteration | usually fast | usually good; tooling mature | often slower, especially large workspaces | slower again when full lint/test gates run |
| 4 | Ecosystem breadth | very strong for cloud/network services | very strong enterprise ecosystem | strong systems/backend ecosystem, thinner in some areas but evolving | inherits Rust tooling |
(*) Gears middleware consists of three large layers:
- Toolkit — reusable Rust libraries for API construction, data access, auth integration, observability, transport, testing, and other cross-cutting platform concerns.
- System Gears — platform-level capabilities such as events, tenant and authentication resolvers, type registries, serverless/runtime support, and other shared XaaS services.
- Domain Gears — product/business capabilities such as chat, credential storage, file parsing, approval flows, and other feature-level libraries.
Gears Toolkit internals can be complex because they encode reusable XaaS infrastructure. Individual Gear libraries are usually simpler: they model a concrete business capability inside established platform boundaries, with tenancy, API wiring, validation, and safety checks already provided by the stack.
(**) AI-assisted development changes the readability trade-off. Lints, generators, templates, examples, and deterministic validation help agents produce new Gear code faster and with fewer convention mistakes. Human reviewers still need to understand the code, but the generated result is usually ordinary business logic inside a familiar structure rather than a new service architecture invented from scratch.
TL;DR for Go devs: You keep some operational properties Go teams often value — single binaries, fast startup, low runtime footprint, and explicit control flow. You do not keep Go's simplicity or ecosystem breadth. Gears is a trade: more language and framework complexity in exchange for stronger static checks and a pre-integrated XaaS backbone.
This section is about the language. Gears is built on Rust specifically because it targets long-lived platform code where correctness and maintainability matter more than raw time-to-first-prototype.
In Go, error handling is explicit and simple; serious teams usually add errcheck / golangci-lint to prevent accidentally discarded errors. Rust makes that stricter by putting fallibility in the type signature and making propagation (?) explicit in ordinary language flow.
// Go — compiles unless your lint gate rejects the ignored error.
func loadUser(id string) *User {
u, _ := db.FindUser(id) // ignored error; u may be nil
return u
}
caller := loadUser("42")
fmt.Println(caller.Name) // nil pointer dereference at runtimeIn Rust, the error is part of the type. You must deal with it, and there is no nil.
// Rust — you cannot accidentally ignore the error or deref a null.
fn load_user(id: &str) -> Result<User, RepoError> {
let user = db.find_user(id)?; // `?` propagates the error explicitly
Ok(user)
}
match load_user("42") {
Ok(user) => println!("{}", user.name),
Err(e) => tracing::warn!(error = %e, "user not found"),
}Option<T> replaces nil, and Result<T, E> makes "this can fail" visible in the signature. Whole categories of nil panics and swallowed errors become compile-time or lint-gated failures instead of review conventions.
Go's race detector is excellent — but it only finds races on code paths you actually execute under instrumentation. Races ship to production all the time.
// Go — compiles, runs, and corrupts the map under load. No compile error.
counts := map[string]int{}
for _, ev := range events {
go func(e Event) {
counts[e.Key]++ // concurrent map write -> runtime panic / corruption
}(ev)
}Rust's ownership model and the Send/Sync traits make unsynchronized shared mutable memory across threads a compile error. You're forced to use a proper synchronization primitive.
// Rust — won't compile unless the shared state is actually thread-safe.
use std::sync::{Arc, Mutex};
let counts = Arc::new(Mutex::new(HashMap::<String, i64>::new()));
let mut handles = vec![];
for ev in events {
let counts = Arc::clone(&counts);
handles.push(std::thread::spawn(move || {
*counts.lock().unwrap().entry(ev.key).or_insert(0) += 1;
}));
}"If it compiles, it's free of memory data races" is not a slogan — it's enforced by the borrow checker. This does not eliminate logical races such as TOCTOU bugs, lost updates, deadlocks, or bad transaction boundaries; those still need design, tests, and database constraints. For a platform handling concurrent multi-tenant traffic, Rust shifts an important class of concurrency defects from runtime testing into compilation.
C# gives you a rich runtime, but exceptions are invisible in signatures — any call can throw, and NullReferenceException remains the most common production failure. Go's nil interface trap (err != nil being true for a typed-nil) catches even experienced developers.
Rust has neither. Fallibility (Result) and absence (Option) are explicit in every signature, and exhaustive match means adding a new variant forces you to handle it everywhere.
Gears goes further than plain Rust style advice: nil-like failure paths and unsafe shortcuts are blocked by the build. Project policy treats unwrap, avoidable panic, unchecked assumptions, and unsafe patterns as architecture violations, not personal taste. In Go or C#, teams can enforce similar rules with analyzers, linters, and review policy; in Gears, Clippy plus project-specific lints make these checks part of the standard build gate.
Modeling a state machine in Go usually means a struct with a bunch of optional fields and a comment explaining which combinations are "valid."
// Go — nothing stops you from setting ErrorMsg while Status == "running".
type Job struct {
Status string // "pending" | "running" | "done" | "failed" (by convention)
Result *Output
ErrorMsg string
}Rust enums carry data per-variant, so invalid combinations can't be constructed:
// Rust — the compiler guarantees a failed job has an error and no result.
enum Job {
Pending,
Running { started_at: Instant },
Done { result: Output },
Failed { error: String },
}In Go or C#, if you add a new status, old switch statements may keep compiling unless analyzers or strict review rules require every call site to be revisited:
// Go — adding StatusCanceled later does not force every switch to be updated.
switch job.Status {
case StatusPending:
queue(job)
case StatusRunning:
observe(job)
case StatusDone:
archive(job)
}In Rust, matching an enum is exhaustive by default. If you later add Cancelled, every match that forgot it fails to compile until you decide what the new state means:
// Rust — adding Job::Cancelled forces this match to be updated.
match job {
Job::Pending => queue(job),
Job::Running { started_at } => observe(started_at),
Job::Done { result } => archive(result),
Job::Failed { error } => report(error),
}The same pattern appears everywhere: Option forces you to handle absence, Result forces you to handle failure, and enums force you to handle new states. This is exactly the kind of language support you want when platform APIs and workflows evolve over years.
No garbage collector means no GC pauses and a small, predictable memory footprint — which is exactly what you want for edge/on-prem appliances and for running the full platform locally during development. You get C-like performance with high-level ergonomics, and the abstractions compile away.
cargo, rustfmt, clippy, and cargo-deny give a consistent toolchain. Crucially, Rust's lint infrastructure is extensible — which is the hook Gears uses to enforce architecture at compile time (see Part B).
Most backends have IDs like UserId, TenantId, OrderId. Even when they're all strings or UUIDs underneath, they should not be interchangeable — passing a UserId where a TenantId is expected is a bug the compiler should catch.
Go — named types help, but are not a full validation boundary
type TenantID string
type UserID string
func LoadTenant(id TenantID) {}
var u UserID = "user_123"
LoadTenant(u) // compile error — good
LoadTenant("tenant_123") // ALLOWED — untyped string literal converts implicitly
id := TenantID("anything") // ALLOWED — explicit conversion from any stringEven in this case two escape hatches remain: literals convert implicitly, and any caller can construct TenantID(x) from any string with no validation. To close them you have to fall back to a struct with an unexported field:
type TenantID struct{ value string }
func NewTenantID(v string) (TenantID, error) {
if v == "" { return TenantID{}, errors.New("empty tenant id") }
return TenantID{value: v}, nil
}This works, but there is a cost:
- JSON / DB marshaling must be hand-written (
MarshalJSON,Scan,Value) per type, because the field is unexported. - Zero value is silently invalid —
var t TenantIDproducesTenantID{""}that bypassed the constructor. - Same-package code can still write
TenantID{value: x}directly, bypassing validation. - The type is no longer interchangeable with
stringin generics, reflection, or struct tags.
C# — similar story, with strong value-object options
Plain primitives have the same swap problem as Go. A common fix is a readonly record struct or another value-object pattern:
public readonly record struct TenantId(Guid Value);
public readonly record struct UserId(Guid Value);
void LoadTenant(TenantId id) { }
LoadTenant(new TenantId(userId.Value)); // explicit, distinct typeRust — the validated boundary is the default
pub struct TenantId(String);
pub struct UserId(String);
fn load_tenant(id: TenantId) {}
load_tenant(user_id); // compile error — distinct types
load_tenant(String::from("x")); // compile error — no implicit conversion
load_tenant("tenant_123"); // compile error — &str is not TenantIdThe inner field is private by module unless marked pub, so TenantId(x) is only constructible inside the defining module — no extra ceremony required. Validation lives in one place:
impl TenantId {
pub fn new(v: String) -> Result<Self, IdError> {
if v.is_empty() { return Err(IdError::Empty); }
Ok(Self(v))
}
}Serialization, hashing, equality, ordering, and many schema/DB integrations can usually be derived or implemented once through standard traits instead of hand-written ad hoc codecs per call site. Newtypes compile to the same machine code as the inner type, so the type separation is free at runtime.
Rust also uses types to model resource lifetime. Values are dropped deterministically when they leave scope, so transactions, mutex guards, file handles, tracing spans, and pooled connections can release themselves through Drop even when a function returns early with ?.
async fn update_document(repo: &Repo, id: DocumentId) -> Result<(), RepoError> {
let tx = repo.begin().await?;
let _span = tracing::info_span!("update_document", %id).entered();
repo.lock_document(&tx, id).await?;
repo.write_document(&tx, id).await?;
tx.commit().await?;
Ok(())
} // span exits; uncommitted guards/resources are dropped on every return pathGo's defer and C#'s using / IDisposable are good mechanisms, and experienced teams use them successfully. Rust's advantage is that scoped ownership is the default shape of the language. You can still design a bad transaction boundary or hold a lock too long, but it is harder to forget cleanup code in ordinary control flow.
Rust traits are not just "interfaces". They support associated types, trait bounds, blanket implementations, static dispatch through monomorphized generics, and dynamic dispatch when you explicitly choose it. That combination is useful for framework code: APIs can be generic and strongly typed without requiring a runtime reflection model.
trait DocumentClient {
type Error;
async fn list_documents(
&self,
tenant: TenantId,
owner: UserId,
) -> Result<Vec<Document>, Self::Error>;
}
async fn render_dashboard<C>(client: &C, tenant: TenantId, owner: UserId) -> Result<(), C::Error>
where
C: DocumentClient,
{
let docs = client.list_documents(tenant, owner).await?;
/* ... */
Ok(())
}Go now has generics and remains excellent for simple interfaces. C# has a powerful generic system and mature runtime tooling. Rust's particular advantage for Gears is that SDK crates, in-process clients, and future out-of-process transports can share type-safe contracts while still compiling many abstractions down to direct calls. This is one reason the ClientHub model in B.7 can stay ergonomic without giving up type safety.
Rust macros are not only text substitution. Derive and procedural macros inspect Rust syntax at compile time and generate checked Rust code. That lets framework authors put repetitive correctness rules close to the type definition instead of relying only on runtime reflection, handwritten glue, or external code generation.
#[derive(Scopable, Deserialize, Serialize)]
#[secure(tenant_col = "tenant_id", owner_col = "owner_id")]
struct Document {
tenant_id: TenantId,
owner_id: UserId,
title: String,
}This is the bridge between plain Rust and Gears. #[derive(Scopable)] can generate scoping metadata from the entity type; serde derives serialization without handwritten mapping code; schema tools can derive OpenAPI or JSON Schema from Rust types; SQL tooling such as sqlx::query! can even check queries against a database schema at compile time when configured for that workflow.
Go and C# have strong alternatives: Go has explicit code generation and struct tags; C# has attributes, reflection, and source generators. Rust's advantage is the combination of type-checked generated code, no required runtime reflection, and close integration with the compiler pipeline. That is why B.5 and B.9 are not "magic": Gears leans on Rust's macro and lint ecosystem to turn framework conventions into code the compiler can verify.
Rust gives you a safe language. It does not give you multi-tenancy, an authz model, a consistent API dialect, licensing, or a deployment story. In Go, C#, or Rust, teams still need to choose or build those platform conventions.
Gears is the middleware and framework that provides shared implementations and makes secure-by-default patterns the standard path.
Multi-tenancy, permissions & roles, licensing & quota, usage collection, and an event system are all built in — and each is a regular, replaceable gear with its own SDK. You can swap Gears' authn-resolver / tenant-resolver for your existing vendor systems, or integrate an existing product catalog / license engine via plugins, without modifying core gears.
Gears is designed to keep product and technical documentation close to the code and tests instead of treating it as a separate wiki that slowly diverges. The integrated Studio and spec-driven development workflow store architecture, requirements, decomposition, design, and feature documents alongside the implementation in markdown files.
Traceable IDs such as cpt-* connect specifications, generated code and tests. Deterministic checks validate document structure, templates, tables of contents, references, and traceability, so documentation defects can be found by tools rather than only by human review. AI workflows can then use those same IDs and validated artifacts for semantic spec-to-code transformation, gap analysis, and integrity checks after code changes.
For long-living XaaS systems, this matters as much as the runtime framework: requirements, APIs, tenancy rules, and operational decisions remain navigable and auditable as the system grows.
One of the highest-risk bugs in a SaaS backend is a missing tenant filter — one missing WHERE tenant_id = ? can expose data across tenants.
In many Go or C# services, this is handled through query helpers, ORM conventions, middleware, or code review. Those approaches can work well, but they still depend on every code path using the right abstraction:
// Go — one missing clause = cross-tenant data leak. The compiler is silent.
rows, _ := db.Query("SELECT * FROM documents WHERE owner = ?", userID)
// forgot AND tenant_id = ? -> leaks every tenant's documentsIn Gears, entities derive Scopable, and the recommended repository path uses SecureConn to apply the caller's AccessScope (tenant, resource, owner, type) as automatic WHERE clauses:
// Rust + Gears — scoping is applied by the framework from the SecurityContext.
#[derive(Scopable)]
#[secure(tenant_col = "tenant_id", owner_col = "owner_id")]
struct Document { /* ... */ }
// The AccessScope (derived from the authenticated caller) is applied automatically.
let docs = secure_conn
.scoped::<Document>(&access_scope)
.filter(documents::Column::Status.eq("active"))
.all()
.await?; // emitted SQL always includes the tenant/owner predicatesThe architecture makes the scoped path the normal path. Direct ORM or SQL access is reserved for infrastructure/migration code and guarded by review plus architecture lints.
Gears ships a real authorization architecture, not a middleware stub:
- API Gateway validates the token and injects a
SecurityContext. - The PDP (Policy Decision Point — an AuthZ Resolver plugin) evaluates policies (RBAC/ABAC/ReBAC — vendor's choice) and returns a decision plus row-level constraints.
- The PEP (Policy Enforcement Point — your domain gear) compiles those
constraints into SQL
WHEREclauses viaAccessScope. - Returns predicates, not resource IDs → one PDP decision per request, with the database applying row-level predicates for correct pagination and counts.
- Fail-closed: denied / unreachable PDP / missing constraints →
403.
In Go or C#, a mature platform team can centralize this with shared middleware, repositories, analyzers, and code review. Gears' value is that this repo already provides a standard contract and implementation path for its services.
This is where Gears uses Rust's linting model as a platform feature. This is not fundamentally different in kind from Go projects using golangci-lint / go vet, or .NET projects using Roslyn analyzers. The practical benefit is that Gears already ships a suite of custom dylint lints for its architecture, and CI can fail the build on violations:
- Domain-layer isolation — no infra imports (
sqlx,sea_orm,axum,reqwest) insidedomain/. - Direct-SQL restriction — raw SQL only in migration infrastructure.
- Versioned REST paths — endpoints must be
/<gear>/v1/.... - Mandatory
OperationBuildermetadata — auth posture, error responses, and schemas must be declared. - GTS identifier correctness — valid IDs; no
schema_for!on GTS structs. - No unsafe shortcuts —
unwrap, avoidablepanic, unsafe code paths, and unchecked invariants are treated as build-time failures where they would undermine platform guarantees.
Why this matters for Gears: the framework is not just a set of helper libraries; it is a runtime contract for secure XaaS systems. dylint lets the repository encode rules that ordinary Rust tooling cannot know: which layer may import which crate, which API paths must be versioned, which API metadata is mandatory, where SQL is allowed, and which GTS identifiers are valid. That turns some design-document rules into CI-enforced checks.
Compared with Go/C# alternatives, this is not about one ecosystem being incapable and another being capable. Go has go vet, staticcheck, and custom analyzers; C# has Roslyn analyzers; both are mature and useful. The difference is that Gears already includes project-specific checks for layer boundaries, route metadata, SQL placement, GTS identifiers, and unsafe shortcuts, wired into the same quality gate as formatting, Clippy, tests, and security checks.
Documentation in markdown decays.
dylintmakes selected architecture rules executable in CI; that is not a substitute for design review, but it catches violations that reviewers would otherwise have to remember manually.
Gears offer a set of useful capabilities for building modern XaaS systems:
- Gear-owned migrations — gears own their database migrations and run them as part of the runtime lifecycle, so schema ownership follows capability ownership.
- Cluster primitives — the cluster system gear provides common cross-instance coordination primitives: distributed cache, leader election, distributed locks, and service discovery. Operators can bind each primitive to the right backend for the deployment (in-process, Postgres, Redis, Kubernetes, NATS, etcd), while consumers keep the same facade-style API and get startup validation when a backend cannot satisfy required guarantees.
- Transactional outbox — reliable async message production with per-partition ordering, transactional or leased processing modes, retry/reject semantics, and graceful cancellation.
- HTTP client —
toolkit-httpprovides a standard outbound HTTP client with rustls TLS, pooling, timeouts, retries with exponential backoff, User-Agent injection, fail-fast concurrency limiting, response size limits, transparent gzip/brotli/deflate decompression, and secure redirect handling with SSRF and credential-leakage protections. - SSE streaming — toolkit support for typed server-sent events gives gears a standard way to expose streaming APIs without inventing one-off protocols.
In Go and C#, teams usually choose routers/frameworks and standardize conventions for auth, errors, pagination, and OpenAPI generation. Gears makes one such standard choice for this platform: a single authoritative route-registration mechanism where one declaration produces the route, the auth posture, the license posture, the schemas, the registered error responses, and the OpenAPI entry:
// Rust + Gears — one place declares everything; OpenAPI is generated from it.
OperationBuilder::get("/documents/v1/documents")
.operation_id("documents.list")
.summary("List documents")
.authenticated() // auth posture is part of the route
.require_license_features::<License>([])
.handler(handlers::list_documents)
.json_response_with_schema::<dto::DocumentPage>(openapi, StatusCode::OK, "OK")
.error_401(openapi)
.error_500(openapi)
.register(router, openapi);This gives gears one uniform place for pagination/filtering (OData $filter, $select, $orderby), auth, rate-limiting, timeouts, observability, and OpenAPI generation. It reduces drift, but still depends on routes using the standard builder and on CI/review catching bypasses.
A Gear is a self-contained unit that owns its API (an SDK crate), owns its data (behind SecureConn), is discovered at link time via inventory, and composes through a typed ClientHub in-process — or the same SDK over gRPC out-of-process.
The logical model is identical regardless of the physical boundary. Switching between in-process and out-of-process is a YAML field (runtime.type), not a code change:
- Single-node — all gears in one process → edge, on-prem appliances, dev/test.
- Multi-node — gears across processes/machines over gRPC, no orchestrator.
- Kubernetes — containerized, full orchestration, cloud-native ops.
Develop locally single-node → deploy bare-metal → scale to K8s — no rewrites.
Gears exposes extensible domain objects through GTS: globally unique, human-readable, versioned identifiers (e.g. gts.cf.core.events.event.v1~) with JSON Schemas generated directly from Rust types and registered in a Types Registry. You can add new event types, settings, model attributes, permissions, or license types without touching existing gears. CRUD handlers are customizable via hooks/callbacks implemented as serverless functions or workflows.
Gears uses a canonical error taxonomy aligned with the 16 gRPC error status codes (NotFound, AlreadyExists, PermissionDenied, InvalidArgument, Unauthenticated, and others). Over HTTP, errors are rendered as RFC-9457 Problem documents, so REST handlers, SDK boundaries, and future gRPC transports share the same vocabulary. Handlers return typed domain errors; middleware maps them into stable wire responses with trace context.
Gears standardizes operational concerns that are often re-created per service: OpenTelemetry tracing, request IDs, structured logs, health endpoints (/health, /healthz), timeouts, body limits, CORS/MIME controls, rate limiting, and inflight protection. This gives platform teams a common operational surface across all gears instead of a different observability story per service.
For regulated deployments, Gears can be built with --features fips to route TLS crypto through OS/provider-specific FIPS-capable modules such as Apple corecrypto on supported macOS configurations, AWS-LC FIPS on Linux, and Windows CNG on Windows. Validation status is provider-, platform-, and version-specific; see the security/FIPS docs for the supported matrix. This does not claim that Gears itself is a CMVP-listed module; it means Gears consumes validated cryptographic modules through a controlled TLS provider strategy.
Gears treats the dependency graph as part of the security boundary. The useful part is not the slogan; it is that dependency decisions live in files that reviewers can diff.
- Same inputs for everyone —
Cargo.lockis committed, andrust-toolchain.tomlpins the Rust version and components. A developer, CI job, and release build start from the same dependency closure and compiler baseline for a given commit. - License and advisory checks —
deny.tomldefines the SPDX allowlist, RustSec advisory handling, allowed registries, and pinned per-crate exceptions. Exceptions include the crate, version, and rationale in the file, so a license or vulnerability exception is a code-review item, not a private spreadsheet. - FIPS graph checks —
deny-fips.tomlis a strictercargo-denyprofile for--features fips. It blocks non-approved crypto crates and TLS backends outside the validated provider chain at build time. Phase A bans crates that should not enter the FIPS graph; Phase B records transitive cleanup before stricter bans such asring, non-FIPSaws-lc-rs, pure-Rust AES/HMAC/HKDF/SHA paths, and other non-approved primitives can be enabled. - CI coverage — CI runs
make deny; OpenSSF Scorecard runs on a schedule; CodeQL Advanced scans Rust, Python, and GitHub Actions; ClusterFuzzLite runs PR-scoped Rust fuzz targets on PRs that touch code; and repository-controlled GitHub Actions are pinned by commit SHA rather than mutable tags. - Known gaps are explicit — SBOM generation (CycloneDX),
cargo-vetattestations, and SLSA provenance for release artifacts are useful next steps, but they should be treated as roadmap items until wired into CI.
This is not unique to Rust — Go, C#, and other ecosystems can and should enforce dependency policy too. Rust's advantage for Gears is that the build graph, lockfile, feature flags, advisory database, and cargo-deny checks compose naturally with the same automation model used for formatting, lints, tests, architecture rules, and FIPS-mode builds.
Gears also defines a workspace lint floor in Cargo.toml and clippy.toml. This answers the practical question "what does prohibited mean?" with concrete build rules, not style guidance.
- Unsafe and debug leakage —
unsafe_code = "forbid"applies workspace-wide.unwrap_used,expect_used,dbg_macro,use_debug, andunnecessary_debug_formattingare denied; tests are the explicit exception forunwrap/expectthroughclippy.toml. - Async correctness —
await_holding_lock,await_holding_refcell_ref,async_yields_async, andunused_asyncare denied. These catch common Tokio mistakes: holding a lock across.await, carryingRefCellborrows across suspension points, and adding unnecessary async boundaries. - Numeric safety —
cast_possible_truncation,cast_possible_wrap,cast_precision_loss,cast_sign_loss,integer_division,float_cmp, andlossy_float_literalare denied. For quotas, usage metering, billing, and limits, silent narrowing or precision loss must be explicit and reviewed. - Complexity budget —
cognitive_complexity,type_complexity,too_many_lines, andstruct_excessive_boolsare denied with project thresholds: cognitive complexity20, type complexity190, function length200, and at most2bool fields in a struct. The bool limit nudges state modeling toward enums instead of ambiguous flag bags. - AI-generated code guardrails — the config includes stricter thresholds for LLM-generated code:
single-char-binding-names-threshold = 4,large-error-threshold = 128, plus denials for redundant clones, needless collects, verbose patterns, large stack arrays,Rc<Mutex<_>>, andLinkedList. This catches the kind of plausible-but-bloated code that agents and humans both produce under time pressure. - Tenant-safe ORM use —
clippy.tomlconfiguresdisallowed-methodsfor direct SeaORMall,one,count, update, and delete execution methods, with reasons pointing developers to secure scoped wrappers.
Go and C# teams can enforce many of these rules with golangci-lint, go vet, Roslyn analyzers, and custom build policy. The practical difference in Gears is that the Rust compiler, Clippy, custom dylint rules, and Cargo feature checks are already wired into one standard workspace safety pipeline (make clippy, make lint, make dylint, make safety).
Because gears are composable libraries, the full business logic — including scenarios that span multiple gears — can be run and tested locally on a developer machine, without Jenkins, Ansible, or K8s. A single process can host many gears and exercise cross-gear flows end-to-end entirely in memory.
Gears comes with integrated unit, integration, end-to-end, and fuzzing tests, plus coverage and diff-coverage to show exactly which changes are exercised. The same test suites can then be repeated by CI against a distributed deployment, where real networking, orchestration, and database backends are also exercised. This local-first, fully testable runtime lets developers and Agentic IDEs/LLMs catch logical and cross-gear issues early, before a pull request is opened, so most behavioral defects are found locally long before CI or a release stage.
Choose Gears when you are:
- A XaaS / SaaS vendor building on a governed, multi-tenant backbone.
- A platform/product team that wants security and tenancy handled by shared platform defaults instead of one-off service conventions.
- A GenAI builder needing chat, RAG, model management, agents, tools.
- An on-prem / edge vendor shipping single-binary appliances.
- An enterprise embedding capabilities into an existing platform via plugins.
Gears is deliberately not:
- Optimized for minimalism / the absolute lowest barrier to entry — it prioritizes explicit structure, security, governance, and evolvability.
- A ready-to-use catalog of end-user services — it's the foundation vendors build on.
- A replacement for cloud infrastructure or PaaS — gears are libraries and building blocks.
git clone --recurse-submodules https://github.com/constructorfabric/gears-rust
cd gears-rust
make build # build libs + example server
make example # run the example server -> http://127.0.0.1:8087/cf/docs
curl http://127.0.0.1:8087/cf/health # detailed JSON
curl http://127.0.0.1:8087/healthz # liveness "ok"Next steps
- Read the Architecture Manifest for the full rationale behind the Rust and monorepo choices.
- Browse the Gears inventory to see what's already built.
- Follow the Toolkit guide to build your first gear.
Constructor Fabric Gears (Rust) · Apache-2.0 · by the Cyber Fabric Foundation. Secure · Modular · Composable · GenAI-ready.