Variables and Types in Mojo
Learn about variables, data types, type inference, and the dual type system in Mojo with practical Docker-ready examples
Mojo’s type system is one of its most distinctive features. As a Python superset, Mojo supports both dynamic typing (Python-style) and static typing (systems-style) in the same language. This dual approach lets you write quick, flexible code when prototyping and switch to strict, high-performance code when it matters.
The key to understanding Mojo’s variables is the distinction between def and fn functions. In def functions, variables behave like Python—no declarations needed, types are flexible. In fn functions, every variable must be declared with var and types are enforced at compile time. This progressive typing model means you choose the level of strictness that fits your task.
In this tutorial, you’ll learn how to declare variables, work with Mojo’s built-in types, use type inference, define compile-time constants, and convert between types.
Variable Declarations and Built-In Types
Mojo provides the var keyword for declaring mutable variables. You can specify types explicitly or let the compiler infer them.
Create a file named variables.mojo:
def main():
# --- Explicit type annotations ---
var x: Int = 42
var temperature: Float64 = 98.5
var name: String = "Mojo"
var active: Bool = True
print("=== Typed Variables ===")
print("x =", x)
print("temperature =", temperature)
print("name =", name)
print("active =", active)
# --- Type inference: the compiler deduces the type ---
var count = 100 # Int
var ratio = 0.75 # Float64
var language = String("CodeArchaeology") # String
var done = False # Bool
print("\n=== Inferred Types ===")
print("count =", count)
print("ratio =", ratio)
print("language =", language)
print("done =", done)
# --- Mutability: var declares mutable variables ---
var score = 0
print("\n=== Mutability ===")
print("initial score:", score)
score = 100
score += 50
print("updated score:", score)
# --- Compile-time constants with alias ---
alias MAX_PLAYERS = 4
alias GRAVITY = 9.5
print("\n=== Compile-Time Constants (alias) ===")
print("MAX_PLAYERS =", MAX_PLAYERS)
print("GRAVITY =", GRAVITY)
What’s Happening Here
var x: Int = 42— Declares a mutable variable with an explicit type annotation.Intis Mojo’s native integer type, sized to the platform’s word size (64-bit on modern systems).var count = 100— The compiler infers the type from the value. This is just as efficient as an explicit annotation—the type is still known at compile time.var score = 0thenscore = 100— Variables declared withvarare mutable. You can reassign them freely.alias MAX_PLAYERS = 4— Creates a compile-time constant. Unlikevar,aliasvalues are resolved during compilation, not at runtime. They cannot be changed and are inlined wherever they’re used.
Mojo’s Built-In Types
| Type | Description | Example |
|---|---|---|
Int | Platform-sized integer (64-bit) | 42, -7, 0 |
Float64 | 64-bit floating point | 3.14, 0.5 |
Float32 | 32-bit floating point | For GPU/SIMD work |
String | Mutable string type | "hello" |
Bool | Boolean | True, False |
Int8, Int16, Int32 | Sized signed integers | For specific memory layouts |
UInt8, UInt16, UInt32, UInt64 | Unsigned integers | For bit manipulation, sizes |
StringLiteral | Compile-time string constant | String literals before conversion |
Type Conversions and def vs fn
The choice between def and fn directly affects how variables and types work. This example shows both styles along with type conversion patterns.
Create a file named variables_types.mojo:
def flexible_function():
"""In def functions, variables work like Python."""
x = 42
x = 99
print("def - x:", x)
fn strict_function():
"""In fn functions, var and types are required."""
var x: Int = 42
x = 99
print("fn - x:", x)
def main():
print("=== def vs fn ===")
flexible_function()
strict_function()
# --- Type conversions ---
print("\n=== Type Conversions ===")
# Int to Float64
var i: Int = 42
var f = Float64(i)
print("Int 42 as Float64:", f)
# Float64 to Int (truncates toward zero)
var pi: Float64 = 7.75
var truncated = Int(pi)
print("Float64 7.75 as Int:", truncated)
# Numeric to String
var num: Int = 256
var num_str = str(num)
print("Int 256 as String:", num_str)
# --- StringLiteral vs String ---
print("\n=== StringLiteral vs String ===")
var s: String = "hello" # StringLiteral implicitly converts to String
var s2: String = String("world") # Explicit conversion
print("s =", s)
print("s2 =", s2)
print("combined:", s + " " + s2)
def vs fn for Variables
| Feature | def | fn |
|---|---|---|
var keyword | Optional | Required |
| Type annotations | Optional | Required |
| Reassignment | Free (Python-style) | Only for var variables |
| Performance | Dynamic dispatch possible | Fully compiled, optimized |
When you write def, Mojo behaves like Python—great for scripting and prototyping. When you write fn, Mojo behaves like a systems language—the compiler enforces type safety and can generate maximally optimized code.
Type Conversions
Mojo uses constructor-style conversion: Float64(some_int), Int(some_float), str(some_value). Float-to-integer conversion truncates toward zero, just like C and Rust.
StringLiteral vs String
String literals like "hello" are StringLiteral values—compile-time constants baked into the binary. String is Mojo’s mutable, heap-allocated string type. Mojo implicitly converts StringLiteral to String when needed, but understanding the distinction helps when working with fn functions and strict type checking.
Running with Docker
| |
Expected Output
Output from variables.mojo:
=== Typed Variables ===
x = 42
temperature = 98.5
name = Mojo
active = True
=== Inferred Types ===
count = 100
ratio = 0.75
language = CodeArchaeology
done = False
=== Mutability ===
initial score: 0
updated score: 150
=== Compile-Time Constants (alias) ===
MAX_PLAYERS = 4
GRAVITY = 9.5
Output from variables_types.mojo:
=== def vs fn ===
def - x: 99
fn - x: 99
=== Type Conversions ===
Int 42 as Float64: 42.0
Float64 7.75 as Int: 7
Int 256 as String: 256
=== StringLiteral vs String ===
s = hello
s2 = world
combined: hello world
Key Concepts
vardeclares mutable variables — Required infnfunctions, optional indeffunctions. Allvarvariables can be reassigned.aliascreates compile-time constants — Values resolved at compilation, inlined into the binary. Use for configuration values and mathematical constants.- Type inference is zero-cost — Writing
var x = 42is exactly as efficient asvar x: Int = 42. The compiler knows the type either way. defvsfncontrols strictness —defgives Python flexibility;fngives systems-language performance and safety. You can mix both in the same program.- Progressive typing — Start with
deffor rapid prototyping, then migrate tofnwhen you need performance. No rewrite required—just add types. StringLiteralvsString— Literals are compile-time constants;Stringis a heap-allocated mutable type. Mojo converts between them automatically in most contexts.- Constructor-style conversions — Convert between types using
Float64(x),Int(x),str(x)rather than cast operators. - Sized integer types —
Int8,Int16,Int32,UInt8, etc. are available for memory-precise work, SIMD operations, and hardware interaction.
Running Today
All examples can be run using Docker:
docker pull codearchaeology/mojo:latest
Comments
Loading comments...
Leave a Comment