Skip to content

The unit Type

The unit type represents the absence of a meaningful value while still being a real, first-class type. It is similar to void in other languages, but with an explicit value.

Unlike None (the null value), unit:

  • Has its own distinct runtime value (printed as unit)
  • Is not the same as None / null
  • Is intended for APIs that conceptually "return nothing" but still participate in the type system

Quick Overview

  • Type name: unit
  • Value: a single value, printed as unit
  • Equality:
  • unit == unittrue
  • unit != unitfalse
  • None == unitfalse
  • Truthiness:
  • unit is treated as falsy, similar to None.
Basic unit usage
package main
import fmt

var u1 unit
var u2 unit

fmt.Printf("u1 == u2 ? %v\n", u1 == u2)  // true

if u1 {
    fmt.Println("will NOT run")
} else {
    fmt.Println("unit is falsy in conditionals")
}

When to Use unit vs None

Harneet distinguishes between unit and None:

  • unit
  • A proper type with a single inhabitant
  • Used for functions that conceptually return no data
  • Plays well with the type system (e.g. generics / APIs that want an explicit "no payload" type)
  • None
  • A value representing null / no value
  • Commonly used in error handling patterns as the "no error" sentinel

Example: Side-effecting functions

Functions returning unit
package main
import fmt

// Explicitly declares that the function returns no meaningful value
function logMessage(msg string) unit {
    fmt.Printf("[LOG] %s\n", msg)
}

function touchFile(path string) unit {
    // Imagine opening/creating a file here
}

logMessage("started")
touchFile("/tmp/demo.txt")

Example: Distinguishing None and unit

1
2
3
4
5
6
7
8
package main
import fmt

var n = None
var u unit

fmt.Printf("None == unit ? %v\n", n == u)  // false
fmt.Printf("unit == unit ? %v\n", u == u)  // true

Equality Semantics

The == / != operators for unit follow these rules:

  • All unit values are equal to each other
  • unit is never equal to values of other types (including None)

This mirrors how None behaves (all None values are equal to each other, but not to other types), while keeping the two concepts distinct.

Truthiness in Conditionals

unit is considered falsy:

unit truthiness
package main
import fmt

var u unit

if u {
    fmt.Println("This will not execute")
} else {
    fmt.Println("unit is treated as false")
}

This matches the intuition that a unit result carries no useful data.

Zero Values

The zero value of unit is the unit value itself:

1
2
3
4
var u unit       // initialized to unit
var done unit    // also unit

fmt.Println(u)   // prints: unit

Variables of type unit are always in a valid state; there is no "uninitialized" or "null" unit.

Patterns & Best Practices

Using unit in function return types

When you declare a function to return unit, you are making an explicit API promise: this function performs work but does not return a meaningful value.

For such functions:

  • A naked return is allowed and treated as returning unit.
  • Falling off the end of the function body (no return at all) is also treated as returning unit.
  • Any return with a value is a type error.

This applies consistently to:

  • Named functions
  • Anonymous functions
  • Arrow functions (both block and expression bodies)
unit-returning functions
package main
import fmt

// Named function
function log() unit {
    fmt.Println("side effect only")
    // implicit unit return
}

// Anonymous function
var f = function() unit {
    // work
    return
}

// Arrow function with expression body
var g = () unit => unit

Choosing between unit and None

  • Use unit when you want to say "this API returns no payload" but still participate in the type system (e.g. generics, explicit contracts).
  • Use None as a value when you need a nullable sentinel, especially in error-handling patterns (e.g. (T, error) where error is None on success).
  • Leave the return type unspecified (function f() { ... }) only when you intentionally want a loosely-typed, None-returning function for scripting-style code.

In short:

  • Structured APIs / libraries → prefer unit for no-result functions.
  • Error channels and sentinels → use None as the "no error" value.

Design Notes

  • unit is not an alias for None or null.
  • There is a dedicated runtime object for unit, separate from the None value.
  • The interpreter and VM both agree on:
  • A single logical unit value
  • Equality and truthiness semantics described above.

Use unit when you want your APIs and types to clearly say: this returns no data, while still staying inside Harneet's strong, explicit type system.