Documentation / Casts

Casts

Casts stay explicit and target-driven. The receiving type says where the value goes. cast, cast! or catch says how the program handles conversion failure.

This keeps conversion local. You can see the source, target and failure path without asking the compiler to guess from distant code.

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 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
  • assertions
  • 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 imports.
  • 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.