Program Structure
Hello, World
program hello
implicit none
print *, "Hello, World!"
end program hellodef main() raises:
print("Hello, World!")A Fortran program is a single
program name ... end program name block. Mojo, like Python, always begins execution at a specific function named main, marked raises since almost any Mojo function that does real work might raise an error — there is no equivalent of Fortran needing no special execution-entry marker at all.Comments
program comments_demo
implicit none
! This is a full-line comment
integer :: total ! This is a trailing comment
total = 10
print *, total
end program comments_demodef main() raises:
# This is a full-line comment
var total = 10 # This is a trailing comment
print(total)Fortran comments use
!; Mojo, inheriting Python's syntax, uses # — both run to the end of the line, and neither language has a block-comment syntax.Ahead-of-Time Compilation (Both Agree)
program compile_demo
implicit none
integer :: value
value = 42
print *, value
end program compile_demodef main() raises:
var value = 42
print(value)Despite Mojo's Python-like syntax, it compiles ahead of time to native machine code, just like Fortran —
mojo build produces a standalone binary. This is a deliberate design choice distinguishing Mojo from Python's interpreter, aimed squarely at Fortran and C's traditional performance-critical audience.⚠ IMPLICIT NONE Is Mandatory; Mojo's var Is Optional
program implicit_none_demo
implicit none
integer :: count
count = 5
print *, count
end program implicit_none_demodef main() raises:
count = 5 # "var" is not required here — this alone declares count
print(count)Fortran with
implicit none makes explicit declaration truly mandatory — omitting a variable's declaration is a compile-time error, full stop. Mojo's var keyword looks like it plays the same enforcing role, but it does not: a bare assignment like count = 5 silently declares a new local variable, with no var required at all. var is best understood as an optional, explicit-declaration style choice in current Mojo, not a mandatory gate the way implicit none is in Fortran.Variables & Types
Type Declarations vs. Type Annotations
program type_annotation_demo
implicit none
character(len=20) :: name
integer :: age
real :: score
name = "Alice"
age = 30
score = 9.5
print *, trim(name), age, score
end program type_annotation_demodef main() raises:
var name: String = "Alice"
var age: Int = 30
var score: Float64 = 9.5
print(name, age, score)Both languages require declared types for every variable in their strict mode: Fortran's
character(len=20)/integer/real map onto Mojo's String/Int/Float64 — note Mojo capitalizes its type names, and Int is machine-word-sized (64-bit on modern hardware) rather than a fixed 32-bit width the way an unqualified Fortran integer typically is.PARAMETER vs. comptime
program parameter_demo
implicit none
real, parameter :: pi = 3.14159265358979
integer, parameter :: max_connections = 100
print *, "Pi:", pi
print *, "Max:", max_connections
end program parameter_democomptime PI: Float64 = 3.14159265358979
comptime MAX_CONNECTIONS: Int = 100
def main() raises:
print("Pi:", PI)
print("Max:", MAX_CONNECTIONS)Fortran's
parameter attribute and Mojo's comptime both create true compile-time constants substituted at compile time, rejecting any attempt at reassignment. (Older Mojo code uses the now-deprecated alias keyword for the same thing — comptime is the current spelling.) Mojo comptime constants can additionally appear in compile-time parameter positions (such as a SIMD vector's width), a role Fortran's KIND-parameter system plays for numeric precision.Reassignment & Compound Assignment
program reassignment_demo
implicit none
integer :: counter
counter = 0
counter = 10
counter = counter + 5
print *, counter
end program reassignment_demodef main() raises:
var counter = 0
counter = 10
counter += 5
print(counter)Both languages reassign with a bare
=. Mojo additionally has compound assignment operators (+=, -=, and so on) inherited from Python, which Fortran lacks entirely — Fortran always spells out counter = counter + 5 in full.LOGICAL vs. Bool
program logical_demo
implicit none
logical :: is_ready
is_ready = .true.
if (is_ready) then
print *, "Ready"
end if
end program logical_demodef main() raises:
var isReady: Bool = True
if isReady:
print("Ready")Fortran's boolean literals are
.true./.false., surrounded by dots. Mojo's Bool type uses the capitalized Python-style literals True/False. Both are genuine boolean types requiring an actual boolean value in an if condition, with no truthy/falsy coercion of numbers or strings.Arithmetic
Integer Division & MOD
program division_demo
implicit none
integer :: numerator, denominator, quotient, remainder_value
numerator = 17
denominator = 5
quotient = numerator / denominator
remainder_value = mod(numerator, denominator)
print *, "Quotient:", quotient
print *, "Remainder:", remainder_value
end program division_demodef main() raises:
var numerator = 17
var denominator = 5
var quotient = numerator // denominator
var remainderValue = numerator % denominator
print("Quotient:", quotient)
print("Remainder:", remainderValue)Fortran's plain
/ performs integer division when both operands are integers. Mojo, inheriting Python's operators, uses a dedicated // for integer (floor) division and % for the remainder — plain / in Mojo always produces a floating-point result regardless of operand types, similar to the distinction R and Julia also make.** Operator (Both Agree)
program exponent_demo
implicit none
real :: result_value
result_value = 2.0 ** 10
print *, "2^10 =", result_value
end program exponent_demodef main() raises:
var resultValue = 2.0 ** 10
print("2^10 =", resultValue)Both languages agree here: Fortran and Mojo both use
** as a dedicated exponentiation operator, unlike C or Pascal, which have none at all (Mojo inherits this operator from Python).Math Intrinsics vs. math Module Import
program math_intrinsics_demo
implicit none
print *, "sqrt(144):", sqrt(144.0)
print *, "sin(0.0):", sin(0.0)
end program math_intrinsics_demofrom std.math import sqrt, sin
def main() raises:
print("sqrt(144):", sqrt(144.0))
print("sin(0.0):", sin(0.0))Fortran's
sqrt and sin are intrinsic — always available with no import. Mojo, like Python, requires an explicit from math import sqrt, sin before these functions can be used.KIND System vs. Sized Integer Types
program sized_integer_demo
use iso_fortran_env, only: int8, int64
implicit none
integer(kind=int8) :: tiny_number
integer(kind=int64) :: large_number
tiny_number = 127
large_number = 9000000000_int64
print *, tiny_number
print *, large_number
end program sized_integer_demodef main() raises:
var tinyNumber: Int8 = 127
var largeNumber: Int64 = 9000000000
print(tinyNumber)
print(largeNumber)Fortran's KIND-parameterized
integer(kind=int8)/integer(kind=int64) map onto Mojo's explicitly-sized Int8/Int64 types — both give the programmer precise control over storage width, chosen by picking a type name in Mojo versus a kind parameter in Fortran.Strings
String Concatenation (//) vs. (+)
program concat_demo
implicit none
character(len=20) :: first_name, last_name
character(len=41) :: full_name
first_name = "Alice"
last_name = "Smith"
full_name = trim(first_name) // " " // trim(last_name)
print *, trim(full_name)
end program concat_demodef main() raises:
var firstName: String = "Alice"
var lastName: String = "Smith"
var fullName = firstName + " " + lastName
print(fullName)Fortran concatenates with the dedicated
// operator. Mojo, like Python, reuses the ordinary + arithmetic operator for string concatenation. Mojo strings are dynamically sized with no declared maximum, unlike Fortran's fixed-width, blank-padded character(len=n) fields.LEN_TRIM vs. len()
program length_demo
implicit none
character(len=20) :: greeting
greeting = "Hello"
print *, "Trimmed length:", len_trim(greeting)
end program length_demodef main() raises:
var greeting: String = "Hello"
print(greeting.byte_length())Fortran needs
len_trim() to get the length ignoring trailing blank padding. Mojo's len() always returns the exact character count, since Mojo strings carry no fixed width or padding to account for.Case Conversion
program case_demo
implicit none
character(len=10) :: name
integer :: i, code
name = "alice"
do i = 1, len(name)
code = iachar(name(i:i))
if (code >= iachar('a') .and. code <= iachar('z')) then
name(i:i) = achar(code - 32)
end if
end do
print *, trim(name)
end program case_demodef main() raises:
var name: String = "alice"
print(name.upper())Fortran has no built-in case-conversion intrinsic — converting case requires a hand-written loop over character codes. Mojo, like Python, provides
.upper() and .lower() string methods directly, a case where Mojo's standard library is considerably more convenient for a common text operation.Substring Slicing vs. split()
program substring_demo
implicit none
character(len=11) :: phrase
phrase = "Hello World"
print *, phrase(1:5)
print *, phrase(7:11)
end program substring_demodef main() raises:
var phrase: String = "Hello World"
var words = phrase.split(" ")
print(words[0])
print(words[1])Fortran's
phrase(1:5) extracts a substring directly by numeric position with bracket syntax. Mojo's String type in current versions restricts plain bracket range-slicing (it requires an explicit, currently awkward byte-vs-codepoint disambiguation to stay UTF-8-safe), so the more robust everyday idiom is .split(delimiter), which returns a list of pieces — well suited to extracting words separated by a known delimiter, though less general than Fortran's arbitrary-position slice for extracting a substring at an exact numeric offset.Collections & SIMD
Fixed Array vs. Dynamic List
program array_declaration_demo
implicit none
integer :: numbers(4)
numbers = [10, 20, 30, 40]
print *, numbers(1)
print *, numbers(4)
end program array_declaration_demodef main() raises:
var numbers = List[Int]()
numbers.append(10)
numbers.append(20)
numbers.append(30)
numbers.append(40)
print(numbers[0])
print(numbers[3])Fortran arrays are fixed-size, declared with their length up front and filled with a literal in one step. Mojo's
List[Int] is dynamic and typed, built incrementally with .append() rather than a single literal — closer in spirit to Fortran's allocatable arrays than to a plain fixed-size integer :: numbers(4).⚠ 0-Indexed, and No Negative Indexing (Unlike Python)
program one_based_demo
implicit none
integer :: numbers(4)
numbers = [10, 20, 30, 40]
print *, "First:", numbers(1)
print *, "Last:", numbers(4)
end program one_based_demodef main() raises:
var numbers = List[Int]()
numbers.append(10)
numbers.append(20)
numbers.append(30)
numbers.append(40)
print("First:", numbers[0])
print("Last:", numbers[len(numbers) - 1])Fortran arrays default to 1-based indexing. Mojo's
List is 0-indexed, matching Python — but unlike Python, Mojo's List does not support negative indices at all in current versions, so numbers[-1] is not available; the last element must be reached with numbers[len(numbers) - 1], exactly the pattern Fortran's 1-based numbers(size(numbers)) already uses.No Fortran Equivalent — Dict
program manual_lookup_demo
implicit none
! Fortran has no built-in hash map / dictionary type at all.
! A key-value lookup requires a hand-rolled derived type with
! parallel arrays, or a third-party library.
character(len=10) :: names(2)
integer :: scores(2)
names = ["Alice ", "Bob "]
scores = [95, 87]
print *, trim(names(1)), scores(1)
end program manual_lookup_demodef main() raises:
var scores = Dict[String, Int]()
scores["Alice"] = 95
scores["Bob"] = 87
print(scores["Alice"])Fortran has no built-in hash map or dictionary type at all — an equivalent key-value lookup requires a hand-rolled derived type with parallel arrays, a manual search, or a third-party library. Mojo's
Dict[K, V], inherited conceptually from Python, provides one directly with familiar subscript syntax.Whole-Array Arithmetic vs. Explicit SIMD Types
program whole_array_demo
implicit none
real :: prices(4)
prices = [10.0, 20.0, 30.0, 40.0]
prices = prices * 0.9
print *, prices
end program whole_array_demodef main() raises:
var prices = SIMD[DType.float64, 4](10.0, 20.0, 30.0, 40.0)
var discounted = prices * 0.9
print(discounted)Fortran applies arithmetic to a whole array directly —
prices * 0.9 scales every element with no loop, and the compiler decides internally whether to use SIMD (single-instruction-multiple-data) CPU instructions. Mojo makes this explicit at the type level: SIMD[DType.float64, 4] is a vector type that maps directly onto a CPU SIMD register, and prices * 0.9 compiles to exactly one vectorized instruction rather than relying on the compiler to infer that opportunity — a more explicit, but also more constrained (the width must be a power of two), take on the same underlying hardware capability.SUM Intrinsic vs. reduce_add()
program sum_demo
implicit none
real :: numbers(4)
numbers = [1.0, 2.0, 3.0, 4.0]
print *, "Sum:", sum(numbers)
end program sum_demodef main() raises:
var numbers = SIMD[DType.float64, 4](1.0, 2.0, 3.0, 4.0)
var total = numbers.reduce_add()
print("Sum:", total)Fortran's
sum() intrinsic totals an array in a single call, letting the compiler vectorize internally. Mojo's .reduce_add() method on a SIMD vector performs the equivalent horizontal sum — on modern hardware, both compile down to genuinely vectorized machine code, just reached through different levels of explicitness.KIND Parameters vs. Compile-Time Parameters
program kind_parameter_demo
use iso_fortran_env, only: real64
implicit none
real(kind=real64) :: value
value = 3.14159265358979_real64
print *, value
end program kind_parameter_democomptime BATCH_SIZE: Int = 4
def sum_batch[batch_size: Int](values: SIMD[DType.float64, batch_size]) -> Float64:
return values.reduce_add()
def main() raises:
var batch = SIMD[DType.float64, BATCH_SIZE](10.0, 20.0, 30.0, 40.0)
print(sum_batch[BATCH_SIZE](batch))Fortran's KIND parameters (
real(kind=real64)) let the compiler generate code specialized to a chosen numeric precision, decided once and used consistently. Mojo's square-bracket compile-time parameters (fn sum_batch[batch_size: Int](...)) generalize this idea beyond just precision: the compiler generates a fully specialized version of the function for each distinct batch_size it is called with, including the exact SIMD width — a more general, user-extensible mechanism for the same underlying goal of compile-time specialization.Control Flow
IF / ELSE IF vs. if/elif/else
program if_demo
implicit none
integer :: score
score = 85
if (score >= 90) then
print *, "Grade: A"
else if (score >= 80) then
print *, "Grade: B"
else
print *, "Grade: C or below"
end if
end program if_demodef main() raises:
var score = 85
if score >= 90:
print("Grade: A")
elif score >= 80:
print("Grade: B")
else:
print("Grade: C or below")Fortran's
if / else if / else / end if and Mojo's Python-inherited if / elif / else read almost identically. Mojo uses indentation to delimit blocks, exactly like Python, rather than an explicit closing keyword like Fortran's end if..AND./.OR. vs. and/or
program logical_ops_demo
implicit none
logical :: is_weekday, is_holiday
is_weekday = .true.
is_holiday = .false.
if (is_weekday .and. .not. is_holiday) then
print *, "Office is open"
end if
end program logical_ops_demodef main() raises:
var isWeekday: Bool = True
var isHoliday: Bool = False
if isWeekday and not isHoliday:
print("Office is open")Fortran surrounds its logical operators with dots (
.and., .or., .not.). Mojo, like Python, spells them as bare lowercase words: and, or, not.SELECT CASE vs. an if/elif Chain
program select_case_demo
implicit none
integer :: day_number
day_number = 3
select case (day_number)
case (1)
print *, "Monday"
case (2)
print *, "Tuesday"
case (3)
print *, "Wednesday"
case default
print *, "Some other day"
end select
end program select_case_demodef main() raises:
var dayNumber = 3
if dayNumber == 1:
print("Monday")
elif dayNumber == 2:
print("Tuesday")
elif dayNumber == 3:
print("Wednesday")
else:
print("Some other day")Fortran has a dedicated
select case construct for matching one subject value against several branches. Mojo, inheriting Python's control flow, has no direct multi-way value-matching statement of its own — an if/elif chain of equality comparisons is the standard idiom, more verbose than Fortran's compact case labels but functionally equivalent.Loops
DO Loop vs. for/range
program do_loop_demo
implicit none
integer :: i
do i = 1, 5
print *, "Iteration:", i
end do
end program do_loop_demodef main() raises:
for i in range(1, 6):
print("Iteration:", i)Fortran's
do i = 1, 5 is an inclusive range. Mojo's range(1, 6), inherited from Python, is a half-open range — the upper bound is exclusive, so counting 1 through 5 needs range(1, 6), not range(1, 5).DO WHILE vs. while
program do_while_demo
implicit none
integer :: balance
balance = 1000
do while (balance > 0)
balance = balance - 300
print *, "Balance:", balance
end do
end program do_while_demodef main() raises:
var balance = 1000
while balance > 0:
balance -= 300
print("Balance:", balance)Fortran's
do while (condition) and Mojo's while condition: are functionally identical — both test the condition before each iteration.EXIT/CYCLE vs. break/continue
program exit_cycle_demo
implicit none
integer :: i
do i = 1, 10
if (mod(i, 2) == 0) cycle
if (i > 7) exit
print *, i
end do
end program exit_cycle_demodef main() raises:
for i in range(1, 11):
if i % 2 == 0:
continue
if i > 7:
break
print(i)Fortran's
cycle (skip to the next iteration) and exit (break out of the loop) map onto Mojo's Python-inherited continue and break — the same two concepts, different names for the first one.Free Index vs. Verbose range(len(...))
program indexed_iteration_demo
implicit none
character(len=6) :: fruits(3)
integer :: i
fruits = ["apple ", "pear ", "plum "]
do i = 1, 3
print *, i, trim(fruits(i))
end do
end program indexed_iteration_demodef main() raises:
var fruits = List[String]()
fruits.append("apple")
fruits.append("pear")
fruits.append("plum")
for index in range(len(fruits)):
print(index, fruits[index])A Fortran
do loop already gives you a numeric index for free, so iterating "with index" needs no special construct at all. Mojo's List currently has no built-in enumerate()-style helper (unlike Python), so the standard idiom is range(len(collection)) with subscript access — a case where Mojo is, for now, more verbose than Fortran for indexed iteration.Functions
⚠ There Is Only def Now (Mojo Deleted a Keyword)
program single_strict_mode_demo
implicit none
! Fortran (with implicit none) has exactly one mode: every
! function and every variable is strictly typed, everywhere,
! with no looser alternative available.
print *, square(7)
contains
function square(n) result(product_value)
integer, intent(in) :: n
integer :: product_value
product_value = n * n
end function square
end program single_strict_mode_demodef def_square(n: Int) -> Int:
# def now requires explicit types too, in current Mojo —
# def_square(n) with no annotation is a compile error here.
return n * n
def fn_square(n: Int) -> Int:
return n * n
def main() raises:
print(def_square(7))
print(fn_square(7))Older Mojo documentation describes two function keywords:
def for a loosely, dynamically typed Python-compatible mode, contrasted against a strictly typed fn. First the gap closed — an unannotated def parameter became a compile error. Then, in Mojo 1.0, fn was removed outright: writing it now gives error: 'fn' has been removed; use 'def' instead. There is one function keyword and it is the strict one. Fortran's single, uniform strictness under implicit none — one mode, applied everywhere, with no looser alternative to opt into — now describes Mojo exactly, which it did not when this page was first written.INTENT(INOUT) vs. mut Parameter
program intent_inout_demo
implicit none
integer :: first_number, second_number
first_number = 1
second_number = 2
call swap(first_number, second_number)
print *, first_number, second_number
contains
subroutine swap(first_value, second_value)
integer, intent(inout) :: first_value, second_value
integer :: temp_value
temp_value = first_value
first_value = second_value
second_value = temp_value
end subroutine swap
end program intent_inout_demodef swap(mut firstValue: Int, mut secondValue: Int):
var tempValue = firstValue
firstValue = secondValue
secondValue = tempValue
def main() raises:
var firstNumber = 1
var secondNumber = 2
swap(firstNumber, secondNumber)
print(firstNumber, secondNumber)This is the headline parallel between these two languages. Fortran's
intent(inout) and Mojo's mut parameter modifier serve the identical purpose: both let a function both read and modify the caller's actual argument in place, with the caller needing no special syntax at the call site (unlike C, which requires an explicit pointer and &). This is a far more direct correspondence than most languages compared to Fortran offer — Mojo's ownership-aware argument model was deliberately designed around the same "state your intent explicitly" philosophy Fortran has always had.INTENT(IN) vs. Default Borrowed Parameter
program intent_in_demo
implicit none
call display_score("Alice", 95)
contains
subroutine display_score(person_name, score)
character(len=*), intent(in) :: person_name
integer, intent(in) :: score
print *, trim(person_name), ": ", score
end subroutine display_score
end program intent_in_demodef displayScore(personName: String, score: Int):
# Without "mut", every parameter is borrowed (read-only) by
# default — the same default Fortran's intent(in) states explicitly
print(personName, ":", score)
def main() raises:
displayScore("Alice", 95)Fortran's
intent(in) is an explicit annotation stating a parameter is read-only. Mojo makes read-only the default: any parameter without the mut keyword is automatically borrowed immutably, so displayScore needs no annotation at all to get Fortran's intent(in) behavior — the safer default is simply assumed unless mut opts out of it.Multiple INTENT(OUT) vs. Returning a Tuple
program multiple_return_demo
implicit none
real :: area, circumference
call circle_stats(5.0, area, circumference)
print *, "Area:", area
print *, "Circumference:", circumference
contains
subroutine circle_stats(radius, area_out, circ_out)
real, intent(in) :: radius
real, intent(out) :: area_out, circ_out
real, parameter :: pi = 3.14159265358979
area_out = pi * radius * radius
circ_out = 2.0 * pi * radius
end subroutine circle_stats
end program multiple_return_demodef circleStats(radius: Float64) -> Tuple[Float64, Float64]:
comptime pi: Float64 = 3.14159265358979
var area = pi * radius * radius
var circumference = 2.0 * pi * radius
return (area, circumference)
def main() raises:
var area: Float64
var circumference: Float64
area, circumference = circleStats(5.0)
print("Area:", area)
print("Circumference:", circumference)Fortran returns multiple values through several
intent(out) parameters on a subroutine. Mojo, like Python, returns a genuine tuple that the caller destructures directly — no output-parameter convention needed at all, since a Mojo function can return more than one value as naturally as it returns one. The return type must be spelled out as Tuple[Float64, Float64] rather than bare parentheses, since Mojo requires an explicit, named type for every value, including compound ones.RECURSIVE Keyword vs. No Special Marking Needed
program recursive_demo
implicit none
print *, "5! =", factorial(5)
contains
recursive function factorial(n) result(product_value)
integer, intent(in) :: n
integer :: product_value
if (n <= 1) then
product_value = 1
else
product_value = n * factorial(n - 1)
end if
end function factorial
end program recursive_demodef factorial(n: Int) -> Int:
if n <= 1:
return 1
return n * factorial(n - 1)
def main() raises:
print("5! =", factorial(5))Fortran requires the explicit
recursive keyword before a function may call itself. Mojo has no such requirement — any function may call itself freely with no special declaration needed.Structs
Derived Type vs. struct
program derived_type_demo
implicit none
type :: point
real :: x
real :: y
end type point
type(point) :: origin
origin = point(1.0, 2.0)
print *, origin%x, origin%y
end program derived_type_demo@fieldwise_init
struct Point(Copyable, Movable):
var x: Float64
var y: Float64
def main() raises:
var origin = Point(1.0, 2.0)
print(origin.x, origin.y)A Fortran
type and a Mojo struct both group named, typed fields, accessed with a member operator — % in Fortran, . in Mojo. Both automatically generate a memberwise constructor from the declared fields in order (Fortran always does this; Mojo needs the explicit @fieldwise_init decorator, plus listing the Copyable and Movable traits that must be enabled deliberately).Value Semantics (Both Agree)
program value_semantics_demo
implicit none
type :: point
real :: x, y
end type point
type(point) :: original, copy
original = point(1.0, 2.0)
copy = original ! copies the VALUE — original and copy are independent
copy%x = 99.0
print *, "Original x:", original%x ! still 1.0
print *, "Copy x:", copy%x ! 99.0
end program value_semantics_demo@fieldwise_init
struct Point(ImplicitlyCopyable, Movable):
var x: Float64
var y: Float64
def main() raises:
var original = Point(1.0, 2.0)
var copy = original # copies the VALUE — original and copy are independent
copy.x = 99.0
print("Original x:", original.x) # still 1.0
print("Copy x:", copy.x) # 99.0Both languages agree here, unlike Python or Ruby: assigning a Fortran derived-type value or a Mojo struct value copies the entire value — the two variables become fully independent, and modifying one never affects the other. Mojo requires the type to explicitly list the
ImplicitlyCopyable trait to allow a plain = to copy it this way (the more basic Copyable trait alone permits only an explicit .copy() call), making the capability opt-in and precisely spelled out, rather than universal and implicit the way it is for every Fortran derived type.No Fortran Equivalent — Custom __init__
program constructor_call_demo
implicit none
type :: rectangle
real :: width
real :: height
end type rectangle
type(rectangle) :: rect
! Fortran's automatically-generated constructor takes fields
! positionally in declaration order — there is no way to write
! custom initialization logic inside the type definition itself.
rect = rectangle(3.0, 4.0)
print *, rect%width
end program constructor_call_demostruct Rectangle:
var width: Float64
var height: Float64
def __init__(out self, width: Float64, height: Float64):
# Custom logic could go here — validation, derived
# fields, logging — none of which Fortran's automatic
# constructor allows at all.
self.width = width
self.height = height
def main() raises:
var rectangle = Rectangle(3.0, 4.0)
print(rectangle.width)Fortran's derived-type constructor is always the automatically-generated, positional one — there is no way to write custom initialization logic (validation, computing a derived field, and so on) inside the type definition itself. Mojo structs support an explicit
fn __init__(out self, ...) method, letting arbitrary logic run during construction — the out self parameter marks that this function initializes a not-yet-existing instance, rather than receiving an already-built one.Free Functions vs. Instance Methods
program free_function_demo
implicit none
type :: circle
real :: radius
end type circle
type(circle) :: my_circle
my_circle = circle(5.0)
print *, area(my_circle)
contains
function area(shape) result(computed_area)
type(circle), intent(in) :: shape
real :: computed_area
computed_area = 3.14159265358979 * shape%radius ** 2
end function area
end program free_function_demostruct Circle:
var radius: Float64
def __init__(out self, radius: Float64):
self.radius = radius
def area(self) -> Float64:
return 3.14159265358979 * self.radius * self.radius
def main() raises:
var myCircle = Circle(5.0)
print(myCircle.area())A plain Fortran derived type has no methods bound to it by default —
area() is a free function taking the type as an ordinary parameter, called as area(my_circle). Mojo structs support genuine instance methods defined inside the struct body, called with dot syntax (myCircle.area()) much like Ruby or Python. (Later Fortran standards do support type-bound procedures for a similar dot-call style, but the plain free-function form remains the simpler, more common default shown here.)Error Handling
No Exceptions in Fortran vs. raises/raise
program status_flag_demo
implicit none
integer :: amount, status_flag
call withdraw(-5, amount, status_flag)
if (status_flag /= 0) then
print *, "Error: amount must be positive"
else
print *, amount
end if
contains
subroutine withdraw(requested_amount, amount_out, status_flag_out)
integer, intent(in) :: requested_amount
integer, intent(out) :: amount_out, status_flag_out
if (requested_amount < 0) then
status_flag_out = 1
amount_out = 0
else
status_flag_out = 0
amount_out = requested_amount
end if
end subroutine withdraw
end program status_flag_demodef withdraw(amount: Int) raises -> Int:
if amount < 0:
raise Error("amount must be positive")
return amount
def main() raises:
try:
print(withdraw(-5))
except error:
print("caught:", error)Fortran has no exception-handling mechanism — an error condition is communicated through a status flag the caller must check explicitly. Mojo's
raise Error("message") raises a genuine, catchable condition, and a function that might raise must declare raises in its signature (verified by the compiler along the whole call chain) — a recoverable middle ground Fortran has no equivalent of.try/except — Only One Error Type
program manual_check_demo
implicit none
print *, safe_divide(10.0, 2.0, .true.)
print *, safe_divide(10.0, 0.0, .false.)
contains
function safe_divide(numerator, denominator, should_succeed) result(quotient)
real, intent(in) :: numerator, denominator
logical, intent(in) :: should_succeed
real :: quotient
if (.not. should_succeed) then
quotient = -1.0 ! sentinel standing in for "error"
else
quotient = numerator / denominator
end if
end function safe_divide
end program manual_check_demodef safeDivide(numerator: Float64, denominator: Float64) raises -> Float64:
if denominator == 0.0:
raise Error("divide by zero")
return numerator / denominator
def main() raises:
try:
print(safeDivide(10.0, 2.0))
print(safeDivide(10.0, 0.0))
except error:
print("caught:", error)Since Fortran has no exception mechanism, distinguishing error kinds always means checking a sentinel value or a status code by convention. Mojo's
try/except catches a genuine Error condition — but unlike Python's rich exception hierarchy (ValueError, TypeError, and so on), Mojo currently has only one Error type carrying a plain message string, so except error: always catches everything with no type-based filtering available.Automatic Propagation vs. Manual Status Checks
program manual_propagation_demo
implicit none
integer :: status_flag
character(len=40) :: result_message
call initialise_app(result_message, status_flag)
if (status_flag /= 0) then
print *, "Setup failed:", trim(result_message)
else
print *, trim(result_message)
end if
contains
subroutine read_config(path, config_out, status_flag_out)
character(len=*), intent(in) :: path
character(len=20), intent(out) :: config_out
integer, intent(out) :: status_flag_out
if (path /= "app.yaml") then
status_flag_out = 1
config_out = ""
else
status_flag_out = 0
config_out = "config content"
end if
end subroutine read_config
subroutine initialise_app(message_out, status_flag_out)
character(len=40), intent(out) :: message_out
integer, intent(out) :: status_flag_out
character(len=20) :: config
call read_config("app.yaml", config, status_flag_out)
! Every caller must remember to check status_flag_out manually —
! nothing propagates it automatically.
if (status_flag_out == 0) then
message_out = "App initialised with " // trim(config)
end if
end subroutine initialise_app
end program manual_propagation_demodef readConfig(path: String) raises -> String:
if path != "app.yaml":
raise Error("File not found: " + path)
return "config content"
def initialiseApp() raises -> String:
var config = readConfig("app.yaml") # error propagates automatically
return "App initialised with " + config
def main() raises:
try:
print(initialiseApp())
except error:
print("Setup failed:", error)When a Fortran subroutine calls another that can fail, every caller in the chain must remember to check the status flag manually and decide what to do — nothing propagates automatically, and forgetting one check is a silent bug. When a Mojo
raises function calls another raises function without a try block, the error propagates up the call stack automatically, compiler-verified at every step by the raises annotation each function in the chain must declare.No finally in Either Language (Agreement)
program manual_cleanup_demo
implicit none
! Fortran has no "finally" block; cleanup code after a
! failure must be duplicated on every exit path manually.
print *, "trying"
print *, "cleanup"
end program manual_cleanup_demodef main() raises:
# Mojo's try/except also has no "finally" clause in current
# versions — cleanup code must be duplicated after both the
# success and the except paths, exactly like Fortran.
try:
print("trying")
except error:
print("caught:", error)
print("cleanup")Both languages agree here, unlike Python, Java, or Julia: neither Fortran nor current Mojo has a
finally clause that runs unconditionally regardless of success or failure. Cleanup code must be duplicated after every exit path by hand in both languages — a genuine similarity between an ancient language and one of the newest on this site.Modules & Interop
USE ... ONLY vs. from ... import
program module_use_demo
use iso_fortran_env, only: real64
implicit none
real(kind=real64) :: value
value = 3.14159265358979_real64
print *, value
end program module_use_demofrom std.math import pi
def main() raises:
var value = pi
print(value)Fortran's
use module_name, only: names and Mojo's Python-style from module import names both selectively import specific names from a larger module — a close syntactic parallel, differing mainly in keyword order and punctuation.No Fortran Equivalent — Calling Python Directly
program no_interop_demo
implicit none
! Fortran has no built-in mechanism for calling into a
! dynamic scripting language's ecosystem directly — bridging
! to Python from Fortran requires a separate binding layer
! (such as f2py) built and maintained outside the language itself.
print *, "No native Python interop exists in Fortran."
end program no_interop_demofrom python import Python
def main() raises:
var np = Python.import_module("numpy")
var array = np.array([1, 2, 3])
print(array)Fortran has no built-in mechanism for calling into a dynamic scripting language's ecosystem — bridging to Python from Fortran requires a separate tool such as f2py, built and maintained outside the language itself. Mojo can import and call arbitrary Python packages (including ones with no Mojo-native equivalent, like NumPy here) directly through
Python.import_module(), a genuinely unique capability with no Fortran parallel at all — a deliberate design choice letting Mojo code incrementally adopt the vast existing Python ecosystem.Output & Formatting
PRINT * vs. print() (Close Agreement)
program print_demo
implicit none
integer :: count
real :: price
count = 42
price = 9.99
print *, "Count:", count
print *, "Price:", price
end program print_demodef main() raises:
var count = 42
var price: Float64 = 9.99
print("Count:", count)
print("Price:", price)print *, ... in Fortran and print(...) in Mojo both accept a comma-separated list of values and print them on one line with automatic spacing between them and a trailing newline — one of the closer syntactic matches between the two languages.Internal Files vs. String Formatting
program internal_io_demo
implicit none
character(len=20) :: label_text
integer :: year
year = 2026
write(label_text, '("Report_", i4, ".txt")') year
print *, trim(label_text)
end program internal_io_demodef main() raises:
var year = 2026
var labelText = "Report_" + String(year) + ".txt"
print(labelText)Fortran builds a formatted string by writing to a character variable used as an "internal file," with a FORMAT descriptor controlling the layout. Mojo has no distinct formatted-write mechanism of its own yet — string building goes through ordinary concatenation and explicit conversion (
String(year) converts an Int to its text representation), the same approach a Rubyist would use with to_s before string interpolation became idiomatic.⚠ Gotchas for Fortran Programmers
⚠ Mojo Documentation Ages Fast — Verify Against the Real Compiler
program stable_language_demo
implicit none
! Fortran 2018 has been a fixed, ratified standard since 2018 —
! example code from a ten-year-old Fortran tutorial still
! compiles and means exactly the same thing today.
real, parameter :: pi = 3.14159265358979
print *, pi
end program stable_language_democomptime pi: Float64 = 3.14159265358979
# The now-deprecated "comptime" keyword still compiles (with a
# warning) as of this compiler, but a Mojo tutorial from even a
# few months ago may show "comptime" as the ONLY correct form —
# Mojo is still pre-1.0 and its syntax is actively changing.
def main() raises:
print(pi)Fortran 2018 is a fixed, ratified standard — example code from a decade-old tutorial still compiles today with the same meaning. Mojo is still pre-1.0 and evolving quickly: the compile-time-constant keyword has moved from
alias to comptime, len() on a String has been withdrawn as ambiguous (it now insists you choose byte_length(), codepoints or graphemes), the math module has moved to std.math, and the fn keyword has been deleted from the language — all during this page's own lifetime. Assumptions carried over from earlier Mojo material stop holding remarkably fast. A Fortran programmer's instinct that "the language reference from a few years ago is still accurate" does not transfer to Mojo yet — always verify against the actual compiler in use.⚠ Only SIMD Gets Whole-Array Arithmetic, Not List
program array_arithmetic_always_works_demo
implicit none
real :: prices(4)
! EVERY Fortran array supports whole-array arithmetic — there
! is no separate "vector type" needed to get this behavior.
prices = [10.0, 20.0, 30.0, 40.0]
prices = prices * 0.9
print *, prices
end program array_arithmetic_always_works_demodef main() raises:
var prices = List[Float64]()
prices.append(10.0)
prices.append(20.0)
prices.append(30.0)
prices.append(40.0)
# prices * 0.9 would NOT compile — List has no elementwise
# multiply operator. Only SIMD types get that treatment.
for i in range(len(prices)):
prices[i] = prices[i] * 0.9
for i in range(len(prices)):
print(prices[i])Every Fortran array, with no exceptions, supports whole-array arithmetic — there is no separate "vector type" needed to unlock it. In Mojo, only the dedicated
SIMD type gets elementwise operators; the general-purpose List type does not support * or + across its elements at all, so scaling every element of a List still requires an explicit loop, just like C or Pascal. A Fortran programmer's reflex that "arrays always support whole-array math" only transfers to Mojo's SIMD type, not to its everyday dynamic collections.Mojo Requires the Modular SDK — No Bare-Metal Local Runner Yet
program locally_compiled_demo
implicit none
! Fortran runs against a mature, widely-packaged local
! toolchain (gfortran) available on virtually every platform
! through the OS package manager.
print *, "gfortran is a standard package on most systems."
end program locally_compiled_demodef main() raises:
# Running this example locally requires installing the
# Modular SDK (curl -s https://get.modular.com | sh) — Mojo
# is younger and has a narrower toolchain distribution story
# than Fortran's decades-old, universally-packaged gfortran.
print("Modular SDK required for local compilation.")This is a maturity gap rather than a language-design one, but worth knowing: gfortran is a mature, decades-old compiler packaged by virtually every OS distribution and package manager. Mojo, being far newer, requires installing the Modular SDK directly rather than using a system package manager — installing it is a deliberate step rather than one line of a package manager, which is the practical difference a newcomer meets first.