Why must I capture the return value of a pure function call?

← Comparison Patterns · Ref: Q641

EK9 detects when a call with a return value is used as a bare statement and the return value is discarded. For pure functions this is always an error (E11051) because a pure function has no side effects, so calling it without using the result is completely pointless.

DISCARDED OPERATOR RESULT (E11050)

An operator that returns a value, invoked in method-call form as a standalone statement, is rejected: 'a.+(b)' computes a sum and throws it away. (An ordinary non-pure function call such as 'fetchAndLog(42)' compiles as a bare statement - its side effects may be the point - but capturing the result is still the habit to keep.)

DISCARDED PURE RETURN (E11051)

Calling a pure function as a bare statement is especially bad because pure functions have NO side effects. The call does nothing observable. It is dead code.

DISCARDED CONSTRUCTOR (E11055)

Creating an object without capturing the reference:

  Sensor(98.6)    <- creates and immediately discards

This is always a bug. If you need side effects, use a function.

DISCARDED RESULT OR OPTIONAL (E11052)

Calling a function that returns Result or Optional and ignoring the result defeats the purpose of these types.

CORRECT PATTERN

Always capture the return value of functions that return values:

  total <- computeTotal(items)
  processed <- transform(input)
  newList <- List() of String

See Q316 for discarded return detection. See Q640 for collection patterns. See Q635 for pure mutation restrictions.

Example

defines module qa.comparison.capturereturn

  defines constant

    SUM_THRESHOLD <- 6

  defines function

    <?-
      Pure function: its return value MUST be captured.
      Calling this without capturing triggers E11051.
    -?>
    computeSum() as pure
      ->
        valueA as Integer
        valueB as Integer
      <- total as Integer: valueA + valueB

    <?-
      Pure function: return value must be captured.
    -?>
    formatPrice() as pure
      -> price as Float
      <- formatted as String: `Price: ${price}`

    <?-
      Pure function with string processing.
    -?>
    buildLabel() as pure
      ->
        prefix as String
        suffix as String
      <- label as String: `${prefix}-${suffix}`

    <?-
      Non-pure function with return value.
      Discarding the result compiles (it is not pure), but capturing it is still good practice.
    -?>
    fetchAndLog()
      -> itemId as Integer
      <- itemName as String: `item-${itemId}`

  defines program

    CaptureReturnDemo()
      stdout <- Stdout()

      //Correct: capture the return value
      total <- computeSum(10, 20)
      stdout.println(`Sum: ${total}`)

      //Correct: capture formatted string
      priceStr <- formatPrice(9.99)
      stdout.println(priceStr)

      //Correct: capture and use
      label <- buildLabel("order", "001")
      stdout.println(label)

      //Correct: capture non-pure return
      itemName <- fetchAndLog(42)
      stdout.println(`Fetched: ${itemName}`)

      //Correct: use return value directly in expression
      doubled <- computeSum(5, 5) * 2
      stdout.println(`Doubled: ${doubled}`)

      //Correct: use in condition
      if computeSum(3, 4) > SUM_THRESHOLD
        stdout.println("Sum exceeds threshold")

Common mistakes

E11051 — computeSum is a pure function, so the extra bare call computeSum(1, 2) discards its result and achieves nothing. Capture the result. See ek9 -h E11051 for details.

Incorrect:

total <- computeSum(10, 20)
      computeSum(1, 2)

Correct:

total <- computeSum(10, 20)

E11051 — formatPrice is a pure function, so the extra bare call formatPrice(1.5) discards its result and achieves nothing. Capture the result. See ek9 -h E11051 for details.

Incorrect:

priceStr <- formatPrice(9.99)
      formatPrice(1.5)

Correct:

priceStr <- formatPrice(9.99)
Other ways to ask this
  • What is E11050 discarded return value?
  • What is E11051 discarded pure function return?
  • Why does EK9 reject calling a function without capturing its result?

Coming from another language?

Java: return values can always be silently discarded, no warning by default. IntelliJ warns on discarded return from pure-annotated methods (optional). Rust: #[must_use] attribute warns when return value is discarded (opt-in). Go: error return values often silently discarded (common bug source). Kotlin: no enforcement. C++: [[nodiscard]] attribute (C++17, opt-in). EK9: mandatory compiler error E11051 for discarding the result of a pure function, E11050 for a discarded operator result, E11055 for a discarded constructor, E11052 for a discarded Result or Optional.

Keywords: compare, constructor, result, E11050, E11051, E11052, comparison, E11055, discard, side-effect, return, immutable, guard, pure, error, dead, ok, capture, pattern, function