PONY λ M2 Modula-2

Fortran.CodeCompared.To/Julia

An interactive executable cheatsheet comparing Fortran and Julia

Fortran 2018 (GCC 15.2) Julia 1.12
Program Structure
Hello, World
program hello implicit none print *, "Hello, World!" end program hello
println("Hello, World!")
A Fortran program needs a program name ... end program name wrapper and a separate compile step. Julia needs neither — a top-level println() call is a complete, runnable script, JIT-compiled the moment it runs.
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
# This is a full-line comment total = 10 # This is a trailing comment println(total)
Fortran comments use !; Julia comments use #, both running to the end of the line. Julia additionally supports block comments delimited by #= and =#, which can be nested — Fortran has no block-comment syntax at all.
Ahead-of-Time Compilation vs. JIT
program jit_demo implicit none integer :: value value = 42 print *, value end program jit_demo
value = 42 println(value)
Fortran is compiled ahead of time to a standalone binary; running it means compile, then execute, as two separate steps. Julia is just-in-time (JIT) compiled: julia script.jl compiles each function to native machine code the first time it is called, during the same process that runs it — no separate binary is ever produced. This is why Julia programs are famously slow to start ("time to first plot") but fast once warmed up, a tradeoff Fortran's ahead-of-time model does not have.
IMPLICIT NONE vs. Optional Type Annotations
program implicit_none_demo implicit none integer :: count real :: average count = 5 average = 3.5 print *, count, average end program implicit_none_demo
count::Int64 = 5 average::Float64 = 3.5 println(count, " ", average)
Fortran with implicit none requires every variable to be declared with an explicit type. Julia type annotations (::Int64) are always optional — omitting them entirely is legal, and the variable simply takes on whatever type its assigned value has. Unlike Fortran, this is a choice made per-variable, not a whole-program setting.
Assignment
program assignment_demo implicit none integer :: count count = 5 count = count + 1 print *, count end program assignment_demo
count = 5 count = count + 1 println(count)
Both languages use a bare = for assignment — an identical, unremarkable mapping, unlike R's <- or Pascal's :=.
Variables & Types
PARAMETER vs. Const
program parameter_demo implicit none real, parameter :: pi = 3.14159265358979 real, parameter :: gravity = 9.81 print *, "Pi:", pi print *, "Gravity:", gravity end program parameter_demo
const PI = 3.14159265358979 const GRAVITY = 9.81 println("Pi: ", PI) println("Gravity: ", GRAVITY)
Fortran's parameter attribute and Julia's const both declare constants the compiler treats specially — though Julia's const is really a performance hint (it tells the JIT compiler the variable's type will never change, enabling better-optimized code) rather than an absolute reassignment ban; reassigning a Julia const to a value of the same type is legal, if discouraged by convention (hence the ALL-CAPS naming convention shown here).
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
is_ready = true if is_ready println("Ready") end
Fortran's boolean literals are .true./.false., surrounded by dots. Julia's are the bare lowercase words true/false. Both languages require a genuine boolean in an if condition — neither has Python-style truthiness for numbers or strings.
Multiple Assignment / Tuple Destructuring
program multiple_demo implicit none integer :: first_number, second_number, third_number first_number = 1 second_number = 2 third_number = 3 print *, first_number, second_number, third_number end program multiple_demo
firstNumber, secondNumber, thirdNumber = 1, 2, 3 println(firstNumber, " ", secondNumber, " ", thirdNumber)
Fortran needs one declaration and one assignment statement per variable (or a comma-separated declaration sharing one type). Julia can assign several names at once from a comma-separated tuple on the right-hand side — a form of destructuring with no Fortran equivalent at all.
No Fortran Equivalent — Nothing
program uninitialized_demo implicit none integer :: maybe_value ! Fortran has no "no value yet" marker — an unassigned ! variable simply holds indeterminate garbage until set. maybe_value = 0 ! must assign something before using it print *, maybe_value end program uninitialized_demo
maybeValue = nothing println(maybeValue === nothing) maybeValue = 42 println(maybeValue)
An unassigned Fortran variable holds indeterminate garbage — there is no built-in "not yet set" marker, and reading it before assignment is undefined behavior with no runtime check. Julia's nothing is a real, distinct singleton value that a variable can hold explicitly to mean "no value yet," checked safely with === nothing.
Arrays & Broadcasting
Array Literal
program array_literal_demo implicit none integer :: numbers(4) numbers = [1, 2, 3, 4] print *, numbers end program array_literal_demo
numbers = [1, 2, 3, 4] println(numbers)
Both languages build an array with square brackets and comma-separated elements — [1, 2, 3, 4] means exactly the same thing in both, right down to the bracket character.
1-Based Indexing (Both Languages Agree)
program one_based_demo implicit none integer :: letters(3) letters = [65, 66, 67] print *, letters(1) print *, letters(3) end program one_based_demo
letters = [65, 66, 67] println(letters[1]) println(letters[3])
Both Fortran and Julia index from 1 by default — a direct point of agreement (Julia deliberately chose this to match Fortran and MATLAB, its scientific-computing predecessors). Only the bracket character differs: parentheses in Fortran, square brackets in Julia.
Whole-Array Arithmetic — Both Elementwise Here
program elementwise_demo implicit none real :: prices(4) prices = [10.0, 20.0, 30.0, 40.0] prices = prices * 0.9 print *, prices end program elementwise_demo
prices = [10.0, 20.0, 30.0, 40.0] prices = prices .* 0.9 println(prices)
Scaling every element of an array by a plain number is elementwise in both languages — but notice the dot already: Julia requires .* even here, when multiplying an array by a scalar, because * is reserved for linear-algebra multiplication in Julia's type system. Fortran's plain * is always elementwise no matter what.
⚠ * Is Elementwise in Fortran, Matrix Multiplication in Julia
program matmul_demo implicit none integer :: a(2,2), b(2,2), elementwise_result(2,2), matrix_product(2,2) a = reshape([1, 2, 3, 4], [2,2]) b = reshape([5, 6, 7, 8], [2,2]) elementwise_result = a * b ! Fortran * is ALWAYS elementwise matrix_product = matmul(a, b) ! true matrix multiplication needs matmul() print *, "Elementwise:", elementwise_result print *, "Matrix product:", matrix_product end program matmul_demo
a = [1 3; 2 4] b = [5 7; 6 8] elementwiseResult = a .* b # Julia .* is elementwise matrixProduct = a * b # Julia * is TRUE matrix multiplication println("Elementwise: ", elementwiseResult) println("Matrix product: ", matrixProduct)
This is the headline trap for a Fortran programmer moving to Julia. In Fortran, * between two conformable arrays is always elementwise (Hadamard) multiplication — real matrix multiplication requires the separate matmul() intrinsic. Julia inverts this: bare * between two matrices performs genuine linear-algebra matrix multiplication (and a vector-vector * is a dot product), while the elementwise operation needs the dotted .*. Writing Fortran-style array code with a bare * in Julia compiles fine but silently computes something completely different — often failing loudly instead only when the array shapes happen not to be compatible for multiplication.
ELEMENTAL Functions vs. Dot-Broadcasting Any Function
program elemental_demo implicit none real :: values(3) values = [1.0, 4.0, 9.0] print *, sqrt(values) ! sqrt is already elemental/intrinsic in Fortran end program elemental_demo
values = [1.0, 4.0, 9.0] println(sqrt.(values))
Fortran math intrinsics like sqrt() are already elemental, so they apply automatically across a whole array with no special syntax. Julia requires appending a dot after the function call — sqrt.(values) — to broadcast it elementwise; without the dot, sqrt(values) would try to call the general matrix-square-root function instead (which is a completely different, and generally undefined, operation for most non-square inputs). The advantage: Julia's dot-broadcast syntax works for any function, including ones you write yourself, not just intrinsics.
Array Slicing
program slicing_demo implicit none integer :: numbers(5) numbers = [10, 20, 30, 40, 50] print *, numbers(2:3) end program slicing_demo
numbers = [10, 20, 30, 40, 50] println(numbers[2:3])
Fortran's numbers(2:3) and Julia's numbers[2:3] are functionally identical: both select the 2nd and 3rd elements, both inclusive on both ends, both 1-based.
No Fortran Equivalent — Array Comprehensions
program manual_build_demo implicit none integer :: squares(5), i do i = 1, 5 squares(i) = i * i end do print *, squares end program manual_build_demo
squares = [n^2 for n in 1:5] println(squares)
Fortran has no comprehension syntax — building an array from a per-element expression always requires an explicit loop into pre-declared storage. Julia's comprehension [expr for n in range] builds and sizes the array in a single expression, with no loop or pre-declaration needed.
SUM Intrinsic vs. sum()
program sum_demo implicit none integer :: numbers(5) numbers = [10, 20, 30, 40, 50] print *, "Sum:", sum(numbers) print *, "Max:", maxval(numbers) end program sum_demo
numbers = [10, 20, 30, 40, 50] println("Sum: ", sum(numbers)) println("Max: ", maximum(numbers))
Fortran's sum() intrinsic maps directly onto Julia's sum() — identical name, identical purpose. Fortran's maxval() is Julia's maximum() — same idea, slightly different name.
Arithmetic
Integer Division & MOD vs. ÷ and %
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
numerator = 17 denominator = 5 quotient = numerator ÷ denominator remainderValue = numerator % denominator println("Quotient: ", quotient) println("Remainder: ", remainderValue)
Fortran's plain / performs integer division when both operands are integers. Julia's plain / always produces a Float64 regardless of operand types — integer division needs the dedicated ÷ operator (typed as \div + Tab in the Julia REPL, or spelled out as the div() function), and remainder uses the familiar % rather than Fortran's mod() function call.
** vs. ^
program exponent_demo implicit none real :: result_value result_value = 2.0 ** 10 print *, "2^10 =", result_value end program exponent_demo
resultValue = 2.0 ^ 10 println("2^10 = ", resultValue)
Fortran's exponentiation operator is **; Julia's is ^ — both are dedicated infix operators, just spelled differently.
Math Intrinsics — Both Always Available
program math_functions_demo implicit none print *, "sqrt(144):", sqrt(144.0) print *, "abs(-42):", abs(-42) print *, "sin(0.0):", sin(0.0) end program math_functions_demo
println("sqrt(144): ", sqrt(144.0)) println("abs(-42): ", abs(-42)) println("sin(0.0): ", sin(0.0))
Both languages make the common math functions available with no import at all — sqrt, abs, and the trigonometric functions live directly in Fortran's intrinsics and Julia's always-open Base, with matching names in both.
No Fortran Equivalent — Arbitrary-Precision Integers
program overflow_demo implicit none integer(kind=8) :: large_number large_number = 20 print *, "20! would overflow even a 64-bit integer:" print *, "Fortran has no arbitrary-precision integer type at all." end program overflow_demo
println(factorial(BigInt(30)))
Fortran has no arbitrary-precision integer type — the largest built-in integer is a fixed number of bits (64 at most, via integer(kind=8)), and any value beyond that range simply overflows silently. Julia's BigInt grows to hold integers of any size, limited only by available memory — factorial(BigInt(30)) computes an exact 33-digit result that would silently wrap around in Fortran's largest integer type.
NINT/INT vs. round/trunc
program rounding_demo implicit none real :: value value = 3.7 print *, "nint:", nint(value) print *, "int (truncate):", int(value) print *, "floor:", floor(value) print *, "ceiling:", ceiling(value) end program rounding_demo
value = 3.7 println("round: ", round(value)) println("trunc: ", trunc(value)) println("floor: ", floor(value)) println("ceil: ", ceil(value))
Fortran's nint()/int()/floor()/ceiling() map directly onto Julia's round()/trunc()/floor()/ceil() — same four operations, nearly the same spelling.
Strings
⚠ // vs. * for Concatenation
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
firstName = "Alice" lastName = "Smith" fullName = firstName * " " * lastName println(fullName)
Fortran concatenates with //. Julia, unusually among mainstream languages, concatenates strings with the ordinary multiplication operator * — a deliberate design choice, since string concatenation is genuinely non-commutative like matrix multiplication, whereas + is reserved for the commutative case. This can trip up anyone expecting + or a dedicated concatenation operator; Julia does provide string(a, b) as a more conventional-looking alternative.
No Fortran Equivalent — String Interpolation
program manual_build_demo implicit none character(len=20) :: name integer :: age name = "Alice" age = 30 ! Fortran has no string interpolation; building a message ! requires concatenation of pre-formatted pieces. print *, trim(name) // " is " // "30" // " years old" end program manual_build_demo
name = "Alice" age = 30 println("$name is $age years old")
Fortran has no string interpolation syntax at all — embedding a variable's value inside a larger string always means concatenating separately-converted pieces. Julia's $name inside a string literal substitutes the variable's value directly (and $(expression) for anything more complex than a bare name), with automatic conversion to text.
LEN_TRIM vs. length()
program length_demo implicit none character(len=20) :: greeting greeting = "Hello" print *, "Trimmed length:", len_trim(greeting) end program length_demo
greeting = "Hello" println(length(greeting))
Fortran needs len_trim() to get the length ignoring trailing blank padding. Julia's length() always returns the exact character count, since Julia strings carry no fixed width or padding to account for.
Substring Slicing
program substring_demo implicit none character(len=11) :: phrase phrase = "Hello World" print *, phrase(1:5) print *, phrase(7:11) end program substring_demo
phrase = "Hello World" println(phrase[1:5]) println(phrase[7:11])
Fortran's phrase(1:5) and Julia's phrase[1:5] both take a 1-based, inclusive start-and-end range — a direct match, differing only in bracket style.
No Fortran Equivalent — split()
program manual_split_demo implicit none character(len=20) :: sentence ! Fortran has no string-splitting intrinsic; splitting on a ! delimiter requires a hand-written loop using index(). sentence = "apple,banana,cherry" print *, trim(sentence) end program manual_split_demo
sentence = "apple,banana,cherry" fruits = split(sentence, ",") println(fruits)
Fortran has no string-splitting intrinsic — breaking a string on a delimiter always means writing a manual loop with index(). Julia's split() does the whole job in a single call, returning an array of substrings.
Control Flow
IF / ELSE IF vs. if/elseif
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
score = 85 if score >= 90 println("Grade: A") elseif score >= 80 println("Grade: B") else println("Grade: C or below") end
Both languages use an if/else block terminated by end (end if in Fortran, bare end in Julia). Julia writes its middle branch as one word, elseif, where Fortran uses two, else if.
.AND./.OR. vs. &&/||
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
isWeekday = true isHoliday = false if isWeekday && !isHoliday println("Office is open") end
Fortran surrounds its logical operators with dots (.and., .or., .not.). Julia uses C-style symbols: &&, ||, !.
MERGE Intrinsic vs. Ternary Operator
program merge_demo implicit none integer :: score character(len=4) :: result_label score = 85 result_label = merge("Pass", "Fail", score >= 60) print *, trim(result_label) end program merge_demo
score = 85 resultLabel = score >= 60 ? "Pass" : "Fail" println(resultLabel)
Fortran's merge(trueValue, falseValue, condition) intrinsic picks between two values based on a condition — functionally equivalent to Julia's more familiar condition ? trueValue : falseValue ternary operator, just spelled as a function call with the condition last instead of an operator with the condition first.
Loops & Iteration
DO Loop vs. for Over a Range
program do_loop_demo implicit none integer :: i do i = 1, 5 print *, "Iteration:", i end do end program do_loop_demo
for i in 1:5 println("Iteration: ", i) end
Fortran's do i = 1, 5 and Julia's for i in 1:5 are nearly identical: both count an inclusive range from 1 to 5 with a step of 1, both terminated with end (end do versus bare end).
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
function countdown(startingBalance) balance = startingBalance while balance > 0 balance -= 300 println("Balance: ", balance) end end countdown(1000)
Fortran's do while (condition) and Julia's while condition are functionally identical — both test before each iteration. Julia additionally has compound assignment operators like -=, which Fortran lacks entirely (Fortran always spells out balance = balance - 300).
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
for i in 1:10 if i % 2 == 0 continue end if i > 7 break end println(i) end
Fortran's cycle (skip to the next iteration) and exit (break out of the loop) map onto Julia's continue and break — the same two concepts, different names for the first one.
No Fortran Equivalent — enumerate()
program manual_index_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 manual_index_demo
fruits = ["apple", "pear", "plum"] for (i, fruit) in enumerate(fruits) println(i, " ", fruit) end
A Fortran do loop over an index already gives you the index for free, so there is nothing to add — this is arguably a point where Fortran's counting-based loop needs no special construct at all. Julia's for i in 1:n loop, by contrast, does not automatically pair a value with its position, so enumerate(fruits) is needed to get both the 1-based index and the element together, packaged as a tuple destructured directly in the loop header.
Functions
FUNCTION vs. function
program function_demo implicit none print *, "Fahrenheit:", to_fahrenheit(100.0) contains function to_fahrenheit(celsius) result(fahrenheit) real, intent(in) :: celsius real :: fahrenheit fahrenheit = celsius * 9.0 / 5.0 + 32.0 end function to_fahrenheit end program function_demo
function toFahrenheit(celsius) return celsius * 9 / 5 + 32 end println("Fahrenheit: ", toFahrenheit(100))
Both languages define a real function terminated by end (end function versus bare end) with a return value usable inside expressions. Julia needs no declared parameter or return types at all (though optional annotations are allowed), and its last-evaluated expression becomes the return value automatically — return is optional except for an early return.
One-Liner Function Definitions
program oneliner_demo implicit none print *, square(7) contains pure function square(n) result(product_value) integer, intent(in) :: n integer :: product_value product_value = n * n end function square end program oneliner_demo
square(n) = n * n println(square(7))
Both languages offer a compact one-expression function form: Fortran 4.0's endless-method syntax has an exact peer in Julia's name(args) = expression assignment-style function definition, which needs no function/end block at all for a single-expression body.
Named Arguments vs. Keyword Arguments
program named_args_demo implicit none print *, greet(person_name="Alice", greeting_word="Hi") contains function greet(person_name, greeting_word) result(message) character(len=*), intent(in) :: person_name, greeting_word character(len=40) :: message message = trim(greeting_word) // ", " // trim(person_name) // "!" end function greet end program named_args_demo
function greet(personName; greetingWord="Hello") return "$greetingWord, $(personName)!" end println(greet("Alice", greetingWord="Hi"))
Fortran already supports calling any procedure with named arguments in any order (greet(person_name="Alice", greeting_word="Hi")) — a direct parallel to Julia's keyword arguments. The difference is declaration: Julia marks which parameters are keyword-only with a semicolon in the signature (; greetingWord="Hello") and lets them carry a default, while every Fortran parameter can be called by name with no special marker required.
No Fortran Equivalent — Variadic Arguments
program fixed_arity_demo implicit none ! Fortran has no variadic-arguments syntax — a procedure's ! argument count is fixed at compile time. Passing a variable ! number of values means using an array argument instead. integer :: numbers(4) numbers = [1, 2, 3, 4] print *, sum(numbers) end program fixed_arity_demo
function total(numbers...) return sum(numbers) end println(total(1, 2, 3, 4))
Fortran procedures always take a fixed, compile-time-known number of arguments — accepting "as many numbers as the caller wants to pass" means the caller must package them into an array first. Julia's trailing ... after a parameter name collects any number of extra positional arguments directly, with no array-wrapping required at the call site.
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
function circleStats(radius) area = pi * radius^2 circumference = 2 * pi * radius return area, circumference end area, circumference = circleStats(5.0) println("Area: ", area) println("Circumference: ", circumference)
Fortran returns multiple values through several intent(out) parameters on a subroutine. Julia returns a genuine tuple (return area, circumference) that the caller destructures directly into two names — no array or record wrapper needed, and no separate "out parameter" concept at all.
⚠ No Closures in Fortran
program no_closure_demo implicit none integer :: total_calls total_calls = 0 call increment_counter(total_calls) call increment_counter(total_calls) call increment_counter(total_calls) print *, total_calls contains subroutine increment_counter(counter) integer, intent(inout) :: counter counter = counter + 1 end subroutine increment_counter end program no_closure_demo
function makeCounter() count = 0 return function() count += 1 return count end end counter = makeCounter() println(counter(), " ", counter(), " ", counter())
Julia functions are true closures — a nested function captures its enclosing scope's variables directly, and count += 1 inside the inner function modifies the captured count in place, giving makeCounter() its own private, persistent state. Fortran has no equivalent: internal subprograms after contains can only see the host program's variables while that host program is actively executing, and there is no way to package "a function plus its own private data" into a single value that can be returned and called later.
Types & Dispatch
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
struct Point x::Float64 y::Float64 end origin = Point(1.0, 2.0) println(origin.x, " ", origin.y)
A Fortran type and a Julia struct both group named, typed fields into a value with an automatically generated constructor, accessed with a member operator — % in Fortran, . in Julia. Both are close structural equivalents.
⚠ Immutable by Default vs. Always Mutable
program mutable_fields_demo implicit none type :: counter_type integer :: count end type counter_type type(counter_type) :: counter counter = counter_type(0) counter%count = counter%count + 1 ! always allowed — Fortran fields are always mutable counter%count = counter%count + 1 print *, counter%count end program mutable_fields_demo
mutable struct Counter count::Int end function increment!(counter::Counter) counter.count += 1 end counter = Counter(0) increment!(counter) increment!(counter) println(counter.count)
Every Fortran derived-type field can always be reassigned after construction — Fortran has no notion of an immutable type at all. Julia inverts the default: a plain struct is immutable (its fields can never change after construction, which lets the compiler optimize it aggressively), and mutation requires opting in explicitly with mutable struct. A Fortran programmer's reflex of "fields are always assignable" needs a conscious check in Julia — plain struct field assignment is a compile-time error.
⚠ Generic Interfaces (Static) vs. Multiple Dispatch (Runtime)
module shapes implicit none type :: circle real :: radius end type circle type :: rectangle real :: width, height end type rectangle ! A generic INTERFACE lists every specific procedure up front, ! in one fixed, closed set — resolved by the compiler at COMPILE time. interface area module procedure area_circle module procedure area_rectangle end interface area contains function area_circle(shape) result(computed_area) type(circle), intent(in) :: shape real :: computed_area computed_area = 3.14159265358979 * shape%radius ** 2 end function area_circle function area_rectangle(shape) result(computed_area) type(rectangle), intent(in) :: shape real :: computed_area computed_area = shape%width * shape%height end function area_rectangle end module shapes program generic_interface_demo use shapes implicit none print *, area(circle(2.0)) print *, area(rectangle(3.0, 4.0)) end program generic_interface_demo
struct Circle radius::Float64 end struct Rectangle width::Float64 height::Float64 end area(shape::Circle) = pi * shape.radius^2 area(shape::Rectangle) = shape.width * shape.height println(area(Circle(2.0))) println(area(Rectangle(3.0, 4.0)))
Fortran can overload a name across multiple specific procedures with a generic interface block, but the compiler resolves which one to call at compile time, from a closed, fixed list declared up front in that one place. Julia's multiple dispatch looks superficially similar — multiple area methods, one per type — but resolution happens per-call based on the runtime types of all arguments, against an open set of methods that can be extended later, even from entirely different files or packages, without ever touching the original definition.
No Direct Fortran Equivalent — Abstract Types
program no_hierarchy_demo implicit none type :: dog character(len=20) :: name end type dog print *, trim(speak(dog("Rex"))) contains function speak(animal) result(sound) type(dog), intent(in) :: animal character(len=10) :: sound sound = "Woof" end function speak end program no_hierarchy_demo
abstract type Animal end struct Dog <: Animal name::String end speak(animal::Dog) = "Woof" println(speak(Dog("Rex")))
Fortran derived types have no formal inheritance-like hierarchy in the way this comparison needs — later Fortran standards added a limited extends mechanism for derived types, but it sees far less everyday use than Julia's abstract types. Julia builds type hierarchies with abstract type and the subtype operator <:; a concrete type like Dog <: Animal can then be the target of dispatch on the more general Animal supertype elsewhere.
Error Handling
No Exceptions in Fortran vs. try/catch
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
function withdraw(amount) if amount < 0 error("amount must be positive") end return amount end try withdraw(-5) catch e println("caught: ", e.msg) end
Fortran has no exception-handling mechanism — an error condition is communicated through a status flag the caller must check explicitly, as shown here. Julia's error() raises a genuine, catchable exception, and try/catch intercepts it, letting the program recover and continue — a recoverable middle ground Fortran does not have between "check a flag" and "call stop to kill the whole program."
No Fortran Equivalent — Custom Exception Types
program generic_status_demo implicit none ! Fortran has only integer status codes to distinguish error ! kinds — there is no way to define a named, structured error ! type the way Julia's custom exceptions allow. integer, parameter :: error_negative_amount = 1 integer :: status_flag status_flag = error_negative_amount print *, "Status code:", status_flag end program generic_status_demo
struct NegativeAmountError <: Exception amount::Float64 end Base.showerror(io::IO, e::NegativeAmountError) = print(io, "amount must be positive, got ", e.amount) try throw(NegativeAmountError(-5)) catch e println("caught: ", sprint(showerror, e)) end
Fortran distinguishes error kinds only by convention — typically distinct integer status codes with no structure or attached data beyond the number itself. Julia lets you define a custom exception type (a struct subtyping Exception) that can carry arbitrary data about the failure and even customize its own display message via Base.showerror — a level of structured error information Fortran has no path to at all.
No Fortran Equivalent — finally
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
try println("trying") error("something went wrong") catch e println("caught: ", e.msg) finally println("cleanup") end
Since Fortran has no exception mechanism, there is nothing resembling a finally block either — any cleanup code that must run regardless of success or failure has to be duplicated at every possible exit point by hand. Julia's finally clause runs unconditionally after the try/catch, whether the body succeeded, failed, or even returned early.
Modules & Packages
MODULE/USE vs. module/using
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
using Printf value = 3.14159265358979 @printf("%.14f\n", value)
Fortran's use module_name, only: names and Julia's using ModuleName both import a library of related declarations — Fortran can selectively import with only:; a plain Julia using brings in everything a module exports (Julia's import ModuleName: name form is the selective equivalent of Fortran's only:).
Fortran Intrinsics vs. Julia Base
program intrinsic_demo implicit none print *, mod(17, 5) end program intrinsic_demo
println(mod(17, 5))
Both languages make common functions like mod available with absolutely no import needed — Fortran's intrinsics and Julia's always-open Base module serve the identical role, and this particular function even shares the exact same name in both.
No Fortran Equivalent — a Real Package Manager
program no_package_manager_demo implicit none ! Fortran has no de facto standard package manager. Third-party ! libraries are typically vendored, linked manually, or managed ! through a build system like CMake or fpm on a project-by-project ! basis, with no single universally adopted registry. print *, "No universal package registry exists for Fortran." end program no_package_manager_demo
using Pkg Pkg.add("Example") # installs from Julia's General package registry using Example
Fortran has no single, universally adopted package manager or central package registry — third-party code is typically linked manually or managed through a project-specific build system (CMake, or the newer fpm, which itself is not yet universal). Julia ships with Pkg built directly into the language, backed by a central "General" registry that Pkg.add() installs from directly — much closer to what a Rubyist would expect from Bundler and RubyGems.
Output & Formatting
PRINT * vs. println()
program print_demo implicit none integer :: count real :: price count = 42 price = 9.99 print *, "Count:", count print *, "Price:", price end program print_demo
count = 42 price = 9.99 println("Count: ", count) println("Price: ", price)
print *, ... in Fortran and println(...) in Julia both write a list of values to standard output followed by a newline. Julia also has a no-newline print(), matching Fortran's write(*, '(a)', advance='no') pattern but with far less ceremony.
FORMAT Descriptors vs. @printf
program format_demo implicit none real :: amount amount = 1234.5 write(*, '(a, f10.2)') "Amount: ", amount end program format_demo
using Printf amount = 1234.5 @printf("Amount: %10.2f\n", amount)
Fortran's FORMAT descriptors (f10.2: fixed-point, 10 wide, 2 decimals) are built into the base language. Julia has no formatting syntax in its core at all — the Printf standard library's @printf macro provides C-style %10.2f format specifiers instead, requiring an explicit using Printf import that Fortran's always-available FORMAT strings do not need.
String Interpolation in Output
program building_output_demo implicit none character(len=15) :: name integer :: rank name = "Alice" rank = 1 print *, trim(name), " ", rank end program building_output_demo
name = "Alice" rank = 1 println("$name finished in position $rank")
Fortran builds an output line by listing separate comma-separated values to print *,. Julia can instead build the entire message as one interpolated string literal — "$name finished in position $rank" — which often reads more naturally than a list of pieces, especially once the surrounding text has real sentence structure rather than just labels.
⚠ Gotchas for Fortran Programmers
Column-Major Arrays (Both Languages Agree)
program column_major_demo implicit none integer :: matrix(2,2) matrix = reshape([1, 2, 3, 4], [2,2]) ! Fortran stores column-by-column: memory order is 1, 2, 3, 4 print *, matrix(1,1), matrix(2,1), matrix(1,2), matrix(2,2) end program column_major_demo
matrix = reshape([1, 2, 3, 4], (2,2)) # Julia also stores column-by-column: memory order is 1, 2, 3, 4 println(matrix[1,1], " ", matrix[2,1], " ", matrix[1,2], " ", matrix[2,2])
Both languages store multi-dimensional arrays in column-major order — the first index varies fastest in memory. This is not a coincidence: Julia deliberately chose column-major specifically for compatibility with the same BLAS/LAPACK linear-algebra libraries Fortran code has called for decades. A Fortran programmer's intuition about cache-friendly loop nesting order (iterate the first index in the innermost loop) transfers directly to Julia — unlike C, Python, or Ruby, which are all row-major.
⚠ Fixed-Width Overflow vs. No Automatic Promotion
program overflow_demo implicit none integer(kind=1) :: tiny_value tiny_value = 127 tiny_value = tiny_value + 1 ! wraps silently to -128 print *, tiny_value end program overflow_demo
tinyValue::Int8 = 127 tinyValue = tinyValue + Int8(1) # wraps silently to -128, just like Fortran println(tinyValue)
Both languages agree here: a fixed-width integer type overflows silently with no error or warning in either Fortran or Julia — Int8 wraps exactly the same way Fortran's integer(kind=1) does. The one thing to watch in Julia specifically is that ordinary (untyped) integer literals default to the platform's native Int width (64-bit on virtually all modern machines) and do not automatically promote to BigInt on overflow — only an explicit BigInt(...) conversion gets arbitrary precision, exactly like Fortran requiring an explicit, deliberate choice of integer kind.
⚠ The ! Naming Convention Is Convention Only
program intent_enforced_demo implicit none integer :: total_calls total_calls = 0 call increment(total_calls) print *, total_calls contains subroutine increment(counter) integer, intent(inout) :: counter ! The compiler enforces intent(inout): a bare intent(in) ! parameter genuinely cannot be reassigned here. counter = counter + 1 end subroutine increment end program intent_enforced_demo
mutable struct Counter count::Int end # The trailing ! is PURE CONVENTION — nothing stops you from # naming a mutating function without it, or a non-mutating # function with it. The compiler does not check this at all. function increment!(counter::Counter) counter.count += 1 end counter = Counter(0) increment!(counter) println(counter.count)
Fortran's intent(in)/intent(out)/intent(inout) are compiler-enforced contracts — the compiler rejects a program that violates them. Julia's convention of naming a mutating function with a trailing ! (as in increment! or push!) is only a naming convention: nothing in the language enforces that a !-suffixed function actually mutates its argument, or that a function without one does not. A Fortran programmer used to the compiler catching intent violations should not expect the same safety net from Julia's naming convention alone.
⚠ Global Variables Are a Performance Trap in Julia
program module_scope_demo implicit none ! Fortran module-level variables have a fixed, declared type ! for their entire lifetime — the compiler always knows it ! and generates fully optimized code around it. integer :: shared_total shared_total = 0 print *, shared_total end program module_scope_demo
# A top-level (global) Julia variable's type can change at # any time, so the JIT compiler cannot specialize code that # reads it — this is a well-known, significant performance # trap specific to Julia, with no Fortran parallel. sharedTotal = 0 function addToTotal(amount) global sharedTotal sharedTotal += amount end addToTotal(5) println(sharedTotal)
A Fortran module-level variable has one fixed, declared type for its entire lifetime, so the compiler generates fully specialized code around it with no penalty. A Julia global variable's type is never guaranteed fixed — since Julia has no implicit none-style declaration requirement, the JIT compiler cannot specialize code that reads a global the way it can for a local variable or a function argument, making heavy use of globals a well-documented and significant performance trap unique to Julia's design. (Note also the explicit global sharedTotal declaration required just to modify a global from inside a function — reading one needs no such declaration.)