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.)