Mojo Type Casting

Type casting converts a value from one data type to another. Mojo requires explicit casting — it never silently converts between incompatible types. This keeps programs predictable and prevents subtle bugs caused by unexpected automatic conversions.

Why Explicit Casting Matters

Implicit casting (Python / JavaScript style):
  result = 5 + 2.5   → Python automatically widens 5 to 5.0
  This hides what is happening and can introduce precision bugs.

Explicit casting (Mojo style):
  result = Float64(5) + 2.5   → you state the intent clearly
  The compiler knows exactly what types are in play at every step.

Numeric Casting

Cast between numeric types by wrapping the value in the target type's constructor.

fn main():
    var i: Int = 42
    var f: Float64 = Float64(i)   # Int → Float64
    print(f)    # 42.0

    var pi: Float64 = 3.14159
    var trunc: Int = Int(pi)      # Float64 → Int (truncates, does NOT round)
    print(trunc)  # 3
Truncation vs Rounding:
  Float64(3.9) → Int(3.9) = 3   (drops .9, does NOT round up)
  Float64(3.1) → Int(3.1) = 3   (drops .1)
  Float64(-2.7)→ Int(-2.7)= -2  (drops .7, moves toward zero)

  To round before truncating:
    import math
    Int(math.round(3.9))  # → 4

Integer Size Casting

fn main():
    var big: Int64 = 1_000_000_000
    var small: Int32 = Int32(big)   # narrow cast — may overflow!
    print(small)   # 1000000000 (safe here, within Int32 range)

    var too_big: Int64 = 3_000_000_000
    var overflow: Int32 = Int32(too_big)   # wraps around — undefined behavior
    print(overflow)   # garbage value — do not do this
Safe narrowing check:
  Int32 max = 2,147,483,647
  If your Int64 value > 2,147,483,647, do NOT cast to Int32.
  Always verify the value fits in the target range before a narrow cast.

Signed and Unsigned Casting

fn main():
    var signed: Int8 = -1
    var unsigned: UInt8 = UInt8(signed)   # -1 in Int8 = 255 in UInt8
    print(unsigned)   # 255

    var u: UInt8 = 200
    var s: Int8 = Int8(u)   # 200 in UInt8 = -56 in Int8 (two's complement)
    print(s)   # -56
Two's complement reinterpretation:
  Int8  range: -128 to 127
  UInt8 range:    0 to 255

  Bit pattern 11111111:
    As Int8:  = -1
    As UInt8: = 255

Float Precision Casting

fn main():
    var precise: Float64 = 3.141592653589793
    var reduced: Float32 = Float32(precise)   # loses precision
    var half: Float16    = Float16(precise)   # loses more precision

    print(precise)   # 3.141592653589793
    print(reduced)   # 3.1415927   (Float32 has ~7 significant digits)
    print(half)      # 3.14        (Float16 has ~3 significant digits)
Precision loss diagram:
  Float64  ──→  3.141592653589793  (15 significant digits)
  Float32  ──→  3.1415927          ( 7 significant digits)
  Float16  ──→  3.14               ( 3 significant digits)

  Widening (Float16 → Float64): safe, no data lost
  Narrowing (Float64 → Float16): loses precision — intentional trade-off

Casting to and from String

Numeric → String

fn main():
    var age: Int = 28
    var score: Float64 = 98.5
    var flag: Bool = True

    var s1 = String(age)    # "28"
    var s2 = String(score)  # "98.5"
    var s3 = String(flag)   # "True"

    print("Age: " + s1)
    print("Score: " + s2)

String → Numeric

fn main() raises:
    var text = "42"
    var number = Int(text)       # "42" → 42
    print(number + 1)            # 43

    var ftext = "3.14"
    var fnum = Float64(ftext)    # "3.14" → 3.14
    print(fnum * 2.0)            # 6.28

    # Parsing a non-numeric string raises an error:
    try:
        var bad = Int("hello")
    except e:
        print("Cannot parse:", str(e))

SIMD Type Casting

SIMD vectors can be cast between element types using the cast() method. This converts each lane independently.

fn main():
    var v_int = SIMD[DType.int32, 4](1, 2, 3, 4)
    var v_flt = v_int.cast[DType.float32]()   # each int → float
    print(v_flt)   # [1.0, 2.0, 3.0, 4.0]

    var v_f64 = SIMD[DType.float64, 4](1.9, 2.7, 3.1, 4.5)
    var v_i32 = v_f64.cast[DType.int32]()    # truncates each lane
    print(v_i32)   # [1, 2, 3, 4]
SIMD cast diagram (float → int, width=4):
  Input:  [1.9, 2.7, 3.1, 4.5]
            │     │     │     │
          cast  cast  cast  cast   (truncate per lane)
            │     │     │     │
  Output: [  1,   2,   3,   4  ]

Casting Rules Summary

Conversion              | Safety      | Data Loss?
------------------------|-------------|---------------------------
Int8 → Int64            | Safe        | None (widening)
Int64 → Int8            | Risky       | Overflow if value too large
Float32 → Float64       | Safe        | None (widening)
Float64 → Float32       | Precision   | Decimal digits lost
Float64 → Int           | Truncates   | Fractional part dropped
Int → Float64           | Safe        | None for most Int values
UInt8 → Int8            | Reinterpret | Sign may flip
String → Int            | Can fail    | Raises if not numeric
Int → String            | Safe        | None

Practical Example: Temperature Converter

fn celsius_to_fahrenheit(c: Float64) -> Float64:
    return c * 9.0 / 5.0 + 32.0

fn main():
    var temps_int = List[Int](0, 20, 37, 100)

    for i in range(len(temps_int)):
        var c = Float64(temps_int[i])     # Int → Float64 for arithmetic
        var f = celsius_to_fahrenheit(c)
        var f_int = Int(f)                # Float64 → Int to display whole number
        print(String(temps_int[i]) + "°C = " + String(f_int) + "°F")

Output:

0°C = 32°F
20°C = 68°F
37°C = 98°F
100°C = 212°F

Key Takeaways

Mojo requires explicit type casting — nothing converts silently. Wrap the value in the target type constructor to cast: Float64(my_int), Int(my_float), String(my_num). Widening casts (small → large type) are always safe. Narrowing casts (large → small) risk overflow or precision loss — verify the value fits first. Float-to-Int truncates toward zero, it does not round. String-to-number conversion can fail at runtime if the string is not a valid number, so wrap it in try/except. SIMD vectors cast all lanes simultaneously with .cast[DType]().

Leave a Comment

Your email address will not be published. Required fields are marked *