Documentation / Casts

Casts

A cast converts a value for an immediate typed destination. Moth doesn't use constructor-like conversions such as Int(value), and it doesn't infer a target from distant code.

The receiving type names the target. cast, cast! or catch makes the failure path visible beside the conversion.

Choose the explanation level: Cast syntax

Cast syntax

A cast converts a value to the type expected by its destination.

parse_count |text String| -> Int, Error!:
    return cast! text
;

ratio Float = cast 3

Read the first conversion as:

> Convert text to the Int required by the function's return slot.

Use:

  • cast when the conversion cannot fail
  • cast! when failure should leave through the function's error return
  • cast ... catch: when failure should be handled here

Moth does not use Int(value) or String(value) as conversion syntax.

cast performs an explicit conversion to a compiler-supported builtin target.

The target comes from the immediate typed receiving boundary.

parse_count |text String| -> Int, Error!:
    return cast! text
;

ratio Float = cast 3
label String = cast ratio

Forms

value Target = cast expression
value Target = cast! expression

value Target = cast expression catch:
    then fallback
;

value Target = cast expression catch |err|:
    then fallback
;
  • Plain cast requires infallible evidence.
  • cast! requires fallible evidence and propagates through the current function's builtin Error! slot.
  • cast ... catch: requires fallible evidence and recovers locally.
  • Cast syntax does not contain the target in parentheses or after the expression.

Removed conversion constructors

These scalar conversion forms are invalid:

Int(value)
Float(value)
Bool(value)
String(value)
Char(value)

Error(...) remains a real builtin constructor:

err = Error("Missing number", 200)

Conversion to Error still uses cast when conversion rather than construction is intended:

err Error = cast "Missing number"
Choose the explanation level: Cast targets

Cast targets

A cast needs a clear destination type.

These are supported cast targets:

  • Bool
  • Int
  • String
  • Char
  • Float
  • Error

Not every source type can convert to every target.

An annotation gives the cast its target:

count Int = cast! text
label String = cast count

Without that annotation, the compiler cannot know which conversion you want:

count = cast text

The second form is invalid.

Casts can also receive a target from a function parameter, return slot, field or another immediate typed destination.

Explicit casts target compiler-supported builtin types:

Bool
Int
String
Char
Float
Error

A type being a valid target does not mean every source type can convert to it.

Builtin cast table

  • Int -> Float (infallible): Numeric conversion to finite Float
  • Int -> String (infallible): Base-10 signed integer text
  • Float -> String (infallible): Moth's stable Float formatter
  • Bool -> String (infallible): "true" or "false"
  • Char -> String (infallible): One Unicode scalar as UTF-8 text
  • Char -> Int (infallible): Unicode scalar code point
  • String -> Error (infallible): Error message from the text with code 0
  • Error -> String (infallible): The error message only
  • Float -> Int (fallible): Truncate toward zero, then require signed 32-bit range
  • Int -> Char (fallible): Require a valid Unicode scalar value
  • String -> Int (fallible): Parse the complete Moth integer text grammar
  • String -> Float (fallible): Parse the complete numeric grammar and require a finite result
  • String -> Bool (fallible): Trim surrounding whitespace, then accept exactly lowercase true or false
  • String -> Char (fallible): Require exactly one Unicode scalar

No other builtin source-target pair is implied by the target list.

Same-type casts are invalid.

Target source

The target must come from the immediate typed boundary.

Valid target boundaries include:

  • an annotated declaration or assignment
  • an explicit return slot
  • a concrete function parameter
  • a struct or choice field
  • a default value
  • a typed collection or map entry
  • a compiler-owned typed assertion argument slot (condition: Bool or message: String?)
  • a then arm whose enclosing value-producing block has an explicit receiver

Generic parameter slots are not cast targets. Generic inference does not look through cast.

These positions do not supply cast targets:

  • conditions
  • loop conditions
  • template interpolation
  • operator operands
  • expression statements
  • untyped declarations
-- INVALID: these positions do not supply cast targets
value = cast source

if cast value:
    io.line("invalid")
;

[: [cast value]]

The forms above are invalid because no immediate builtin target exists.

Optional receiving contexts

A T? receiving context casts to inner T, then applies normal contextual wrapping to T?.

Optional source values are not automatically unwrapped.

In:

target T? = cast source catch:
    then fallback
;

the recovery handler produces inner T. then none is invalid because recovery happens before optional wrapping.

Numeric and text details

  • String -> Int and String -> Float consume the whole string.
  • Numeric text rejects surrounding whitespace, uppercase exponent markers, unary plus, malformed separators, malformed exponents, NaN and infinity spellings.
  • String -> Float additionally rejects a grammar-valid non-finite result.
  • String -> Int and Float -> Int must materialise inside -2147483648 through 2147483647.
  • Float -> String normalizes -0.0 to 0 and uses lowercase exponent formatting when exponential notation is required.
  • Contextual Int -> Float promotion remains separate from explicit casting, though explicit cast is valid from a naturally Int source to a Float target.
Choose the explanation level: Fallible casts

Fallible casts

Some conversions can fail. Text might not contain a number and a Float might not fit inside Int.

Use cast! when the current function already returns Error!:

parse_count |text String| -> Int, Error!:
    return cast! text
;

Use catch when this part of the program has a sensible fallback:

count Int = cast text catch:
    then 0
;

Choose one path. A cast cannot use ! and catch at the same time.

A fallible cast can propagate through builtin Error! or recover locally.

Propagation

parse_count |text String| -> Int, Error!:
    return cast! text
;

cast! requires fallible cast evidence and a compatible builtin Error! return path.

Local recovery

count Int = cast text catch:
    then 0
;

other_count Int = cast text catch |err|:
    io.line(err.message)
    then 0
;

A recovery handler must produce the success value required by the receiving site or leave explicitly with return or return!.

Rules

  • cast! expression catch: is invalid.
  • Propagation and local recovery are mutually exclusive.
  • cast! and cast ... catch: handle only cast failures.
  • If the operand itself produces Error!, handle or propagate that operand before applying the cast.
  • Optional receiving contexts recover inner T, then wrap the successful or recovered value into T?.
  • then none is invalid in a cast recovery handler targeting T?.

Plain cast is not a promise that every source-target pair is infallible. It requires infallible evidence for the selected pair.

Choose the explanation level: Cast evidence

Cast evidence

The compiler already knows the built-in conversions.

A user-defined struct or choice can opt into a supported target by providing the required method and declaring explicit conformance.

Label = |
    text String,
|

to_string |this Label| -> String:
    return this.text
;

Label must CASTABLE_TO_STRING

Now a Label can be cast to String at a typed destination.

This stays explicit. Matching method names alone do not create cast support.

Compiler-owned static traits prove cast availability.

Every builtin target has one infallible trait and one fallible trait.

  • Int: CASTABLE_TO_INT requires to_int |This| -> Int; TRY_CASTABLE_TO_INT requires try_to_int |This| -> Int, Error!
  • Float: CASTABLE_TO_FLOAT requires to_float |This| -> Float; TRY_CASTABLE_TO_FLOAT requires try_to_float |This| -> Float, Error!
  • Bool: CASTABLE_TO_BOOL requires to_bool |This| -> Bool; TRY_CASTABLE_TO_BOOL requires try_to_bool |This| -> Bool, Error!
  • String: CASTABLE_TO_STRING requires to_string |This| -> String; TRY_CASTABLE_TO_STRING requires try_to_string |This| -> String, Error!
  • Char: CASTABLE_TO_CHAR requires to_char |This| -> Char; TRY_CASTABLE_TO_CHAR requires try_to_char |This| -> Char, Error!
  • Error: CASTABLE_TO_ERROR requires to_error |This| -> Error; TRY_CASTABLE_TO_ERROR requires try_to_error |This| -> Error, Error!

The table shows trait requirement signatures. A source-authored implementation uses an ordinary lowercase this receiver of the concrete type.

Builtin evidence

The compiler registers evidence for the supported builtin source-target table.

Source-authored evidence

A same-file nominal struct, choice or aligned generic nominal constructor may add evidence for a supported builtin target.

It must provide the required receiver method and declare explicit conformance.

Label = |
    text String,
|

to_string |this Label| -> String:
    return this.text
;

Label must CASTABLE_TO_STRING

label = Label("hello")
text String = cast label

Contract

  • Core cast traits resolve without dependency clauses.
  • They cannot be redeclared.
  • They cannot be imported, exported, aliased or shadowed.
  • They are static contracts, not ordinary value types.
  • Matching method shape alone is not evidence. Explicit conformance is required.
  • Same-file nominal source types may provide explicit evidence.
  • Aligned generic nominal constructors may provide evidence for their concrete instances.
  • Infallible and fallible trait pairs for the same target are incompatible through compiler-owned must not metadata.
  • Evidence is resolved before HIR and backend lowering.

Outside cast design scope

  • user-defined cast target types
  • generic cast target parameters
  • external opaque cast targets
  • generic cast traits
  • broad return-type-directed conversion
  • implicit conversion chosen from distant context

Cast traits prove source evidence only. They do not create a general user conversion framework.