puts "Hello, World!" fn main() raises:
print("Hello, World!") Every Mojo program starts with fn main(), the required entry point. The raises qualifier allows the function to propagate errors — it is a good habit to include it so that any function called from main can use raise freely. Mojo's print() works like Python's: it adds a newline after each call, just like Ruby's puts.
puts "First"
puts "Second"
puts "Third" fn main() raises:
print("First")
print("Second")
print("Third") Mojo uses Python-style significant indentation. Statements inside a function body must be indented consistently. Each print() call outputs one line, like Ruby's puts.
name = "Alice"
age = 30
puts "#{name} is #{age} years old" fn main() raises:
var name = "Alice"
var age = 30
print(name, "is", age, "years old") Mojo's print() accepts multiple arguments and joins them with spaces, similar to Python. Mojo 0.26 does not have string interpolation syntax like Ruby's #{} or Python's f-strings; the idiomatic approach is to pass multiple values directly to print().
# Single-line comment
puts "Hello" # inline comment
=begin
Multi-line
comment block
=end fn main() raises:
# Single-line comment
print("Hello") # inline comment
# Mojo has no block comment syntax;
# use consecutive line comments instead. Mojo uses # for line comments, identical to Ruby and Python. Unlike Ruby, Mojo has no built-in block comment syntax such as =begin / =end. Multiple consecutive line comments are the standard approach for longer explanatory text.
name = "Alice"
count = 42
temperature = 98.6
puts name, count, temperature fn main() raises:
var name = "Alice"
var count = 42
var temperature = 98.6
print(name, count, temperature) Mojo requires the var keyword to declare a new variable — unlike Ruby, where bare assignment creates a variable implicitly. The type is inferred from the initial value, so var count = 42 creates an Int without an explicit annotation.
# Ruby has no built-in type annotation syntax
name = "Alice"
age = 30
height = 5.9
puts "#{name}, #{age}, #{height}" fn main() raises:
var name: String = "Alice"
var age: Int = 30
var height: Float64 = 5.9
print(name, age, height) Mojo supports optional type annotations in the form var name: Type = value. The annotation is redundant when the type can be inferred, but it serves as documentation and will cause a compile error if the assigned value does not match. Ruby has no equivalent built-in syntax.
count = 0
count = 10
count += 5
puts count fn main() raises:
var count = 0
count = 10
count += 5
print(count) var variables in Mojo are mutable by default and can be reassigned freely, just like local variables in Ruby. Compound assignment operators like +=, -=, and *= are all supported.
MAX_CONNECTIONS = 100
APP_NAME = "MyApp"
puts MAX_CONNECTIONS
puts APP_NAME alias MAX_CONNECTIONS: Int = 100
alias APP_NAME: String = "MyApp"
fn main() raises:
print(MAX_CONNECTIONS)
print(APP_NAME) alias creates a compile-time constant, similar to Ruby's capitalized constants. Because alias values are resolved at compile time rather than runtime, they can be used in places that require compile-time known values — for example, as SIMD type parameters. Note that alias with an explicit type annotation is deprecated in some Mojo versions but remains functional in 0.26.
whole = 10
decimal = whole.to_f / 3.0
text = whole.to_s
puts decimal
puts text + " items" fn main() raises:
var whole: Int = 10
var decimal = Float64(whole) / 3.0
var text = String(whole)
print(decimal)
print(text + " items") Mojo does not perform implicit type coercion between numeric types. Converting an Int to a Float64 requires an explicit constructor call such as Float64(whole), similar to Ruby's .to_f. Converting to String uses String(value), like Ruby's .to_s.
# Ruby has one Integer type (arbitrary precision)
small = 42
large = 1_000_000_000
puts small.class
puts large fn main() raises:
var machine_word: Int = 42
var signed_64: Int64 = 1_000_000_000
var signed_32: Int32 = 32_767
print(machine_word)
print(signed_64)
print(signed_32) Mojo has multiple integer types sized to specific bit widths: Int8, Int16, Int32, Int64, and their unsigned counterparts. The plain Int type is machine-word-sized (64-bit on a 64-bit system) and is the most common choice. Unlike Ruby, which uses a single arbitrary-precision Integer, Mojo integers can overflow.
pi = 3.14159265358979
small_pi = 3.14
puts pi
puts small_pi.class fn main() raises:
var pi: Float64 = 3.14159265358979
var approximate: Float32 = 3.14
print(pi)
print(approximate) Mojo provides Float16, Float32, and Float64 types. The default floating-point literal type is Float64, equivalent to Ruby's Float. Use Float32 when you need to reduce memory usage or take advantage of SIMD hardware that prefers 32-bit floats.
is_active = true
is_done = false
puts is_active
puts !is_active
puts is_active && !is_done fn main() raises:
var is_active: Bool = True
var is_done: Bool = False
print(is_active)
print(not is_active)
print(is_active and not is_done) Mojo's Bool type uses Python-style capitalized literals True and False, unlike Ruby's lowercase true and false. Boolean operators use English words (and, or, not) rather than symbols, which is another Python-style difference from Ruby's &&, ||, !.
def find_score(scores, name)
scores[name] # returns nil if not found
end
scores = { "Alice" => 95 }
result = find_score(scores, "Bob")
puts result.nil? ? "Not found" : result fn safe_parse(text: String) -> Optional[Int]:
if text == "42":
return 42
return None
fn main() raises:
var found = safe_parse("42")
if found:
print(found.value())
var missing = safe_parse("abc")
print(missing.or_else(0)) Optional[T] is Mojo's equivalent of Ruby's nilable references. A function returning Optional[T] can return either a value or None. Use .value() to extract the value when you know it is present, or .or_else(default) to provide a fallback — similar to Ruby's &. operator and || default pattern.
greeting = "Hello"
name = "World"
combined = greeting + ", " + name + "!"
puts combined
puts combined.length fn main() raises:
var greeting = "Hello"
var name = "World"
var combined = greeting + ", " + name + "!"
print(combined)
print(len(combined)) String concatenation uses + in both Ruby and Mojo. Mojo uses the built-in len() function for string length, inherited from Python's style, whereas Ruby uses the .length or .size method. All Mojo strings are UTF-8 encoded and immutable by default.
message = "Hello, World!"
puts message.upcase
puts message.downcase fn main() raises:
var message = "Hello, World!"
print(message.upper())
print(message.lower()) Mojo's String type uses Python-style method names: .upper() and .lower() rather than Ruby's .upcase and .downcase. Both languages return a new string; the original is not modified.
sentence = "The quick brown fox"
puts sentence.include?("quick")
puts sentence.index("fox")
puts sentence.gsub("fox", "cat") fn main() raises:
var sentence = "The quick brown fox"
print(sentence.find("quick") != -1)
print(sentence.find("fox"))
print(sentence.replace("fox", "cat")) Mojo's .find() returns the byte index of the first occurrence, or -1 if not found — similar to Ruby's .index(). Use != -1 to check for containment; Mojo does not have a separate .include? method. The .replace() method replaces all occurrences, like Ruby's .gsub.
padded = " hello "
puts padded.strip
words = "one two three".split
puts words.length fn main() raises:
var padded = " hello "
print(padded.strip())
var sentence = "one two three"
var words = sentence.split()
print(len(words)) .strip() removes leading and trailing whitespace, equivalent to Ruby's .strip. .split() without arguments splits on whitespace and returns a List[String], like Ruby's .split. Both methods follow Python naming conventions rather than Ruby's.
filename = "report.csv"
puts filename.start_with?("report")
puts filename.end_with?(".csv")
puts filename.end_with?(".txt") fn main() raises:
var filename = "report.csv"
print(filename.startswith("report"))
print(filename.endswith(".csv"))
print(filename.endswith(".txt")) Mojo uses Python-style method names .startswith() and .endswith(), compared to Ruby's .start_with? and .end_with?. The Mojo versions return a Bool value and follow the same semantics.
numbers = [10, 20, 30, 40]
puts numbers[0]
puts numbers[-1]
puts numbers.length fn main() raises:
var numbers = List[Int]()
numbers.append(10)
numbers.append(20)
numbers.append(30)
numbers.append(40)
print(numbers[0])
print(numbers[len(numbers) - 1])
print(len(numbers)) Mojo's List[T] is a typed, resizable array. Unlike Ruby's array literals ([1, 2, 3]), Mojo requires explicit construction and type parameter. Mojo lists are zero-indexed like Ruby's, but Mojo 0.26 does not support negative indices — use len(list) - 1 for the last element.
fruits = ["apple", "banana", "cherry"]
fruits.each { |fruit| puts fruit }
fruits.each_with_index do |fruit, index|
puts "#{index}: #{fruit}"
end fn main() raises:
var fruits = List[String]()
fruits.append("apple")
fruits.append("banana")
fruits.append("cherry")
for fruit in fruits:
print(fruit)
for index in range(len(fruits)):
print(index, fruits[index]) Iterating over a Mojo List with a for loop yields the element value directly. Index-based iteration using range(len(list)) also works and gives access to the index, similar to Ruby's each_with_index. Direct subscript access (list[index]) returns the value without any dereference step.
scores = { "Alice" => 95, "Bob" => 87 }
puts scores["Alice"]
scores["Charlie"] = 92
puts scores.length fn main() raises:
var scores = Dict[String, Int]()
scores["Alice"] = 95
scores["Bob"] = 87
print(scores["Alice"])
scores["Charlie"] = 92
print(len(scores)) Dict[K, V] is Mojo's typed hash map, equivalent to Ruby's Hash. Like Ruby, subscript assignment (dict["key"] = value) adds or updates entries, and subscript access returns the value. Accessing a missing key in Mojo raises an error at runtime, whereas Ruby returns nil — use .get() for safe access.
config = { "host" => "localhost", "port" => "5432" }
host = config.fetch("host", "127.0.0.1")
timeout = config.fetch("timeout", "30")
puts host
puts timeout fn main() raises:
var config = Dict[String, String]()
config["host"] = "localhost"
config["port"] = "5432"
var host = config.get("host", "127.0.0.1")
var timeout = config.get("timeout", "30")
print(host)
print(timeout) Dict.get(key, default) returns the value for key if present, otherwise returns the default — equivalent to Ruby's Hash#fetch(key, default). This is the safe alternative to subscript access, which raises an error when the key is missing.
grades = { "Alice" => "A", "Bob" => "B", "Charlie" => "C" }
grades.each do |name, grade|
puts "#{name}: #{grade}"
end fn main() raises:
var grades = Dict[String, String]()
grades["Alice"] = "A"
grades["Bob"] = "B"
grades["Charlie"] = "C"
for key in grades.keys():
var name = String(key)
print(name, grades[name]) Iterating over dict.keys() yields key values that can be used for lookup. Because the iterator holds a reference into the dict's own memory, accessing grades[key] while iterating can trigger an aliasing error — copy the key first with String(key) to get an independent value. Mojo 0.26 does not guarantee dictionary insertion order.
score = 85
if score >= 90
puts "A"
elsif score >= 80
puts "B"
elsif score >= 70
puts "C"
else
puts "F"
end fn main() raises:
var score = 85
if score >= 90:
print("A")
elif score >= 80:
print("B")
elif score >= 70:
print("C")
else:
print("F") Mojo uses Python-style elif instead of Ruby's elsif. Conditional blocks are delimited by indentation rather than end keywords. The comparison operators (>=, ==, !=) are identical to Ruby's.
(1..5).each { |number| puts number }
(0...10).step(2) { |number| puts number } fn main() raises:
for number in range(1, 6):
print(number)
for number in range(0, 10, 2):
print(number) Mojo uses Python's range(start, stop, step) for numeric loops. The stop value is exclusive, like Ruby's ... range. The three-argument form sets the step. Unlike Ruby, there is no step method on ranges — the step is a third argument to range().
countdown = 5
while countdown > 0
puts countdown
countdown -= 1
end
puts "Go!" fn main() raises:
var countdown = 5
while countdown > 0:
print(countdown)
countdown -= 1
print("Go!") Mojo's while loop is syntactically identical to Python's. The loop body is delimited by indentation rather than Ruby's do / end or braces. Unlike Ruby, Mojo has no until keyword — use while not condition: instead.
(1..10).each do |number|
next if number % 2 == 0
break if number > 7
puts number
end fn main() raises:
for number in range(1, 11):
if number % 2 == 0:
continue
if number > 7:
break
print(number) Mojo uses continue (Python-style) for what Ruby calls next, and break is the same in both languages. Unlike Ruby, Mojo's break cannot return a value — it only exits the loop.
colors = ["red", "green", "blue"]
colors.each_with_index do |color, index|
puts "#{index}: #{color}"
end fn main() raises:
var colors = List[String]()
colors.append("red")
colors.append("green")
colors.append("blue")
for index in range(len(colors)):
print(index, colors[index]) Mojo 0.26 does not have a built-in equivalent of Ruby's each_with_index or Python's enumerate() for lists. The standard approach is to iterate over range(len(collection)) and access elements by index. Both for element in list: and list[index] give you the element value directly.
def greet(name)
"Hello, #{name}!"
end
puts greet("Alice") def greet(name: String) -> String:
return "Hello, " + name + "!"
fn main() raises:
print(greet("Alice")) Mojo's def functions are Python-style: they support type annotations but do not require them, and can raise errors without declaring raises. Unlike fn functions, def functions may implicitly copy arguments and can use dynamic types. Ruby's def is the closest analogy — both are flexible and dynamically typed by default.
def greet(name = "World", punctuation = "!")
"Hello, #{name}#{punctuation}"
end
puts greet
puts greet("Alice")
puts greet("Bob", ".") def greet(name: String = "World", punctuation: String = "!") -> String:
return "Hello, " + name + punctuation
fn main() raises:
print(greet())
print(greet("Alice"))
print(greet("Bob", ".")) Default argument syntax is nearly identical between Ruby and Mojo's def functions. The Mojo version requires type annotations alongside default values. Unlike Ruby, Mojo does not support keyword-only arguments in def functions (you cannot call greet(punctuation: ".")).
def min_max(numbers)
[numbers.min, numbers.max]
end
low, high = min_max([3, 1, 4, 1, 5, 9, 2])
puts low
puts high def min_max(first: Int, second: Int, third: Int) -> Tuple[Int, Int]:
var minimum = first
var maximum = first
if second < minimum:
minimum = second
if second > maximum:
maximum = second
if third < minimum:
minimum = third
if third > maximum:
maximum = third
return (minimum, maximum)
fn main() raises:
var result = min_max(3, 7, 1)
print(result[0])
print(result[1]) Mojo functions return multiple values via Tuple[T1, T2], accessed by numeric index (result[0]). Ruby returns multiple values as an array, which can be destructured automatically. Mojo 0.26 does not support automatic tuple destructuring in assignment (var low, high = min_max(...) is not valid syntax).
def factorial(number)
return 1 if number <= 1
number * factorial(number - 1)
end
puts factorial(6) def factorial(number: Int) -> Int:
if number <= 1:
return 1
return number * factorial(number - 1)
fn main() raises:
print(factorial(6)) Mojo supports recursion in both def and fn functions. The recursive call syntax is identical to Ruby. Unlike Ruby, Mojo does not perform tail-call optimization, so deeply recursive calls can overflow the stack.
# Ruby has no equivalent of fn; all methods are dynamically typed
def add(x, y)
x + y
end
puts add(3, 4)
puts add(1.5, 2.5) fn add(left: Int, right: Int) -> Int:
return left + right
fn main() raises:
print(add(3, 4)) fn functions require explicit type annotations for all parameters and the return type. They are stricter than def functions: arguments are not copied implicitly, borrowed by default. Where a Ruby method accepts any type, an fn function is locked to specific types — you would need a separate overload to handle Float64 arguments.
# Ruby passes objects by reference-value; modifying inside is visible outside
def double_in_place(numbers)
numbers.map! { |n| n * 2 }
end
numbers = [1, 2, 3]
double_in_place(numbers)
puts numbers.inspect fn double_in_place(mut numbers: List[Int]):
for index in range(len(numbers)):
numbers[index] = numbers[index] * 2
fn main() raises:
var numbers = List[Int]()
numbers.append(1)
numbers.append(2)
numbers.append(3)
double_in_place(numbers)
for number in numbers:
print(number) The mut keyword on a parameter declares that the function can modify the caller's variable. Without mut, the parameter is borrowed immutably and cannot be changed. This is explicit where Ruby implicitly shares mutable object references — in Mojo, the mutation contract is visible at the call site.
def parse_age(text)
age = Integer(text)
raise ArgumentError, "Age cannot be negative" if age < 0
age
rescue ArgumentError => error
raise error
end
puts parse_age("25") fn parse_age(text: String) raises -> Int:
var age = atol(text)
if age < 0:
raise Error("Age cannot be negative")
return age
fn main() raises:
print(parse_age("25")) An fn function that can raise an error must declare raises in its signature, unlike Ruby where any method can raise at any time. This makes the error contract explicit and visible to callers. The atol() built-in (ASCII to long) converts a String to Int and itself raises if parsing fails.
# Ruby uses duck typing instead of overloading
def describe(value)
if value.is_a?(Integer)
"Integer: #{value}"
elsif value.is_a?(Float)
"Float: #{value}"
else
"String: #{value}"
end
end
puts describe(42)
puts describe(3.14) fn describe(value: Int) -> String:
return "Integer: " + String(value)
fn describe(value: Float64) -> String:
return "Float: " + String(value)
fn main() raises:
print(describe(42))
print(describe(3.14)) Mojo supports function overloading: multiple functions with the same name but different parameter types. The compiler selects the correct version at compile time based on the argument types. Ruby achieves similar behavior through duck typing and is_a? checks at runtime — Mojo does the same work statically.
Point = Struct.new(:x, :y)
point = Point.new(3.0, 4.0)
puts point.x
puts point.y @fieldwise_init
struct Point(Copyable, Movable):
var x: Float64
var y: Float64
fn main() raises:
var point = Point(3.0, 4.0)
print(point.x)
print(point.y) The @fieldwise_init decorator generates a constructor that accepts one argument per field in declaration order. Copyable and Movable are built-in traits that allow the struct to be copied and moved. This is similar to Ruby's Struct.new, which also auto-generates accessors and a constructor. Mojo structs are value types — assigning one struct to another copies it, unlike Ruby objects which are always reference types.
class Rectangle
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
end
end
rect = Rectangle.new(3.0, 4.0)
puts rect.width struct Rectangle:
var width: Float64
var height: Float64
fn __init__(out self, width: Float64, height: Float64):
self.width = width
self.height = height
fn main() raises:
var rectangle = Rectangle(3.0, 4.0)
print(rectangle.width) Mojo structs use fn __init__(out self, ...) for explicit constructors. The out self parameter indicates that the function constructs and initializes the struct — it is Mojo's equivalent of Ruby's initialize. Fields are accessed via self.field_name inside the struct, and instance.field_name from outside (like Ruby's instance variables with attr_reader).
class Circle
def initialize(radius)
@radius = radius
end
def area
Math::PI * @radius ** 2
end
def circumference
2 * Math::PI * @radius
end
end
circle = Circle.new(5.0)
puts circle.area.round(4) from math import pi
struct Circle:
var radius: Float64
fn __init__(out self, radius: Float64):
self.radius = radius
fn area(self) -> Float64:
return pi * self.radius * self.radius
fn circumference(self) -> Float64:
return 2.0 * pi * self.radius
fn main() raises:
var circle = Circle(5.0)
print(circle.area()) Read-only methods take self (borrowed, immutable). Mojo imports standard library functions like pi explicitly — unlike Ruby, where Math::PI is always available. Methods are called with instance.method(), always requiring parentheses even for zero-argument methods (unlike Ruby).
class Counter
def initialize
@count = 0
end
def increment
@count += 1
end
def value
@count
end
end
counter = Counter.new
counter.increment
counter.increment
counter.increment
puts counter.value struct Counter:
var count: Int
fn __init__(out self):
self.count = 0
fn increment(mut self):
self.count += 1
fn value(self) -> Int:
return self.count
fn main() raises:
var counter = Counter()
counter.increment()
counter.increment()
counter.increment()
print(counter.value()) Methods that modify the struct's state must declare mut self instead of self. This is explicit where Ruby has no distinction — any Ruby method can modify @instance_variables. In Mojo, calling a mut self method on an immutable binding would be a compile error, making mutation intent visible in the type signature.
class Person
def initialize(name, age)
@name = name
@age = age
end
def to_s
"Person(#{@name}, #{@age})"
end
end
person = Person.new("Alice", 30)
puts person struct Person(Stringable):
var name: String
var age: Int
fn __init__(out self, name: String, age: Int):
self.name = name
self.age = age
fn __str__(self) -> String:
return "Person(" + self.name + ", " + String(self.age) + ")"
fn main() raises:
var person = Person("Alice", 30)
print(String(person)) Implementing the Stringable trait and defining fn __str__(self) -> String gives a struct a string representation, analogous to Ruby's to_s method. Use String(instance) to invoke __str__ and get the string — Mojo 0.26 does not expose a standalone str() function. Passing the struct directly to print() requires implementing the Writable trait instead.
module Greetable
def greet
raise NotImplementedError, "#{self.class} must implement greet"
end
end
module Farewell
def farewell
"Goodbye from #{self.class}"
end
end trait Greetable:
fn greet(self) -> String: ...
trait Printable:
fn display(self): ...
fn main() raises:
pass A Mojo trait declares a set of required method signatures — similar to Ruby modules used as interfaces, but with compile-time enforcement. The ... body means "no default implementation; implementors must define this." Unlike Ruby modules, Mojo traits cannot contain instance variables or default method bodies in 0.26.
module Describable
def describe
raise NotImplementedError
end
end
class Dog
include Describable
def initialize(name)
@name = name
end
def describe
"Dog named #{@name}"
end
end
dog = Dog.new("Rex")
puts dog.describe trait Describable:
fn describe(self) -> String: ...
@fieldwise_init
struct Dog(Describable, Copyable, Movable):
var name: String
fn describe(self) -> String:
return "Dog named " + self.name
fn main() raises:
var dog = Dog("Rex")
print(dog.describe()) A struct implements a trait by listing it in parentheses after the struct name (struct Dog(Describable, ...)) and providing all required methods. The compiler verifies that every trait method is implemented — unlike Ruby, where missing included methods only raise at runtime when called. Multiple traits can be listed together with built-in traits like Copyable and Movable.
module Shape
def area
raise NotImplementedError
end
end
class Square
include Shape
def initialize(side) = @side = side
def area = @side ** 2
end
class Circle
include Shape
def initialize(radius) = @radius = radius
def area = Math::PI * @radius ** 2
end
[Square.new(4.0), Circle.new(3.0)].each { |shape| puts shape.area.round(4) } from math import pi
trait Shape:
fn area(self) -> Float64: ...
@fieldwise_init
struct Square(Shape, Copyable, Movable):
var side: Float64
fn area(self) -> Float64:
return self.side * self.side
@fieldwise_init
struct Circle(Shape, Copyable, Movable):
var radius: Float64
fn area(self) -> Float64:
return pi * self.radius * self.radius
fn print_area[T: Shape](shape: T):
print(shape.area())
fn main() raises:
var square = Square(4.0)
var circle = Circle(3.0)
print_area(square)
print_area(circle) Mojo achieves polymorphism via parametric functions: fn print_area[T: Shape](shape: T) accepts any type T that implements Shape. The square brackets [T: Shape] are compile-time type parameters — the compiler generates a specialized version for each concrete type, like C++ templates. This differs from Ruby's runtime duck typing, but produces the same behavior with zero runtime overhead.
class Temperature
include Comparable
attr_reader :celsius
def initialize(celsius)
@celsius = celsius
end
def <=>(other)
@celsius <=> other.celsius
end
end
temps = [Temperature.new(100), Temperature.new(37), Temperature.new(0)]
puts temps.min.celsius
puts temps.sort.map(&:celsius).inspect @fieldwise_init
struct Temperature(Copyable, Movable, Comparable):
var celsius: Float64
fn __lt__(self, other: Temperature) -> Bool:
return self.celsius < other.celsius
fn __le__(self, other: Temperature) -> Bool:
return self.celsius <= other.celsius
fn __gt__(self, other: Temperature) -> Bool:
return self.celsius > other.celsius
fn __ge__(self, other: Temperature) -> Bool:
return self.celsius >= other.celsius
fn __eq__(self, other: Temperature) -> Bool:
return self.celsius == other.celsius
fn __ne__(self, other: Temperature) -> Bool:
return self.celsius != other.celsius
fn main() raises:
var boiling = Temperature(100.0)
var body = Temperature(37.0)
var freezing = Temperature(0.0)
print(boiling > body)
print(freezing < body) Mojo's built-in traits include Copyable, Movable, Stringable, Comparable, and others. The Comparable trait requires implementing all six comparison dunder methods. Ruby achieves the same result by implementing only <=> and including Comparable — Mojo requires explicit implementations for each operator.
def check_positive(number)
raise ArgumentError, "Must be positive" if number <= 0
number
end
puts check_positive(5) fn check_positive(number: Int) raises -> Int:
if number <= 0:
raise Error("Must be positive")
return number
fn main() raises:
print(check_positive(5)) Mojo's raise Error("message") is the equivalent of Ruby's raise. Unlike Ruby's hierarchy of exception classes (ArgumentError, RuntimeError, etc.), Mojo 0.26 has a single Error type. The function signature must declare raises whenever it can raise, making the error contract part of the public API.
def risky_divide(dividend, divisor)
raise ZeroDivisionError, "Cannot divide by zero" if divisor == 0
dividend.to_f / divisor
end
begin
puts risky_divide(10, 2)
puts risky_divide(5, 0)
rescue ZeroDivisionError => error
puts "Caught: #{error.message}"
end fn risky_divide(dividend: Float64, divisor: Float64) raises -> Float64:
if divisor == 0.0:
raise Error("Cannot divide by zero")
return dividend / divisor
fn main() raises:
try:
print(risky_divide(10.0, 2.0))
print(risky_divide(5.0, 0.0))
except error:
print("Caught:", error) Mojo uses try / except (Python-style) instead of Ruby's begin / rescue. The caught value in except error: is of type Error. Unlike Ruby, Mojo cannot catch specific error subtypes — all raised errors are the same Error type in 0.26, so there is no equivalent of rescue ZeroDivisionError.
def read_config(path)
raise IOError, "File not found: #{path}" unless File.exist?(path)
"config content"
end
def initialize_app
config = read_config("app.yaml")
"App initialized with #{config}"
end
begin
puts initialize_app
rescue IOError => error
puts "Setup failed: #{error.message}"
end fn read_config(path: String) raises -> String:
if path != "app.yaml":
raise Error("File not found: " + path)
return "config content"
fn initialize_app() raises -> String:
var config = read_config("app.yaml")
return "App initialized with " + config
fn main() raises:
try:
print(initialize_app())
except error:
print("Setup failed:", error) When a function marked raises calls another raises function without a try block, the error propagates automatically up the call stack — identical to Ruby's default behavior. The raises annotation on each function in the chain makes the propagation path explicit and compiler-verified.
def process_data(data)
raise "Empty data" if data.empty?
data.upcase
end
begin
result = process_data("hello")
puts "Success: #{result}"
rescue => error
puts "Error: #{error.message}"
end fn process_data(data: String) raises -> String:
if len(data) == 0:
raise Error("Empty data")
return data.upper()
fn main() raises:
try:
var result = process_data("hello")
print("Success:", result)
except error:
print("Error:", error) Mojo 0.26 does not have an ensure / finally clause equivalent. Error handling uses try / except blocks. Cleanup logic that must run regardless of success or failure is typically placed after the try block, or managed via Mojo's lifetime system using destructors (fn __del__(owned self)).
class BankAccount
def initialize(balance)
@balance = balance
end
def withdraw(amount)
raise "Insufficient funds" if amount > @balance
@balance -= amount
@balance
end
end
account = BankAccount.new(100)
puts account.withdraw(40) struct BankAccount:
var balance: Float64
fn __init__(out self, balance: Float64):
self.balance = balance
fn withdraw(mut self, amount: Float64) raises -> Float64:
if amount > self.balance:
raise Error("Insufficient funds")
self.balance -= amount
return self.balance
fn main() raises:
var account = BankAccount(100.0)
print(account.withdraw(40.0)) Struct methods can be declared raises just like standalone functions. A method that both mutates the struct and can raise combines mut self with raises in the signature. This mirrors Ruby's pattern of instance methods that modify state and raise on invalid input.
# Ruby has no built-in SIMD; this shows equivalent serial computation
prices = [10.0, 20.0, 30.0, 40.0]
discounted = prices.map { |price| price * 0.9 }
puts discounted.inspect fn main() raises:
var prices = SIMD[DType.float64, 4](10.0, 20.0, 30.0, 40.0)
var discounted = prices * 0.9
print(discounted) SIMD[DType, width] is a vector type that maps directly to CPU SIMD instructions. Operations like * 0.9 are applied to all elements in a single instruction, rather than Ruby's sequential loop. The width must be a power of two and match what the target hardware supports (4 or 8 Float64 elements is typical on x86-64). This is one of Mojo's headline features — C-level performance in high-level syntax.
# Ruby serial operations
vector_a = [1.0, 2.0, 3.0, 4.0]
vector_b = [5.0, 6.0, 7.0, 8.0]
total = vector_a.zip(vector_b).sum { |a, b| a + b }
puts total fn main() raises:
var vector_a = SIMD[DType.float64, 4](1.0, 2.0, 3.0, 4.0)
var vector_b = SIMD[DType.float64, 4](5.0, 6.0, 7.0, 8.0)
var combined = vector_a + vector_b
var total = combined.reduce_add()
print(total) SIMD types support all standard arithmetic operators element-wise: +, -, *, /. The .reduce_add() method sums all elements in the vector into a scalar, equivalent to Ruby's .sum. On modern hardware, the 4-element addition is performed in a single CPU instruction instead of four separate additions.
# Ruby determines array size at runtime
def process_batch(items)
items.each_slice(4).map { |batch| batch.sum }
end
puts process_batch([1, 2, 3, 4, 5, 6, 7, 8]).inspect alias BATCH_SIZE: Int = 4
fn sum_batch[size: Int](values: SIMD[DType.int64, size]) -> Int64:
return values.reduce_add()
fn main() raises:
var batch = SIMD[DType.int64, BATCH_SIZE](10, 20, 30, 40)
print(sum_batch[BATCH_SIZE](batch)) Square-bracket parameters like [size: Int] are compile-time parameters. They are resolved when the compiler generates code, unlike function arguments which are resolved at runtime. Using alias constants as type parameters (like SIMD[DType.int64, BATCH_SIZE]) allows the vector width to be defined once and reused without any runtime overhead.
# Ruby can call C extensions, but not Python libraries directly
require "json"
data = JSON.parse('{"name": "Alice"}')
puts data["name"] from python import Python
fn main() raises:
var json = Python.import_module("json")
var data = json.loads('{"name": "Alice"}')
print(data["name"]) Mojo can import and call any Python library directly via from python import Python and Python.import_module(). This requires a CPython installation to be present at runtime. The example above cannot run in Compiler Explorer because the CE sandbox does not include a Python runtime — it works in a full Mojo installation. Ruby has no equivalent facility for calling Python code.