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