Skip to content

Why Modern Languages Use fn/func Instead of C-Style Return Type First

Purpose

I saw this question on Reddit: “Why do modern languages like Go and Rust use fn or func keywords instead of C-style return type first?” This is a good question. When you learn multiple programming languages, you notice these differences. I want to explain the reasons in simple terms.

The Problem

When I started learning C, then later tried Go and Rust, I found different syntax for declaring functions. Here’s what I saw:

C style:

c_function.c
int add(int x, int y) {
return x + y;
}

Go style:

go_function.go
func add(x int, y int) int {
return x + y
}

Rust style:

rust_function.rs
fn add(x: i32, y: i32) -> i32 {
x + y
}

I wondered: Why did modern languages change the pattern? C puts the return type first. Go and Rust put the return type last. And they add func or fn keywords at the start.

C’s Historical Design Choices

When I looked into the history, I found that C was created in the 1970s. Back then, computing resources were very different:

  • No monitors or screens
  • Line printers for output
  • Punch cards for input
  • Very limited memory and storage

Every character mattered. The C designers chose “return type first” to save characters. It was efficient for that era.

Imagine writing code on punch cards. Each line has 80 characters. You want to be concise. The C syntax int add(int x, int y) is shorter than func add(x int, y int) int.

The Dynamic Language Connection

When I learned JavaScript, Python, and Ruby, I noticed they don’t specify return types at all. They look like this:

javascript_function.js
function add(x, y) {
return x + y;
}
python_function.py
def add(x, y):
return x + y

These languages don’t have return types in the declaration. They can’t use C-style syntax because there’s no return type to put first.

Modern languages like Go and Rust borrowed this pattern. They kept the function/fn keyword but added type annotations. This makes the syntax familiar to people who know dynamic languages.

Go’s Consistency Principle

When I studied Go, I noticed something interesting. Go puts types after names everywhere, not just in functions.

go_consistency.go
// Variable declaration
var name string
// Function declaration
func greet(name string) string {
return "Hello, " + name
}
// Array declaration
var numbers []int
// Map declaration
var ages map[string]int

This pattern is consistent. First you say what it is, then you say what type it is. I found this easier to remember because it follows the same rule everywhere.

The Go specification shows this clearly:

FunctionDecl = "func" FunctionName Signature FunctionBody
Signature = Parameters [ Result ]

The func keyword comes first, then the name, then the parameters, then the return type. This matches how Go declares variables.

Parser and Compiler Advantages

When I thought about how compilers work, I realized that the func or fn keyword makes parsing easier.

Here’s a simple comparison:

C: int add(int x, int y) {...}
Parser must check if "int" is a type name or function name
Needs more lookahead to decide
Go: func add(x int, y int) int {...}
Parser sees "func" and knows it's a function immediately
No ambiguity, simple to parse

The keyword at the start makes it clear what follows. This reduces ambiguity. The compiler doesn’t need to look ahead as far. It makes the grammar simpler.

Semicolon-Free Programming

I noticed that Go, Rust, Kotlin, Swift, and Scala all have optional or no semicolons. The func keyword helps with this.

go_semicolons.go
func main() {
fmt.Println("Hello")
fmt.Println("World")
// No semicolons needed
}

When the compiler sees func, it knows a function declaration starts. When it sees the closing brace, it knows it ends. The boundaries are clear without semicolons.

C-style syntax often needs semicolons because it’s harder to tell where statements end:

c_semicolons.c
int main() {
printf("Hello"); // Semicolon required
printf("World"); // Semicolon required
return 0;
}

Multiple Return Values

One big difference is that modern languages support multiple return values. This works well with the func keyword style.

Here’s how Go handles multiple returns:

go_multiple_returns.go
func divide(a float64, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
}

The return types are grouped together in parentheses. This is clear and easy to read.

With C-style syntax, multiple returns look awkward. You need structs or pointers:

c_struct_return.c
typedef struct {
float64 value;
int error;
} Result;
Result divide(float64 a, float64 b) {
Result r;
if (b == 0) {
r.error = 1;
return r;
}
r.value = a / b;
r.error = 0;
return r;
}

The modern syntax is more direct.

Named Return Parameters

Go also supports named return parameters. This feature works naturally with type-after-name syntax:

go_named_returns.go
func complexF3() (re float64, im float64) {
re = 7.0
im = 4.0
return // Naked return - returns re and im
}

Here, re and im are declared as part of the return signature. They behave like local variables. The return statement without values returns the current values of these named variables.

This would be awkward with C-style syntax. You’d need something like float64 re, im which doesn’t clearly show that these are return values.

Practical Comparison

Let me compare different languages directly:

LanguageKeywordType PositionMultiple ReturnsNamed Params
CNoneBefore nameNo (use structs)No
GofuncAfter nameYesYes
RustfnAfter nameYes (tuples)No
KotlinfunAfter nameYesYes
SwiftfuncAfter nameYesYes

I can see the pattern clearly. Modern languages share the same approach.

Here’s the same simple function in different languages:

// C
int add(int x, int y) {
return x + y;
}
// Go
func add(x int, y int) int {
return x + y
}
// Rust
fn add(x: i32, y: i32) -> i32 {
x + y
}
// Kotlin
fun add(x: Int, y: Int): Int {
return x + y
}
// Swift
func add(_ x: Int, _ y: Int) -> Int {
return x + y
}

When I look at these examples, I see that the modern languages are more consistent with each other. They all follow the keyword name parameters -> return pattern.

When C-Style Still Makes Sense

I don’t think C-style is bad. It made sense for its time. There are still situations where it’s useful:

  1. Existing C codebases - When working with legacy C code, you need to follow the C style
  2. Embedded systems - C is still the main language for embedded programming
  3. Historical compatibility - Languages like C++, Java, and C# kept C-style syntax to be familiar to C programmers

If you’re working in these areas, C-style is the right choice.

Summary

In this post, I explained why modern programming languages like Go and Rust use fn or func keywords with type-after-name syntax instead of C’s return-type-first style.

The key points are:

  • Historical context: C’s return-type-first syntax was optimized for the 1970s era of limited resources
  • Dynamic language influence: The function keyword pattern came from languages like JavaScript and Python
  • Consistency: Go places types after names everywhere, making it easier to remember
  • Parser simplicity: The keyword at the start reduces ambiguity for compilers
  • No semicolons: Modern languages can omit semicolons because statement boundaries are clear
  • Multiple returns: This syntax works better with modern features like multiple return values

I think the modern style is more approachable for beginners. It’s consistent across languages and supports newer language features well. But C-style still has its place in the right contexts.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments