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:
int add(int x, int y) { return x + y;}Go style:
func add(x int, y int) int { return x + y}Rust style:
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:
function add(x, y) { return x + y;}def add(x, y): return x + yThese 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.
// Variable declarationvar name string
// Function declarationfunc greet(name string) string { return "Hello, " + name}
// Array declarationvar numbers []int
// Map declarationvar ages map[string]intThis 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 FunctionBodySignature = 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 parseThe 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.
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:
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:
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:
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:
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:
| Language | Keyword | Type Position | Multiple Returns | Named Params |
|---|---|---|---|---|
| C | None | Before name | No (use structs) | No |
| Go | func | After name | Yes | Yes |
| Rust | fn | After name | Yes (tuples) | No |
| Kotlin | fun | After name | Yes | Yes |
| Swift | func | After name | Yes | Yes |
I can see the pattern clearly. Modern languages share the same approach.
Here’s the same simple function in different languages:
// Cint add(int x, int y) { return x + y;}// Gofunc add(x int, y int) int { return x + y}// Rustfn add(x: i32, y: i32) -> i32 { x + y}// Kotlinfun add(x: Int, y: Int): Int { return x + y}// Swiftfunc 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:
- Existing C codebases - When working with legacy C code, you need to follow the C style
- Embedded systems - C is still the main language for embedded programming
- 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
functionkeyword 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