Functions
Functions are fundamental building blocks in any programming language. Let’s explore how functions work in Rust and how they compare to JavaScript.
Defining Functions
JavaScript Functions
In JavaScript, you can define functions in several ways:
// Function declarationfunction add(a, b) { return a + b;}
// Function expressionconst multiply = function(a, b) { return a * b;};
// Arrow functionconst subtract = (a, b) => a - b;
Rust Functions
In Rust, functions are defined with the fn
keyword:
fn add(a: i32, b: i32) -> i32 { a + b}
Key differences:
- Rust requires type annotations for parameters
- Return type is specified with
->
after the parameter list - The final expression without a semicolon is implicitly returned
- No function expressions or arrow functions in Rust
Function Body and Return Values
JavaScript Return Values
In JavaScript, the return
keyword is used to return a value:
function square(x) { return x * x;}
// Functions without return statements return undefinedfunction greet(name) { console.log(`Hello, ${name}!`); // implicitly returns undefined}
Rust Return Values
In Rust, the final expression is returned without the return
keyword:
fn square(x: i32) -> i32 { x * x // No semicolon - this is an expression that returns a value}
// Explicit returnfn early_return(x: i32) -> i32 { if x < 0 { return 0; // Early return requires the return keyword } x * x}
// Functions can return nothing (the unit type)fn greet(name: &str) { println!("Hello, {}!", name); // Implicitly returns the unit type ()}
The key concept here is that in Rust, statements end with a semicolon and don’t return values, while expressions don’t have a semicolon and do return values.
Function Parameters
JavaScript Parameters
JavaScript is flexible with parameters:
// Optional parameters with default valuesfunction greet(name = "World") { return `Hello, ${name}!`;}
// Rest parametersfunction sum(...numbers) { return numbers.reduce((total, num) => total + num, 0);}
// Destructuring parametersfunction printPerson({ name, age }) { console.log(`${name} is ${age} years old`);}
Rust Parameters
Rust parameters are more strictly typed:
// All parameters require type annotationsfn greet(name: &str) -> String { format!("Hello, {}!", name)}
// Multiple parametersfn calculate_rectangle_area(width: f64, height: f64) -> f64 { width * height}
// Default parameters don't exist in the same way as JavaScript// You can use Option<T> or implement multiple functionsfn greet_option(name: Option<&str>) -> String { let name = name.unwrap_or("World"); format!("Hello, {}!", name)}
// Call it with Some or Nonelet greeting = greet_option(Some("Alice")); // "Hello, Alice!"let default_greeting = greet_option(None); // "Hello, World!"
Function Overloading
JavaScript doesn’t have true function overloading, though you can simulate it:
function process(arg) { if (typeof arg === 'number') { return arg * 2; } else if (typeof arg === 'string') { return arg.toUpperCase(); } return null;}
Rust doesn’t have function overloading either, but you can:
- Use different function names
- Use generic types (which we’ll cover later)
- Use trait objects (which we’ll cover later)
fn double_number(x: i32) -> i32 { x * 2}
fn uppercase_string(s: &str) -> String { s.to_uppercase()}
Main Function
JavaScript doesn’t have a special main function - script execution starts at the top level.
In Rust, the main
function is the entry point for executables:
fn main() { println!("Hello, world!");}
Passing Functions as Arguments
JavaScript Higher-Order Functions
In JavaScript, functions are first-class citizens:
function applyFunction(x, operation) { return operation(x);}
// Passing a function as an argumentconst result = applyFunction(5, x => x * x); // 25
Rust Function Pointers
Rust also supports passing functions:
fn apply_function(x: i32, operation: fn(i32) -> i32) -> i32 { operation(x)}
fn square(x: i32) -> i32 { x * x}
// Passing a function as an argumentlet result = apply_function(5, square); // 25
// Using closures (similar to lambda expressions)let result = apply_function(5, |x| x * x); // 25
Closures (Lambda Functions)
JavaScript Closures
JavaScript’s closure syntax is lightweight:
const add = (a, b) => a + b;
// Capturing variables from the environmentfunction makeCounter() { let count = 0; return function() { count += 1; return count; };}
const counter = makeCounter();console.log(counter()); // 1console.log(counter()); // 2
Rust Closures
Rust closures use pipes (|
) for parameters:
let add = |a: i32, b: i32| a + b;
// Type inference works with closureslet add_inferred = |a, b| a + b;let sum = add_inferred(5, 10); // 15
// Capturing variables from the environmentlet x = 5;let add_x = |y| x + y; // Captures x from the environmentlet result = add_x(10); // 15
// Mutable closureslet mut counter = 0;let mut increment = || { counter += 1; counter};
println!("{}", increment()); // 1println!("{}", increment()); // 2
Rust closures have three traits:
Fn
: Captures by reference (similar to JS closures)FnMut
: Captures by mutable referenceFnOnce
: Captures by value and can only be called once
This is related to Rust’s ownership system, which we’ll cover later.
Methods
JavaScript Methods
In JavaScript, methods are functions attached to objects:
class Rectangle { constructor(width, height) { this.width = width; this.height = height; }
area() { return this.width * this.height; }}
const rect = new Rectangle(10, 20);console.log(rect.area()); // 200
Rust Methods
In Rust, methods are defined within impl
blocks:
struct Rectangle { width: u32, height: u32,}
impl Rectangle { // Static method (no self parameter) fn new(width: u32, height: u32) -> Rectangle { Rectangle { width, height } }
// Instance method (uses &self - "self" is like "this" in JavaScript) fn area(&self) -> u32 { self.width * self.height }
// Mutable method (uses &mut self) fn resize(&mut self, width: u32, height: u32) { self.width = width; self.height = height; }}
// Usagelet mut rect = Rectangle::new(10, 20);println!("Area: {}", rect.area()); // 200rect.resize(30, 40);println!("New area: {}", rect.area()); // 1200
Associated Functions
These are similar to static methods in JavaScript:
impl Rectangle { // This is an associated function (no self parameter) fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size, } }}
// Called with :: syntax, like JavaScript static methodslet square = Rectangle::square(10);
Function Documentation
JavaScript Documentation
JavaScript uses JSDoc comments:
/** * Calculates the sum of two numbers * @param {number} a - The first number * @param {number} b - The second number * @returns {number} The sum of a and b */function add(a, b) { return a + b;}
Rust Documentation
Rust uses doc comments:
/// Calculates the sum of two numbers////// # Examples////// ```/// let sum = add(5, 10);/// assert_eq!(sum, 15);/// ```fn add(a: i32, b: i32) -> i32 { a + b}
Key Differences
- Type Annotations: Rust requires explicit parameter and return types
- Expressions vs. Statements: Rust’s final expression is returned without a
return
keyword - Default Parameters: Rust doesn’t have default parameters like JavaScript
- Function Overloading: Neither language has true function overloading
- Closures: Both languages support closures, but Rust’s have more nuanced capture behavior
- Methods: Rust methods are defined in
impl
blocks and have explicitself
parameters
Next Steps
Now that you understand functions in Rust, let’s explore Comments to see how to document your code effectively.