When should I use 'name as Type: value' instead of 'name <- value' for a class field to avoid E04050?
← Classes and OOP · Ref: Q1283
EK9 offers two forms for initialising a class field and they are NOT interchangeable. Pick the right one for the expression you are writing and E04050 disappears.
FORM 1 — '<-' INFERRED DECLARATION
The compiler infers the field type from a simple constructor call, a literal, or a list/dict literal. This runs at phase 2, BEFORE full expression processing.
count <- 0 // Integer tags <- ["alpha", "beta"] // List of String config <- {"timeout": 30, "retries": 3} // Dict of (String, Integer) items <- List() of String // empty List of String scores <- Dict() of (String, Integer) // empty Dict
FORM 2 — 'as Type: value' EXPLICIT DECLARATION
You state the type up front, and the initialiser can be any expression. This tells the compiler the field's type without needing to resolve the initialiser first, so complex expressions are allowed.
totalPrice as Float: basePrice * quantityFactor discount as Float: Float(0.0) label as String: `Item ${itemId}`
WHICH FORM TO USE
If the initialiser is a simple constructor call or a literal, use '<-'. If the initialiser is ANY of the following, use 'as Type:' instead:
- Arithmetic or string interpolation involving other variables
- A method call on an existing object
- A function call that returns a value
- A conditional or multi-branch expression
THE E04050 TRAP
The natural instinct 'just use <- everywhere' fails on expressions like:
total <- price * quantity // E04050 — arithmetic label <- `Item ${itemId}` // E04050 — interpolation head <- items.first() // E04050 — method call
The fix is mechanical: change '<-' to 'as <inferred-type>:' with no other edits.
total as Float: price * quantity // OK label as String: `Item ${itemId}` // OK head as String: items.first() // OK
See Q585 for field initialisation rules and Q1282 for the specific is-set-operator trap.
Example
defines module qa.classesandoop.e04050explicittype defines class PriceTag unitPrice as Float: Float() itemId as String: String() PriceTag() -> unitPrice as Float itemId as String this.unitPrice :=: unitPrice this.itemId :=: itemId label() as pure <- rtn as String: `Item ${itemId} @ ${unitPrice}` override operator ? as pure <- rtn as Boolean: unitPrice? and itemId? operator $ as pure <- rtn as String: label() defines program E04050ExplicitTypeDemo() stdout <- Stdout() tag <- PriceTag(19.99, "WIDGET-001") stdout.println($tag)
Common mistakes
E04050 — Using '<-' with an arithmetic expression fails E04050 because type inference cannot resolve the expression type during phase 2. Use 'as Type: expression' instead — the explicit type annotation lets the compiler know the field's type without resolving the initialiser first.
Incorrect:
unitPrice <- Float() + Float()
Correct:
unitPrice as Float: Float()
Other ways to ask this
- What's the difference between '<-' and 'as Type:' for class fields in EK9?
- How do I initialize a class field with a complex expression without E04050?
- When is explicit type annotation required in EK9 class field declarations?
- Why does 'total <- price * quantity' fail in a class body?
Coming from another language?
Java/Kotlin/Swift all allow 'var total = price * quantity' in a class body — the inferrer runs after full expression resolution. EK9 field type inference runs earlier in the pipeline, so '<-' only works for simple constructors and literals. For anything else, EK9 asks you to state the type with 'as Type: value'. One character of annotation, complete clarity for the compiler.
Keywords: field initialisation, type inference, explicit type, as Type, class field, E04050