PONY λ M2 Modula-2

Fortran.CodeCompared.To/Mojo

An interactive executable cheatsheet comparing Fortran and Mojo

Fortran 2018 (GCC 15.2) Mojo 0.26.1
Program Structure
Hello, World
program hello implicit none print *, "Hello, World!" end program hello
fn 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_demo
fn 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_demo
fn 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_demo
fn 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 even inside a strict fn function, 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_demo
fn 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_demo
comptime PI: Float64 = 3.14159265358979 comptime MAX_CONNECTIONS: Int = 100 fn 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_demo
fn 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_demo
fn 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_demo
fn 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_demo
fn 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_demo
from math import sqrt, sin fn 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_demo
fn 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_demo
fn 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_demo
fn main() raises: var greeting: String = "Hello" print(len(greeting))
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_demo
fn 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_demo
fn 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_demo
fn 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_demo
fn 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_demo
fn 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_demo
fn 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_demo
fn 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_demo
comptime BATCH_SIZE: Int = 4 fn sum_batch[batch_size: Int](values: SIMD[DType.float64, batch_size]) -> Float64: return values.reduce_add() fn 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_demo
fn 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_demo
fn 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_demo
fn 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_demo
fn 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_demo
fn 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_demo
fn 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_demo
fn 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
⚠ def and fn Both Require Types (a Change from Older Mojo)
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_demo
def 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 fn fn_square(n: Int) -> Int: return n * n fn main() raises: print(def_square(7)) print(fn_square(7))
Older Mojo documentation (and this site's own Ruby/Python-anchored Mojo pages, written against an earlier build) describes def as a loosely, dynamically typed Python-compatible mode with no annotations required, contrasted against a strictly typed fn. On the current stable compiler this page targets, that gap has closed: a def function with an unannotated parameter is now a compile error ("argument type must be specified"), identical to fn. Fortran's single, uniform strictness under implicit none — one mode, applied everywhere, with no looser alternative to opt into — turns out to describe modern Mojo far more closely than it used to, at least for the basic type-checking behavior shown here.
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_demo
fn swap(mut firstValue: Int, mut secondValue: Int): var tempValue = firstValue firstValue = secondValue secondValue = tempValue fn 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_demo
fn 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) fn 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 fn 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_demo
fn 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) fn 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 fn functions can return more than one value as naturally as they return one. The return type must be spelled out as Tuple[Float64, Float64] rather than bare parentheses, since Mojo's strict fn functions require 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_demo
fn factorial(n: Int) -> Int: if n <= 1: return 1 return n * factorial(n - 1) fn main() raises: print("5! =", factorial(5))
Fortran requires the explicit recursive keyword before a function may call itself. Mojo has no such requirement — every fn and def 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 fn 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 fn 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.0
Both 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_demo
struct Rectangle: var width: Float64 var height: Float64 fn __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 fn 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_demo
struct Circle: var radius: Float64 fn __init__(out self, radius: Float64): self.radius = radius fn area(self) -> Float64: return 3.14159265358979 * self.radius * self.radius fn 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_demo
fn withdraw(amount: Int) raises -> Int: if amount < 0: raise Error("amount must be positive") return amount fn 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_demo
fn safeDivide(numerator: Float64, denominator: Float64) raises -> Float64: if denominator == 0.0: raise Error("divide by zero") return numerator / denominator fn 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_demo
fn readConfig(path: String) raises -> String: if path != "app.yaml": raise Error("File not found: " + path) return "config content" fn initialiseApp() raises -> String: var config = readConfig("app.yaml") # error propagates automatically return "App initialised with " + config fn 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_demo
fn 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_demo
from math import pi fn 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_demo
from python import Python fn 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_demo
fn 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_demo
fn 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_demo
comptime pi: Float64 = 3.14159265358979 # The now-deprecated "alias" keyword still compiles (with a # warning) as of this compiler, but a Mojo tutorial from even a # few months ago may show "alias" as the ONLY correct form — # Mojo is still pre-1.0 and its syntax is actively changing. fn 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 alone has already moved from alias to comptime during this page's own development, and several other assumptions carried over from earlier Mojo material (that def allows untyped parameters, that fn strictly requires var for every new local) no longer hold on the compiler this page targets. 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_demo
fn 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_demo
fn 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 — this site's own test suite runs every Mojo example through the remote Compiler Explorer API for exactly this reason, unlike Fortran, C, Pascal, and every other language on this page, which all run locally.