Variables
EZ uses two keywords for declaring variables: temp for mutable values and const for immutable values.
Mutable Variables (temp)
Use temp to declare variables that can be reassigned:
temp x int = 10
temp name string = "Alice"
temp isActive bool = true
// Reassignment is allowed
x = 20
name = "Bob"
isActive = false
Immutable Constants (const)
Use const to declare values that cannot change:
const PI float = 3.14159
const MAX_SIZE int = 100
const APP_NAME string = "MyApp"
// This would cause an error:
// PI = 3.14 // Error! Cannot reassign const
Type Inference for Constants
When assigning a function’s return value to a const, you can omit the type annotation and let EZ infer it:
import @io
do main() {
// Type is inferred from the function's return type
const content = io.read_file("config.txt")
const size = io.file_size("data.bin")
// Explicit types still required for literal values
const PI float = 3.14159
const MAX_SIZE int = 100
}
Note: Type inference for const only works with function return values. Literal values (numbers, strings, etc.) still require explicit type annotations.
Type Annotations
EZ is statically typed - you must declare the type:
temp count int = 0
temp price float = 19.99
temp message string = "Hello"
temp letter char = 'A'
temp enabled bool = true
Default Values
Uninitialized temp variables get default values:
temp count int // defaults to 0
temp price float // defaults to 0.0
temp message string // defaults to ""
temp letter char // defaults to '\0'
temp flag bool // defaults to false
Primitive Types
Integers
temp age int = 25
temp negative int = -100
temp zero int = 0
Sized Integers
EZ supports explicitly sized integers:
// Signed integers
temp small i8 = -128
temp medium i32 = -100000
temp large i64 = -9223372036854775808
temp huge i128 = 1000000000000
// Unsigned integers (cannot be negative)
temp byte u8 = 255
temp word u32 = 4294967295
temp big u64 = 18446744073709551615
Numeric Separators
Use underscores for readability in large numbers:
temp million int = 1_000_000
temp billion int = 7_800_000_000
temp pi float = 3.141_592_653
temp money float = 1_234.56
Floats
temp pi float = 3.14159
temp temperature float = -40.5
temp percentage float = 0.85
Strings
temp greeting string = "Hello, World!"
temp empty string = ""
temp multiword string = "This is a sentence."
Characters
temp letter char = 'A'
temp digit char = '5'
temp symbol char = '@'
temp newline char = '\n'
Booleans
temp isValid bool = true
temp hasError bool = false
Arrays
// Dynamic arrays
temp numbers [int] = {1, 2, 3, 4, 5}
temp names [string] = {"Alice", "Bob", "Charlie"}
temp empty [int] = {}
// Fixed-size arrays (must use const because their size is fixed at compile time)
const DAYS [string, 7] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
const PRIMES [int, 5] = {2, 3, 5, 7, 11}
Maps
temp ages map = {
{"Alice", 25},
{"Bob", 30}
}
temp scores map[string:int] = {"math": 95, "english": 88}
Empty Collections
Use {} for empty arrays and {:} for empty maps:
// Empty array
temp numbers [int] = {}
// Empty map - use {:} to distinguish from empty array
temp scores map[string:int] = {:}
The {:} syntax is necessary because {} alone is ambiguous - it could be an empty array or an empty map. The colon : signals that it’s a map (just like {"key": value} uses : for key-value pairs).
import @std
using std
do main() {
// Empty map, then add items
temp users map[string:int] = {:}
users["Alice"] = 25
users["Bob"] = 30
println("Users: ${users}") // {"Alice": 25, "Bob": 30}
println("Count: ${len(users)}") // 2
}
Blank Identifier
The underscore _ serves as a blank identifier for discarding unwanted values in multi-value assignments.
Basic Usage
// Discard the second return value
temp result, _ = divide_with_remainder(10, 3)
std.println(result) // 3
Multiple Discards
// Keep only the middle value
temp _, middle, _ = get_three_values()
Common Use Case: Ignoring Errors
do read_file(path string) -> string, string {
// Returns (content, error)
return "file content", ""
}
do main() {
// When you're confident there's no error:
temp content, _ = read_file("config.ez")
std.println(content)
}
Rules
_can appear multiple times in the same assignment- Values assigned to
_are evaluated but not stored _cannot be read from (it’s write-only)
Variable Scope
Variables are scoped to their containing block:
do main() {
temp x int = 10
if x > 5 {
temp y int = 20 // y only exists in this block
std.println(x + y)
}
// std.println(y) // Error! y is not in scope
}
Shadowing
Inner scopes can shadow outer variables:
do main() {
temp x int = 10
std.println(x) // 10
if true {
temp x int = 20 // shadows outer x
std.println(x) // 20
}
std.println(x) // 10 (original x)
}
Copy-by-Default Semantics
EZ uses copy-by-default for all assignments. When you assign a value to a new variable, you get an independent copy:
const Person struct {
name string
age int
}
do main() {
temp p1 Person = Person{name: "Alice", age: 30}
temp p2 Person = p1 // p2 is an independent copy
p2.age = 31
std.println(p1.age) // 30 - unchanged
std.println(p2.age) // 31
}
This applies to all types including:
- Primitives (int, float, string, bool, char)
- Structs
- Arrays
- Maps
Shared Data with ref()
When you need multiple variables to share the same data, use ref():
do main() {
temp p1 Person = Person{name: "Alice", age: 30}
temp p2 Person = ref(p1) // p2 references the same data
p2.age = 31
std.println(p1.age) // 31 - both changed!
std.println(p2.age) // 31
}
Use ref() when:
- Multiple variables need to share and modify the same data
- You want changes in one place to be visible everywhere
- You want to avoid copying overhead for large data structures
// Arrays with ref()
temp original [int] = {1, 2, 3}
temp shared [int] = ref(original)
shared[0] = 100
std.println(original[0]) // 100 - both share the same array
// Without ref() - independent copy
temp copied [int] = original
copied[1] = 200
std.println(original[1]) // 2 - original unchanged
Explicit Copying with copy()
While copy() is no longer required for independent copies (since that’s the default), it remains available for explicit intent:
temp a Person = Person{name: "Bob", age: 25}
temp b Person = copy(a) // Explicitly states "I want a copy"
// Same result as: temp b Person = a
Compound Assignment
temp count int = 10
count += 5 // count = 15
count -= 3 // count = 12
count *= 2 // count = 24
count /= 4 // count = 6
count %= 4 // count = 2
Increment and Decrement
temp i int = 0
i++ // i = 1
i++ // i = 2
i-- // i = 1
Type Conversion
Convert between types explicitly:
// String to number
temp str string = "42"
temp num int = int(str)
temp decimal float = float("3.14")
// Number to string
temp n int = 100
temp s string = string(n)
// Float to int (truncates)
temp pi float = 3.14159
temp whole int = int(pi) // 3
// Int to float
temp x int = 42
temp y float = float(x) // 42.0
Example Program
import @std
do main() {
// Constants
const TAX_RATE float = 0.08
const STORE_NAME string = "EZ Mart"
// Variables
temp subtotal float = 0.0
temp itemCount int = 0
// Add items
temp prices [float] = {9.99, 24.99, 4.99, 14.99}
for_each price in prices {
subtotal += price
itemCount++
}
temp tax float = subtotal * TAX_RATE
temp total float = subtotal + tax
std.println("=== ${STORE_NAME} ===")
std.println("Items: ${itemCount}")
std.println("Subtotal: $${subtotal}")
std.println("Tax (${TAX_RATE * 100}%): $${tax}")
std.println("Total: $${total}")
}