Functions & Methods β What Changes When def Becomes fn
Return types, semicolons, and the familiar "last expression is the return" rule
In Ruby, defining a function needs no type for arguments or returns. Rust requires both.
# Ruby
def greet(name)
"Hello, #{name}!"
end
// Rust
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
-> followed by the return type. No return value (Ruby's nil return) is -> () but can be omitted.
Semicolon = return or not
Both Ruby and Rust share the "last expression is the return value" rule. But in Rust, adding a semicolon makes it a statement, not a return.
fn add(a: i32, b: i32) -> i32 {
a + b // no semicolon β return value
}
fn add_broken(a: i32, b: i32) -> i32 {
a + b; // semicolon β statement, returns () β compile error!
}
This is genuinely confusing at first. In Ruby, semicolons are just statement separators with no semantic meaning. In Rust, they determine whether something gets returned.
Early return
Just like Ruby, you can return early.
fn check_age(age: u32) -> &'static str {
if age < 18 {
return "minor"; // early return needs semicolon
}
"adult" // last expression, no semicolon
}
return uses a semicolon. Last expression returns without one. This pattern is Rust convention.
Argument passing β value vs reference
In Ruby, you never think about how arguments are passed. In Rust, ownership means you must decide: borrow or move.
// borrow β original kept
fn print_name(name: &str) {
println!("{}", name);
}
// ownership move β original unusable after call
fn take_name(name: String) {
println!("{}", name);
}
let name = String::from("sehwa");
print_name(&name); // borrow, name still usable
take_name(name); // move, name unusable after this
Most of the time, borrow with &. "I'm only reading, no need to transfer ownership."
Closures β inline functions
Ruby's blocks/lambdas are closures in Rust. The pattern for passing them as arguments is nearly identical.
# Ruby
[1, 2, 3].map { |x| x * 2 }
// Rust
vec![1, 2, 3].iter().map(|x| x * 2).collect::<Vec<_>>();
|x| corresponds to Ruby's { |x| }. Pipes (|) instead of curly braces to wrap arguments.
Key Points
Define as fn name(arg: Type) -> ReturnType { }
No semicolon = return value, with semicolon = statement β this is key
Use return keyword + semicolon for early returns
Pass arguments by borrowing (&) or moving ownership
Pros
- ✓ Function signature alone tells you input/output types
- ✓ Same "last expression is return" rule as Ruby speeds up adaptation
Cons
- ✗ Semicolon mistakes causing compile errors are frequent early on
- ✗ Borrow/ownership decisions in function design are a new concept from Ruby