Pre-RFC: global_generic — crate-level type parameters
Summary
Add a global_generic declaration that lets a crate expose a type parameter which is bound exactly once, in main.rs, and then used anywhere in the dependency graph as if it were a concrete type.
Syntax
// lib.rs (or any library crate)
global_generic TypeName: TraitName;
// main.rs (the binary crate — the only place binding is allowed)
global_generic crate::TypeName = ConcreteType;
Example
// infrastructure/src/lib.rs
pub global_generic Db: DatabaseRead;
pub fn load_accounts(client: &mut Db::Client) -> Vec<Account> {
// Db is treated as a concrete type here
}
// main.rs
global_generic infrastructure::Db = PostgresDb;
fn main() {
let mut client = PostgresDb::connect();
let accounts = infrastructure::load_accounts(&mut client);
}
Motivation
Layered applications must pick a handful of types (Db, Runtime, Clock, Logger) once at startup, but Rust forces one of:
- Generic soup — every function declares
<Db: DatabaseRead, Rt: Runtime, …>. dyn Trait— loses static dispatch, breaks on GATs /impl Future.- Service locators — runtime
.unwrap(), no compile-time guarantee. - Forked crates — one copy per backend.
global_generic expresses "this parameter is fixed for the whole build" without any of these.
Semantics (sketch)
global_generic T: Trait;declaresTin the declaring crate.Tbehaves like a type alias with boundTrait.- The binding
global_generic path::T = Concrete;is legal only in a binary crate (the root of the compilation). Exactly one binding per declaredglobal_genericper build. - Resolution happens after macro expansion, before type-checking.
T::Assoc,T::GAT<'a>,T::method()all work as if the concrete type were written literally. - If
main.rsnever binds a reachableglobal_generic, compilation fails with "unbound global genericT(declared in<crate>)". - A library crate that declares
global_genericis monomorphised once per binding. Two binaries with different bindings produce two instantiations. #[global_allocator]becomes a special case of this feature.
Drawbacks
- "What type is
T?" is no longer locally answerable — you must know the binary's bindings. - Changing a binding in
main.rsis a full recompile of the affected library crates. - Adds a new resolution pass and new monomorphisation keys to crate metadata.
- Coherence: two binaries linking the same generic library with different bindings is fine; a single binary with two conflicting bindings is an error.
Prior art
#[global_allocator]— same shape: declared once, bound once, seen everywhere.
Unresolved questions
- Should binding be allowed in a library crate that is itself
global_generic(i.e. re-exported binding)? - Do two binaries sharing the same library binding share the monomorphised rlib?
- Should
pub global_genericparticipate in semver? - Is
#[rebind]needed for tests, or should tests use a wrapper binary? - What is the visibility rule for the bound type inside the declaring crate?