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 Makes It an Error; Mojo Only Warns
program implicit_none_demo
implicit none
integer :: count
count = 5
print *, count
end program implicit_none_demodef main() raises:
var count = 5 # without "var" this still compiles, with a deprecation warning
print(count)Fortran with
implicit none makes explicit declaration truly mandatory — omitting a variable's declaration is a compile-time error, full stop. Mojo stops short of that: a bare assignment like count = 5 still compiles, and the compiler warns that implicit declaration is deprecated and asks for var. Write var every time, as this example does, and treat the warning as the error it is on its way to becoming in your own code.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 import before these functions can be used — from std.math import sqrt, sin, since the standard library lives under std.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 strings carry no padding either, but they are UTF-8, so Mojo asks which length you mean: len() on a String is a compile error, and byte_length() gives the byte count used here.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 rejects s[a:b] outright — the compiler explains that in UTF-8 a numeric range is ambiguous between bytes and characters — so the 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] = [10, 20, 30, 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 rejects a negative index — numbers[-1] is a compile error — so the last element is 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 (def 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 writes the same dispatch the way Python traditionally does — an if/elif chain of equality comparisons — 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.A Free Index vs. enumerate()
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] = ["apple", "pear", "plum"]
for index, fruit in enumerate(fruits):
print(index, fruit)A Fortran
do loop already gives you a numeric index for free, so iterating "with index" needs no special construct at all. Mojo iterates the elements themselves, and enumerate() pairs each with its index — Python's idiom, and just as short as the Fortran loop.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
def __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 vs. Checking a Status Flag
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, and the value it catches has the type the called function raises: Error, carrying a message, as here, or a struct of your own when the function declares one — raises DivideByZero — which lets the caller read its fields.Automatic Propagation vs. Manual Status Checks
program manual_propagation_demo
implicit none
integer :: status_flag
character(len=40) :: result_message
call initialize_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 initialize_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 initialized with " // trim(config)
end if
end subroutine initialize_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 initializeApp() raises -> String:
var config = readConfig("app.yaml") # error propagates automatically
return "App initialized with " + config
def main() raises:
try:
print(initializeApp())
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.⚠ Fortran Has No finally — Mojo Now Does
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 has a finally clause: this runs on every exit path,
# where the Fortran beside it has to repeat the line by hand.
try:
print("trying")
except error:
print("caught:", error)
finally:
print("cleanup")This row used to record an agreement: neither language had a
finally clause, so cleanup had to be repeated by hand on every exit path in both. Mojo 1.0 has one — measured running on the body-succeeded, exception-caught and early-return paths — so the agreement is gone, and only Fortran still duplicates the line. Fortran offers no equivalent at all: no finally, no destructors, no defer, so the repetition in the left column is not a style choice but the only option the language gives you.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 builds the same string by 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
# "comptime" is the current keyword. A Mojo tutorial from even a
# few months ago shows "alias" instead, which still compiles but
# warns that it is deprecated — Mojo reached 1.0 recently and its
# syntax is still moving.
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 reached 1.0 recently and is still evolving quickly: the compile-time-constant keyword
alias has been deprecated in favor of 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 — 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.Installing Mojo: From PyPI, With the Version Pinned
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:
# Mojo installs from PyPI rather than the OS package manager,
# and pinning the version keeps it matched to this page:
# pip install mojo==1.0.0
print("Mojo is installed from PyPI, not the OS package manager.")gfortran is a mature, decades-old compiler packaged by virtually every OS distribution. Mojo is installed from PyPI instead —
pip install mojo==1.0.0 — and the version pin is the part to remember: an unpinned install fetches the newest release the moment it is published, which can already differ from what a tutorial, or this page, was written for.