Lesson 5: Functions and Ownership Basics in Rust
Introduction
Functions help break your program into manageable, reusable pieces. Rust’s approach to ownership is what makes it unique, providing memory safety with zero runtime cost. In this lesson, we’ll learn both!
Defining and Calling Functions
A function in Rust is declared with the fn
keyword, a name, parentheses for parameters, and curly braces for the body.
fn main() {
greet("Rustacean");
}
fn greet(name: &str) {
println!("Hello, {}!", name);
}
fn greet(name: &str)
defines a function that gets a string slice parameter.- You call it inside
main()
.
Function Parameters and Return Values
You can define functions with parameters and return values.
fn add(a: i32, b: i32) -> i32 {
a + b // The last expression is the return value
}
fn main() {
let sum = add(3, 7);
println!("Sum is: {}", sum);
}
- The arrow
->
specifies the return type. - No semicolon for the last line if it’s the return value.
Introducing Ownership
Rust enforces strict rules on how memory is managed, known as ownership.
What is Ownership?
- Each value has a single owner: When you assign a value to another variable or pass it to a function, ownership may move.
- When the owner goes out of scope, the value is dropped (memory is freed).
fn main() {
let text = String::from("hello");
take_ownership(text);
// println!("{}", text); // ⚠️ This won’t work: text's ownership has moved
}
fn take_ownership(s: String) {
println!("Owned string: {}", s);
} // s goes out of scope and is dropped here
Copy Types
Simple types like integers and booleans are copied, not moved.
fn main() {
let x = 10;
print_number(x); // `x` is copied—safe to use after!
println!("x is still accessible: {}", x);
}
fn print_number(num: i32) {
println!("Number: {}", num);
}
Interactive Task 1: Practice Function & Ownership
Write a function shout
that takes a String
, prints the uppercase version, and returns it.
Try using the original argument after calling your function—what do you notice?
fn main() {
let message = String::from("ownership");
let result = shout(message);
// println!("{}", message); // What happens if you uncomment this?
println!("Returned: {}", result);
}
fn shout(text: String) -> String {
let upper = text.to_uppercase();
println!("{}", upper);
upper
}
Try here:
Mini-Challenge
Write a function called multiply_by_two
that takes an integer parameter, doubles it, and returns the new value.
- Call your function from
main
and print the answer.
fn main() {
// Your code here!
}
Post your solution in the comments and discuss what you discover!
Solution: https://gist.github.com/rust-play/c1840523e6246b0ad83fc0abb27d6893
Recap
- Functions in Rust use
fn
and can take parameters and return values. - Rust’s ownership system means each value has one owner and is dropped when out of scope.
- Simple types (integers, bools, char) are copied; complex types (like
String
) are moved.
Questions about functions or ownership? Leave a comment below!