Why must operator and pure function return values be captured?

← Code Quality · Ref: Q693

EK9 requires that return values from constructors, operators, pure functions, and Result-producing functions are captured in variables. Discarding these values indicates a bug.

CONSTRUCTOR RETURNS (E11055)

A constructor creates an object. Calling a constructor as a bare statement discards the result immediately. This is always either a bug (forgot the assignment) or a misuse of constructors for side effects:

  Sensor(98.6)          // E11055: constructor result discarded
  sensor <- Sensor(98.6)  // Correct: captured

OPERATOR RETURNS (E11050)

Operators like + and * always produce new values. Discarding an operator result means the computation was pointless:

  name + " suffix"     // E11050: result discarded
  combined <- name + " suffix"  // Correct: captured

PURE FUNCTION RETURNS (E11051)

Pure functions have no side effects. If the return value is discarded, the call accomplished nothing:

  validate(input)       // E11051: pure result discarded
  isValid <- validate(input)  // Correct: captured

RESULT RETURNS (E11052)

Result-producing functions may fail. Discarding the Result ignores potential errors:

  parseAge(text)        // E11052: Result discarded
  result <- parseAge(text)  // Correct: captured

See Q310 for code quality overview. See Q316 for discarded returns detail. See Q321 for naming conventions.
See Q732 for discarded return boundary examples.

Example

defines module qa.codequality.capturedreturns

  defines function

    <?-
      Pure function: return value must be captured.
      Discarding a pure return is always a bug.
    -?>
    sanitizeInput() as pure
      -> rawInput as String
      <- cleaned as String: rawInput.trim()

    <?-
      Another pure function in the chain.
    -?>
    validateLength() as pure
      -> inputText as String
      <- isValid as Boolean: length inputText > 0

  defines class

    <?-
      Class demonstrating correct return value capture.
      Every operator and pure return is stored in a variable.
    -?>
    TextBuffer
      content <- String()

      TextBuffer()
        -> initialContent as String
        this.content: initialContent

      getContent() as pure
        <- rtn as String: content

      append()
        -> suffix as String
        content: content + suffix

      default operator ?

  defines program

    CapturedOperatorReturnsDemo()
      stdout <- Stdout()

      //Correct: pure function return captured
      cleaned <- sanitizeInput("  hello  ")
      stdout.println(cleaned)

      //Correct: pure function return captured
      isValid <- validateLength(cleaned)
      stdout.println(`Valid: ${isValid}`)

      //Correct: operator return captured
      combined <- cleaned + " world"
      stdout.println(combined)

      //Correct: method return captured
      textStore <- TextBuffer("start")
      storeContent <- textStore.getContent()
      stdout.println(storeContent)

Common mistakes

E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.

Incorrect:

stdout.println(cleaned.toUpperCase())

Correct:

stdout.println(cleaned)

E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.

Incorrect:

combined <- cleaned.toUpperCase() + " world"

Correct:

combined <- cleaned + " world"

E50001 — Removing the variable declaration means later references to the variable become unresolved, triggering E50001. See ek9 -h E50001 for details.

Incorrect:

sanitizeInput("  hello  ")

Correct:

cleaned <- sanitizeInput("  hello  ")

E50001 — The pure method getContent returns a value that must be captured. Discarding a pure return is always a bug. See ek9 -h E50001 for details.

Incorrect:

textStore.getContent()

Correct:

storeContent <- textStore.getContent()
Other ways to ask this
  • What is E11050 uncaptured operator return?
  • What is E11051 uncaptured pure function return?
  • What is E11052 uncaptured Result return?
  • Why does EK9 require capturing return values?

Coming from another language?

Java: return values can be silently discarded. Rust: #[must_use] annotation (optional). Go: blank identifier _ explicitly discards. Python: no enforcement. C++: [[nodiscard]] attribute (optional). EK9: all operator, pure, and Result returns must be captured (compile error, not warning).

Keywords: discard, return, quality, operator, constructor, E11055, E11052, E11051, E11050, pure, capture, metric, side-effect, immutable, Result, function, clean-code