Start learning free

LESSON 37 OF 414 · FUNCTIONS I

Function Parameters and Arguments in Rust

Lesson Read the full explanation

So far, the functions we've created execute a fixed task without context / inputs. But most often we want our functions to produce different outcomes depending on the context behind the function call.

For example, it would be nice if say_hello() would print a personalized message such as "Hello humans" or "Hello cats" or "Hello dogs", depending on the audience instead of "Hello world" no matter what.


Function parameters:

Function parameters allow functions to accept input(s) and perform a task using the input(s). We use parameters as placeholders for information that will be passed to the function when it is called.

When a function is defined, its parameters are specified between the parentheses that follow the function name.

Here is our function with one string parameter, audience:

fn say_hello(audience: &str) {
    println!("Hello {}", audience);
}

Calling with arguments:

Then we can call say_hello() and specify the values in the parentheses that follow the function name. The values that are passed to the function when it is called are called arguments.

// Function declaration:
fn say_hello(audience: &str) {
    println!("Hello {}", audience);
}

// Function call:
say_hello("humans"); // Output: Hello humans

We have passed one string argument: "humans". Inside the function audience will equal string "humans" and acts just like a regular variable.


Multiple parameters:

A function can have as many parameters as it needs. Here is a function with two parameters:

fn say_hello(name: &str, age: i32) {
    println!("{}, aged {}", name, age);
}

// Let's call it:
say_hello("John", 30); // Output: John, aged 30
say_hello("Mary", 26); // Output: Mary, aged 26

Notice that the order in which arguments are passed and assigned follows the order that the parameters are declared.


Variables as arguments:

Variables can also be passed as arguments for function calls:

fn say_hello(name: &str, age: i32) {
    println!("{}, aged {}", name, age);
}

let name = "Andy";
let age = 28;

say_hello(name, age); // Output: Andy, aged 28

say_hello("Mary", age); // Output: Mary, aged 28

Ownership & borrowing:

Passing a value can move it — give away ownership, so you can't use it afterwards. To let a function read a value without taking it, pass a reference with & (a 'borrow'); use &mut to let it modify the value. In short: & lets a function use a value without owning it.

fn greet(name: &str) {        // borrows name, does not take it
    println!("Hello {}", name);
}

let name = String::from("Andy");
greet(&name);                 // lend a reference
println!("{}", name);         // still usable — we only borrowed it

That's why parameter types in the coding tutorials often show a &, like name: &str.


Assignment
Follow the Coding Tutorial and let's write some functions.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 5
    Ctrl + Enter to check

    RUST · FUNCTIONS I

    Function Parameters And Arguments in Rust, explained

    What parameters and arguments are

    A function that does one fixed job is limited. Often you want the same code to give different results depending on the input. Parameters are how a function receives that input.

    • A parameter is a named placeholder in the function definition.
    • An argument is the actual value you pass when you call the function.

    In Rust every parameter is written as name: Type. The type is required, and the compiler checks every call against it.

    fn say_hello(name: &str, age: i32) {
        println!("{}, aged {}", name, age);
    }
    
    fn main() {
        say_hello("John", 30); // John, aged 30
        say_hello("Mary", 26); // Mary, aged 26
    }
    

    Arguments are matched to parameters by position: the first argument goes to name, the second to age. Swapping them is a compile error, because 30 is not a &str and "John" is not an i32. Passing too few or too many arguments is also an error.

    Owned values and borrowed values

    Rust has two common types for text, and the difference matters for parameters.

    • &str is a borrowed view of text. A string literal such as "Andy" has this type.
    • String is owned, growable text. "Andy".to_string() or String::from("Andy") creates one.

    A parameter of type String takes ownership of the value. The value is moved into the function, and the caller can no longer use it. A parameter of type &str only borrows, so the caller keeps its value. To lend a String without giving it away, pass a reference with &:

    fn greet(name: &str) {
        println!("Hello {}", name);
    }
    
    fn main() {
        let name = String::from("Andy");
        greet(&name);          // lend a reference
        println!("{}", name);  // still usable: it was only borrowed
    }
    

    Passing a plain &str literal where a String is expected does not compile. The compiler reports expected `String`, found `&str` and suggests adding .to_string().

    The exercise

    The task is to write welcome_user, which takes an owned String called name and prints Welcome, Andy! when given "Andy". Then main_function uses it twice.

    1. Define the function above main_function, with the parameter name: String.
    2. Print the greeting with println!("Welcome, {}!", name). Each {} is replaced by the next argument after the format string. The comma, the space and the final ! are all part of the expected output.
    3. Call it with a literal. The parameter wants a String but "Andy" is a &str, so convert it with "Andy".to_string().
    4. Create a variable with let name = "John".to_string();. It does not need mut, because it is never changed.
    5. Pass the variable with welcome_user(name);. This moves the String into the function.
    fn welcome_user(name: String) {
        println!("Welcome, {}!", name);
    }
    
    fn main_function() {
        welcome_user("Andy".to_string());
        let name = "John".to_string();
        welcome_user(name);
    }
    

    Running these two calls prints:

    Welcome, Andy!
    Welcome, John!
    

    Gotchas

    • Use after move. After welcome_user(name);, the variable name is gone. A second call welcome_user(name); fails with use of moved value: `name`. If you need the value again, pass name.clone() the first time, or change the parameter to &str and pass &name.
    • Parameters are variables inside the function. The function sees only its own parameters and what it creates itself, not the caller's variables. The caller's name and the parameter name are two separate variables that happen to share a name.
    • Unused parameters. A parameter the body never uses compiles, with a warning, and the function ignores whatever you pass.

    Choosing between String and &str

    For a function that only reads text, &str is usually the better parameter type, because callers can pass literals directly and can pass a borrowed String with &name. A String parameter makes sense when the function needs to keep or change the text. This lesson uses String on purpose, so you can see ownership move along with the value.

    For numbers such as i32 there is no such issue. Simple values like these are copied when passed, so the caller can keep using its variable afterward.

    Key takeaways

    • Parameters sit in the definition, written name: Type. Arguments are the values in the call.
    • Arguments match parameters by position and type, and the compiler checks both.
    • A String parameter takes ownership. A &str parameter borrows.
    • Convert a literal with .to_string() when the parameter is a String.
    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free