Why can't I reassign an injected component variable?
← DI Validation · Ref: Q669
An injected variable ('cache as CacheService!') is never assigned - not with ':=', and not with ':=?' either (E08170). The application declares which concrete component satisfies each abstract injection point; if code could reassign the variable, the wiring would become unpredictable.
AN INJECTED COMPONENT IS ALWAYS SET
The compiler proves that every injection a program can reach is registered in its application (E08210, E08220), so the value always arrives. That is also why ':=?' (assign only if unset) would never apply, and why checking it - 'if cache?', 'require cache?', 'cache ?: other', 'x <- cache? <- cache else other' - is itself an error (E08266): the check is always true.
THE FALLBACK IDIOM: A NEW VARIABLE
When something else may supply a component and the injected one is the default, the fallback runs the other way round - assign to a NEW variable, then fall back to the injected one:
chosen <- supplied chosen :=? cache // only if 'supplied' was unset
The example below passes a set and an unset component to such a function: the set one is used, the unset one falls back to the injected cache.
See Q667 for abstract injection. See Q668 for injectable contexts. See Q670 for field initialization.
Example
#!ek9 defines module qa.divalidation.reassignment defines component <?- Abstract cache contract. -?> CacheService as abstract lookup() as abstract -> cacheKey as String <- cachedValue as String? default operator ? <?- Simple in-memory cache implementation - unset when it has no prefix. -?> InMemoryCache extends CacheService prefix as String? InMemoryCache() prefix: String() InMemoryCache() -> keyPrefix as String prefix: keyPrefix override lookup() -> cacheKey as String <- cachedValue as String: `${prefix}${cacheKey}` default operator ? defines function <?- Use the supplied cache if it is set, else fall back to the injected one. The injected variable is never assigned: the fallback goes in a NEW variable. -?> chooseCache() -> supplied as CacheService <- chosen as CacheService? cache as CacheService! chosen: supplied chosen :=? cache defines application CacheApp register InMemoryCache("cached:") as CacheService defines program InjectionReassignmentDemo() with application of CacheApp stdout <- Stdout() cache as CacheService! directLookup <- cache.lookup("settings") stdout.println(`Lookup: ${directLookup}`) preferred <- chooseCache(InMemoryCache("preferred:")) stdout.println(`Preferred: ${preferred.lookup("theme")}`) fallback <- chooseCache(InMemoryCache()) stdout.println(`Fallback: ${fallback.lookup("theme")}`)
Common mistakes
E08170 — Injected component variables are set by the application and are never assigned, with ':=' or with ':=?'. The application wiring defines which implementation satisfies each injection point, and manual reassignment would break that contract. See ek9 -h E08170 for details.
Incorrect:
cache as CacheService! cache := InMemoryCache() directLookup <- cache.lookup("settings")
Correct:
cache as CacheService! directLookup <- cache.lookup("settings")
E08266 — An injected component is always set, so 'cache?' is always true and the 'else' never runs. For a fallback, assign to a NEW variable and fall back to the injected one with ':=?'. See ek9 -h E08266 for details.
Incorrect:
cache as CacheService! chosen: cache? <- cache else supplied
Correct:
cache as CacheService! chosen: supplied chosen :=? cache
Other ways to ask this
- What is E08170 reassignment of injected variable?
- Can I change an injected dependency at runtime?
- How do I handle optional injection?
Coming from another language?
Java: Spring injection is final by convention (not enforced), and optional injection (@Autowired(required = false)) can leave a null to check. Python: no enforcement. Go: manual wiring can be changed. Rust: ownership prevents aliasing. EK9: the compiler prevents any assignment to an injected variable, proves the injection always arrives, and rejects set-checks on it; a fallback goes in a new variable.
Keywords: DI, E08170, E08266, component, fallback, immutable, inject, injection, isset, null-safe, reassignment, registration, safe, validate