Rust Essential Course – Lesson 5

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:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=c6fddc8216251991f07ff8ce0fdd2326

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!

Leave a Comment

Your email address will not be published. Required fields are marked *