def, func, fun, fn, sub: Why Every Language Has a Different Word for the Same Thing

Open almost any programming language reference and you will find, somewhere near the beginning, a section explaining how to define a reusable named block of code. The concept is universal. The word for it is not.

Python calls it a def. Rust calls it an fn. Kotlin calls it a fun. Haskell doesn’t use a keyword at all. FORTRAN uses two different words depending on whether you’re returning a value. Visual Basic still uses Sub and Function as distinct constructs — a choice it inherited, in a direct unbroken line, from the 1950s.

These aren’t arbitrary aesthetic differences. Each keyword choice carries a philosophy about what a reusable code block is, what it should do, and how it relates to the rest of the language. Tracing those choices takes us back to before there were programming languages at all.


The Origin: ENIAC and a Problem With Repeating Code

The concept of a reusable named block of code was conceived in the late 1940s, during the era of the earliest computers. John Mauchly and Kathleen Antonelli, working with the ENIAC — one of the first electronic general-purpose computers — recognized that programs were full of repeated sequences of instructions. Duplicating those sequences wasted precious memory and made programs nearly impossible to maintain.

The idea of extracting a repeated sequence into a single named routine, calling into it from multiple places, and returning to wherever you left off was a profound one. It required hardware support (a way to track where to return) and a programming convention. Neither existed at the time. They had to be invented.

Maurice Wilkes, David Wheeler, and Stanley Gill formalized this concept at Cambridge University in the early 1950s. Wheeler’s 1951 doctoral thesis coined the term “closed sub-routine” — a routine defined once, closed off from its callers, and jumped to as needed. This is the direct origin of the word subroutine, which survives to this day in FORTRAN, COBOL, and VB/VBA.

That work from 1951 is why you still type Sub in Microsoft Excel macros.


The First Split: Procedures vs. Functions

Once subroutines existed, language designers faced an immediate design question: what should one return?

Some routines compute a value — calculate a square root, look up a record, convert a temperature. Others perform an action — write to a file, update a database, print output — and return nothing meaningful. These feel like different things. Early language designers disagreed about whether that difference deserved different syntax.

The “two keyword” camp argued that procedures (no return value) and functions (returns a value) were semantically distinct and should be spelled differently. This camp won early: FORTRAN, the first widely deployed high-level language (1957), used SUBROUTINE for no-return routines and FUNCTION for value-returning ones. That distinction is still in modern Fortran, unchanged after nearly seven decades.

The “one keyword” camp argued that the distinction was a type-system concern, not a syntax concern. C (1972) took this position: everything is a function, and a function that returns nothing has return type void. This decision — made by Dennis Ritchie as a simplification — influenced nearly every mainstream language designed in the following 50 years.


FORTRAN (1957): The Original Split

FORTRAN formalized the two-keyword model in its very first version:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
! A SUBROUTINE: performs an action, returns nothing
SUBROUTINE GREET(NAME)
  CHARACTER(*), INTENT(IN) :: NAME
  PRINT *, "Hello, " // TRIM(NAME)
END SUBROUTINE

! A FUNCTION: computes and returns a value
FUNCTION DOUBLE(X)
  REAL, INTENT(IN) :: X
  REAL :: DOUBLE
  DOUBLE = X * 2.0
END FUNCTION

This was not an arbitrary choice. FORTRAN was designed for scientific computation, where the mathematical distinction between a function (maps inputs to outputs) and a procedure (performs a side effect) was considered meaningful and worth preserving in the code. Mathematicians already knew what a function was. The keyword should match the math.

Modern Fortran still uses both keywords. The split from 1957 remains.


ALGOL 60: One Keyword to Rule Them All

ALGOL 60, the enormously influential language that shaped virtually every structured programming language that followed, took a different approach: it used procedure for everything, regardless of whether a value was returned. A function-like procedure was simply declared with a return type prefix.

ALGOL’s designers were less concerned with the mathematical purity of the distinction and more concerned with clean, uniform grammar. The fact that a routine returned a value was a type annotation issue, not a naming issue. This was the germ of the C approach, though ALGOL and C arrived at it from different directions.


Pascal (1970): Reviving the Split With New Vocabulary

Niklaus Wirth designed Pascal as a teaching language, and he wanted students to understand the distinction between computation and action at a fundamental level. Pascal deliberately revived the two-keyword model, but with cleaner vocabulary than FORTRAN’s SUBROUTINE / FUNCTION:

{ A procedure: performs an action, no return value }
procedure Greet(name: string);
begin
  writeln('Hello, ', name);
end;

{ A function: computes and returns a value }
function Double(x: real): real;
begin
  Double := x * 2.0;
end;

Pascal’s procedure / function split proved highly influential. Ada, Delphi, Modula-2, and several other languages adopted it directly. Wirth’s argument was pedagogical: beginners should know, from the keyword alone, whether a block of code is doing something or computing something. The split encoded intent.


C (1972): Unification Wins

Dennis Ritchie’s C unified everything under function. If a function returns nothing, its return type is void. If it returns a value, the type precedes the name. No separate keyword for procedures.

1
2
3
4
5
6
7
8
9
/* A "procedure" in C — just a function returning void */
void greet(const char *name) {
    printf("Hello, %s\n", name);
}

/* A function returning a value */
double double_it(double x) {
    return x * 2.0;
}

C’s design was shaped by pragmatism and minimalism. Adding a second keyword for the same concept — a named reusable block — seemed unnecessary when the type system already captured the distinction. void made the no-return case explicit without inventing new vocabulary.

C was enormously successful. The languages most influenced by C — C++, Java, C#, JavaScript, Go, and dozens of others — all adopted the single-keyword approach. Java uses no keyword at all (the return type in the signature tells you everything). Go uses func. Rust uses fn. They all follow the C tradition: one concept, one keyword, types carry the rest of the information.


Python: def and the Implicit Return

Python uses def — short for “define” — for every callable, regardless of what it returns:

1
2
3
4
5
6
7
# "Procedure" style — no return statement
def greet(name):
    print(f"Hello, {name}")

# Function style — returns a value
def double(x):
    return x * 2

The keyword def is deliberately neutral. It doesn’t commit to whether you’re defining a function or a procedure — it just says you’re defining something. A def without a return statement implicitly returns None. Python’s view is that the distinction between procedures and functions is not meaningful enough to encode in syntax. Every def is callable. What it does when called is its own business.

This also reflects Python’s dynamic nature. A function that currently returns None can be modified to return a value later. Baking the distinction into the keyword would make such changes more disruptive than they need to be.


Haskell: No Keyword Necessary

Haskell occupies the opposite extreme from verbose languages. There is no keyword for defining a function. A function is just an equation:

1
2
3
4
5
-- This is a complete function definition
double x = x * 2

-- So is this
greet name = "Hello, " ++ name

Haskell’s approach flows from its mathematical foundation. In mathematics, you write f(x) = x * 2 — there’s no special syntax for “defining a function.” Functions are first-class values, defined by the equations that describe their behavior. A keyword like def or fn would add noise without adding meaning.

This reflects something deeper: in Haskell, the distinction between “function” and “value” nearly disappears. A constant is just a function that takes no arguments. A higher-order function is just a value that happens to be callable. The uniform treatment makes the language more consistent, at the cost of being unfamiliar to developers coming from C-family languages.


Rust: fn and the Unit Type

Rust uses fn, following the C tradition of unification under one keyword. But Rust’s handling of the no-return case is more philosophically precise than C’s void:

1
2
3
4
5
6
7
8
9
// "Procedure" style — returns unit type ()
fn greet(name: &str) {
    println!("Hello, {}", name);
}

// Function style — returns a value
fn double(x: f64) -> f64 {
    x * 2.0
}

In Rust, a function that “returns nothing” actually returns () — the unit type. This is not a special case or a void keyword; it is a real type with exactly one value. The distinction matters because it makes Rust’s type system fully consistent: every expression has a type, every function returns a type, and () is just a type that happens to carry no information.

Rust also demonstrates single-expression functions concisely — the last expression in a function body is its return value, and you omit the return keyword:

1
2
3
fn double(x: f64) -> f64 {
    x * 2.0  // no semicolon = implicit return
}

Kotlin: fun and Expressions

Kotlin uses fun, and it carries the C tradition’s single-keyword approach onto the JVM. But Kotlin added something C never had: single-expression functions, where the function body is just an equation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Standard form
fun double(x: Int): Int {
    return x * 2
}

// Single-expression form — equivalent, more concise
fun double(x: Int) = x * 2

// "Procedure" style — returns Unit (like Rust's ())
fun greet(name: String) {
    println("Hello, $name")
}

The Unit return type in Kotlin, like Rust’s (), is an actual type rather than a void marker. Functions that “return nothing” return Unit, which is a real value that can be used in type-generic code. This is a deliberate improvement over Java’s void, which creates asymmetries in generic programming.

Kotlin also rolled back one of Java’s most controversial design choices: Java and C# have no free-standing functions — everything must be a method inside a class. Kotlin, Scala, and Swift deliberately reversed this, allowing top-level functions that exist outside any class. It was a recognition that not all code is object-oriented, and forcing everything into a class is a formalism that often obscures rather than clarifies.


VB/VBA: The Pascal Tradition Survives

Visual Basic and VBA maintain the explicit Sub vs. Function distinction from the Pascal tradition — making them among the few widely-used modern environments that still encode the procedure/function split in keywords:

1
2
3
4
5
6
7
8
9
' A Sub: performs an action, returns nothing
Sub Greet(name As String)
    MsgBox "Hello, " & name
End Sub

' A Function: computes and returns a value
Function Double(x As Double) As Double
    Double = x * 2
End Function

The Sub keyword is a direct descendant of Wheeler’s 1951 “closed sub-routine.” Through BASIC (1964), through the original Visual Basic (1991), and into VBA today, that lineage is unbroken. Every Excel macro that starts with Sub is carrying 70 years of language history.


The Lambda Syntax Explosion

Once languages started treating functions as first-class values that could be passed around and composed, they needed syntax for anonymous functions — functions without a name, defined inline. Every language invented its own syntax:

1
2
// JavaScript arrow function (ES6, 2015)
const double = (x) => x * 2;
1
2
# Python lambda
double = lambda x: x * 2
1
2
// Java lambda (Java 8, 2014)
Function<Integer, Integer> double = x -> x * 2;
1
2
-- Haskell lambda
double = \x -> x * 2
1
2
// Rust closure
let double = |x: f64| x * 2.0;
1
2
// Kotlin lambda
val double = { x: Int -> x * 2 }

Each syntax was designed independently, optimized for the aesthetic and structural conventions of its language. Haskell’s \x -> is a deliberate allusion to the lambda (λ) symbol from lambda calculus, written as a backslash because λ isn’t on most keyboards. Rust’s |x| uses pipe characters as delimiters, a syntax inherited from Ruby that also avoids parsing ambiguity with C-style operators. JavaScript’s => was chosen to visually suggest “maps to.”

The explosion of lambda syntaxes shows what happens when a concept is discovered independently in multiple places: each community invents its own notation, optimized for local context, and the resulting diversity is simultaneously fascinating and mildly inconvenient for anyone who switches between languages regularly.


Side-by-Side: The Same Function in 12 Languages

Here is the “double a number” function — the simplest possible value-returning function — written in twelve languages:

LanguageDefinition
FORTRANFUNCTION DOUBLE(X) / DOUBLE = X * 2.0 / END FUNCTION
Pascalfunction double(x: real): real; / begin double := x * 2.0; end;
Cdouble double_it(double x) { return x * 2.0; }
Pythondef double(x): return x * 2
JavaScriptfunction double(x) { return x * 2; }
Rustfn double(x: f64) -> f64 { x * 2.0 }
Kotlinfun double(x: Int) = x * 2
Haskelldouble x = x * 2
Gofunc double(x float64) float64 { return x * 2 }
Visual BasicFunction Double(x As Double) As Double / Double = x * 2 / End Function
Rubydef double(x) = x * 2
Swiftfunc double(_ x: Double) -> Double { return x * 2 }

Look at this table and two camps snap into focus immediately.

The Pascal/FORTRAN/VB camp: verbose keyword choices that encode intent (FUNCTION, Function, function), explicit End/end delimiters, return type annotation in the signature.

The C camp: terse keywords (fn, func, fun, def) or no keyword at all (Haskell), implicit return in many cases, return type often follows an arrow (->) rather than preceding the name.

And then FORTRAN, which predates both camps and just used all capitals, as was the style at the time.


What the Keyword Choice Reveals

The split between the Pascal tradition and the C tradition runs deeper than keyword length. It reflects two different theories about what programming languages are for.

The Pascal tradition believed that language syntax should reflect semantic distinctions. A procedure and a function are different things — they have different relationships to side effects, different mathematical status, different implications for reasoning about program behavior. Making students and programmers type different words would reinforce those distinctions and produce clearer thinking.

The C tradition believed that syntax should be minimal and orthogonal. A named reusable block is a named reusable block; the return type annotation tells you what you need to know. Adding vocabulary for distinctions the type system already captures is complexity without benefit.

Modern languages are converging — the C tradition has largely won, and the single-keyword approach dominates new language design. But they haven’t fully shed the historical baggage. Rust returns (). Kotlin returns Unit. Swift has both func (which can return Void) and a rigid opinion that not everything should be a class method. These are all echoes of the original debates, refracted through seventy years of design iteration.

The fact that you still type Sub in Excel macros — the word tracing directly to Wheeler’s 1951 work — is a reminder that programming language design is not abstract. It is accumulated history, preserved in the small words we type thousands of times a day without thinking about where they came from.


Want to dig deeper? CodeArchaeology has complete guides for the languages in this article:

  • FORTRAN — where FUNCTION and SUBROUTINE were born
  • Pythondef and the philosophy of the neutral keyword
  • Rustfn, the borrow checker, and the unit type
  • Kotlinfun, top-level functions, and the JVM evolution

Or browse the full language encyclopedia to see how 1,200+ languages chose their own word for “function.”

Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining