Functions

Functions are one of the most fundamental concepts in Rust. You define a function by using the fn keyword.

Input Arguments

Rust functions can define zero or more input arguments. Each input argument must declare its data type.

For example, check out the input arguments declared within the parentheses of the following function.

#![allow(unused)]
fn main() {
fn get_full_name(first_name: String, last_name: String) {
  return format!("{} {}", first_name, last_name);
}
}

Return Values

Functions can optionally define a return value. By default, the return value of a function is the Rust "unit" type, displayed as a pair of empty parentheses ().

#![allow(unused)]
fn main() {
fn get_name() { } // This function returns a unit type
}

To specify an alternate return value than the "unit" type, add a "thin arrow" after your function input arguments, and specify the return type. After specifying the return type, you can use the return keyword to specify a value that you want the function to return, inside the function body.

#![allow(unused)]
fn main() {
fn get_name() -> String {
  return "Trevor".to_string();
}
}

In addition to returning primitive types, such as integers, floating-point values, and strings, you can also return enum variants or an instance of a user-defined struct. In the following example, we'll define a Dog struct with a name field, and then return a new instance of the Dog type to the function caller. Note that the function named get_dog has declared a return type of Dog.

#![allow(unused)]
fn main() {
struct Dog {
  name: String,
}

fn get_dog() -> Dog {
  return Dog{ name: "Fido".to_string() };
}
}

Calling Functions

To call a Rust function from your application or library, use the function name, followed by parentheses, containing the input arguments.

Rust does NOT support named arguments, as some other languages do. All input arguments are interpreted positionally, according to the order they're declared in. The documentation for a given function, or your development environment, should indicate which order the function arguments ought to be passed in.

Let's define a function with input arguments and a return type.

#![allow(unused)]
fn main() {
fn get_name(first: &str, last: &str) -> String {
  let mut new_string = String::from(first);
  new_string.push_str(" ");
  new_string.push_str(last);
  return new_string;
}
}

To call this function, let's try the following example:

fn main() {
  let full_name = get_name("Trevor", "Sullivan");
  println!("{full_name}");
}

Discard Function Return Value

Capturing the return value of a function call is optional. To discard the return value of a function, you can either:

  • Skip assigning the result to a variable
  • Assign the function's result to the underscore character _
fn main() {
  _ = get_name("Trevor", "Sullivan"); // Valid, explicitly discards the function return value
  get_name("Trevor", "Sullivan");     // Valid, implicitly discards the function return value
}

In some cases, the Rust compiler will issue a warning for implicitly discarding a function return value. For example, if a function returns a Result<T, E>, you might see the following warning:

unused Result that must be used this Result may be an Err variant, which should be handled #[warn(unused_must_use)] on by default

Here's an example:

fn get_name(first: &str, last: &str) -> Result<String, Box<dyn std::error::Error>> {
  let mut new_string = String::from(first);
  new_string.push_str(" ");
  new_string.push_str(last);
  return Ok(new_string);
}

fn main() {
  get_name("Trevor", "Sullivan");
}

Async Functions

Rust functions can be declared as asynchronous, which means that they will return a type implementing the Future trait, by default. You can use the async Rust keyword to denote a function as being asynchronous.

Keep in mind that a Future will not be executed unless you include an async executor in your application. You can write your own executor or you can use an off-the-shelf async executor like smol or tokio.

#![allow(unused)]
fn main() {
async fn get_age() -> i32 {
  return 34;
}
}

We will discuss asynchronous functions, in more depth, in a separate section.