Functions
Function syntax
Functions are declared with fn. Return type comes after ->:
fn add(a: i32, b: i32) -> i32 {
a + b
}In C#: Like static int Add(int a, int b) => a + b; or a method with return a + b;.
No return keyword for tail expression
The last expression in a function (no semicolon) is the return value:
fn double(x: i32) -> i32 {
x * 2 // no semicolon = return value
}In C#: Similar to expression-bodied members: int Double(int x) => x * 2;. In Rust, any block can end with an expression.
Explicit return
Use return for early return or with a semicolon:
fn maybe_zero(n: i32) -> i32 {
if n < 0 {
return 0;
}
n
}Unit type ()
Functions that “return nothing” return the unit type ():
fn say_hi() {
println!("Hi");
}
// same as: fn say_hi() -> () { ... }In C#: Like void SayHi(). In Rust there is no void; () is the type of “no meaningful value.”
More on the unit type
The unit type () has exactly one value (also written ()) and is zero-sized. Rust uses it
wherever there is "no meaningful value," because every function and expression must have
some type: a println!(...); statement, and a block with no tail expression, both evaluate
to (). That uniformity is what lets if/else and blocks compose as expressions. (A let
binding like let x = 5; is a statement with no value of its own — distinct from ().)
In C#: void is a special non-type — you can't store it or pass it as a type argument.
() is an ordinary type with one value, so it works in variables, containers, and generics.
What C# function features don't exist
A few function features you would reach for in C# simply aren't here — and the compiler errors don't say "use overloading," so it helps to know up front:
- No overloading. You can't define two
fn area(...)with different parameter lists. Use distinct names (area_circle,area_rect), or make one function generic over a trait. - No default / named / optional parameters. There's no
fn connect(timeout: u32 = 30). Model optionality withOption<T>, a config struct +Default, or the builder pattern. - No
paramsarrays. Instead of variadics, take a slice (fn sum(xs: &[i32])) or an iterator (fn sum(xs: impl IntoIterator<Item = i32>)); genuinely variadic call sites are done with macros (that's whyprintln!has a!).
In C#: overloads, optional/named args, and params are everyday tools; in Rust the
idioms above take their place.
Key takeaway
Use fn name(args) -> ReturnType. The last expression (no semicolon) is the return value. “No value” is (), not void.