Why Modern Languages Use `fn` and `func`: The Greppability Advantage
Purpose
This post explains why modern programming languages use explicit function keywords like fn and func, and how this design choice makes searching code easier.
The Problem
When I tried to find all function definitions in a C codebase using grep, I ran into this problem:
$ grep -r "calculate" src/src/calc.c:int calculate(int a, int b) {src/calc.c: return calculate(a + b);src/calc.c:// TODO: fix calculate() for negative numberssrc/utils.c:typedef int (*calculate)(int);I got four matches. But I only wanted the function definition on line 1. The other results were:
- A function call
- A comment
- A function pointer type declaration
This makes code search noisy and frustrating.
Why This Happens
C and C++ have ambiguous function syntax. Look at these examples:
// Function definitionint calculate(int a, int b) { return a + b;}
// Variable declarationint calculate;
// Function callint result = calculate(3, 4);
// Function pointerint (*calculate)(int);The word calculate appears in all four patterns. When you grep for it, you get everything mixed together.
You cannot tell if something is a function definition until you look at the next character or line. This ambiguity makes text-based search tools less useful.
The Modern Solution
Languages like Go and Rust fix this with explicit keywords:
// Function definitionfunc Calculate(a, b int) int { return a + b}
// Function callresult := Calculate(3, 4)// Function definitionfn calculate(a: i32, b: i32) -> i32 { a + b}
// Function calllet result = calculate(3, 4);Now you can grep for just function definitions:
# Find all function definitions in Go$ grep -r "^func " src/src/calc.go:func Calculate(a, b int) int {src/calc.go:func main() {
# Find all function definitions in Rust$ grep -r "^fn " src/src/calc.rs:fn calculate(a: i32, b: i32) -> i32 {src/calc.rs:fn main() {The pattern ^func or ^fn matches only function declarations. It skips calls, comments, and variables.
Why Greppability Matters
You might think: βWhy not just use an IDE?β
Here are real scenarios where grep-based search is better:
1. Quick code review on remote servers
$ ssh production-server$ grep "^func [A-Z]" /app/api/ | head -20func CreateUser(user User) error {func UpdateUser(id int, user User) error {func DeleteUser(id int) error {I can see all public API functions in seconds without loading the project in an IDE.
2. Counting functions in a file
# Go - easy$ grep -c "^func " handlers.go12
# C - inaccurate$ grep -c "^[a-zA-Z_].*(" handlers.c47 # Includes if statements, while loops, etc.The Go count is accurate. The C count includes control flow statements.
3. Finding specific implementation
# Find where CalculateTotal is defined$ grep -r "^func CalculateTotal" .src/billing/calculate.go:func CalculateTotal(items []Item) int64 {
# C version returns noise$ grep -r "CalculateTotal" .src/billing/calculate.c:int CalculateTotal(Item* items) {src/billing/calculate.c: total += CalculateItem(&items[i]);src/main.c: grand = CalculateTotal(all_items);src/test/test.c: assert(CalculateTotal(test_items) == 100);When This Helps Most
Terminal-based development
- SSH sessions on remote servers
- CI/CD pipelines checking code structure
- Quick inspection without opening a full IDE
- Analyzing code without project setup
Learning a new codebase
- Find all public functions instantly
- Understand code structure quickly
- No need to parse the whole project
Simple code analysis tools
- Generate documentation automatically
- Count functions per module
- Find security-related functions
- Track API changes
Real Examples
Scenario: Finding entry points in a Go project
$ grep "^func [A-Z]" main.gofunc HandleRequest(w http.ResponseWriter, r *http.Request) {func ProcessOrder(order Order) error {func SendEmail(to string, content string) error {I see three exported functions at a glance. In C, this would require complex regex or a full parser.
Scenario: Debugging on production
$ ssh production-server$ grep "^func.*Error" /app/handlers.gofunc HandleTimeoutError(err error) {func HandleDatabaseError(err error) {I can see all error handling functions in one command.
The Design Philosophy
This is about explicit vs implicit syntax:
| Approach | Pros | Cons |
|---|---|---|
| C/C++ (implicit) | Less typing | Harder to grep, more ambiguous |
| Go/Rust (explicit) | Clearer syntax, easier tools | More typing |
The trade-off favors tools and readability over brevity.
Goβs creators chose func specifically to improve code search. Rust chose fn for brevity while keeping the explicit keyword.
Common Grep Patterns
Here are useful patterns for Go and Rust:
Find all functions:
grep -r "^func " . # Gogrep -r "^fn " . # RustFind public functions:
grep "^func [A-Z]" . # Go (capitalized names)grep "^pub fn " . # RustFind functions containing specific text:
grep "^func.*Auth" . # Find auth-related functions in Gogrep "^fn.*auth" . # Find auth-related functions in RustCount functions per file:
for file in *.go; do echo "$file: $(grep -c "^func" "$file")"; doneSummary
In this post, I explained why modern languages use fn and func keywords. The key point is that explicit function keywords make code search easier and more accurate.
When you grep ^func Calculate in Go, you get only the function definition. In C, you get mixed results including calls, comments, and other declarations.
This design choice helps terminal users, simplifies code analysis tools, and makes learning codebases faster. Even with modern IDEs, explicit keywords remain useful for quick searches and automated tools.
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:
- π¨βπ» Reddit Discussion: Why do C successors use fn or func?
- π¨βπ» Go Language Design FAQ
- π¨βπ» Rust Book - Functions
Oh, and if you found these resources useful, donβt forget to support me by starring the repo on GitHub!
Comments