Functions

Together with struct, functions are the core building blocks of hot reloading in Mun. Throughout the documentation you've already seen a lot of examples of the fn keyword, which is used to define a function.

Mun uses snake case as the conventional style for function and variable names. In snake case all letters are lowercase and words are separated by underscores.

pub fn main() {
    another_function();
}

fn another_function() {

}

Function definitions start with an optional access modifier (pub), followed by the fn keyword, a name, an argument list enclosed by parentheses, an optional return type specifier, and finally a body.

Marking a function with the pub keyword allows you to publicly expose that function, for usage in other modules or when hot reloading. Otherwise the function will only be accessible from the current source file.

Type Access Modifier

By default, a type or function defined in a module is not accessible outside of its file and submodules. You can expand accessibility in three ways:

  • pub: accessible within the package and externally (incl. marshalling to the host language)
  • pub(package): accessible within the current package but not by anything else
  • pub(super): accessible within parent module and its submodules
# pub fn main() {
#   bar();
# }
// This function is not accessible outside of this code
fn foo() {
    // ...
}

// This function is accessible from anywhere.
pub fn bar() {
    // Because `bar` and `foo` are in the same file, this call is valid.
    foo()
}

// This function is accessible from the parent module and its submodules
pub(super) fn baz() {
    // ...
}

// This function is accessible from the entire package but not externally
pub(package) fn foobar() {
    // ...
}

When you want to interface from your host language (C++, Rust, etc.) with Mun, you can only access pub functions. These functions are hot reloaded by the runtime when they or functions they call have been modified.

Function Arguments

Functions can have an argument list. Arguments are special variables that are part of the function signature. Unlike regular variables you have to explicitly specify the type of the arguments. This is a deliberate decision, as type annotations in function definitions usually mean that the compiler can derive types almost everywhere in your code. It also ensures that you as a developer define a contract of what your function can accept as its input.

The following is a rewritten version of another_function that shows what an argument looks like:

pub fn main() {
    another_function(3);
}

fn another_function(x: i32) {
}

The declaration of another_function specifies an argument x of the i32 type. When you want a function to use multiple arguments, separate them with commas:

pub fn main() {
    another_function(3, 4);
}

fn another_function(x: i32, y: i32) {
}

Function Bodies

Function bodies are made up of a sequence of statements and expressions. Statements are instructions that perform some action and do not return any value. Expressions evaluate to a result value.

Creating a variable and assigning a value to it with the let keyword is a statement. In the following example, let y = 6; is a statement.

pub fn main() {
    let y = 6;
}

Statements do not return values and can therefore not be assigned to another variable.

Expressions do evaluate to something. Consider a simple math operation 5 + 6, which is an expression that evaluates to 11. Expressions can be part of a statement, as can be seen in the example above. The expression 6 is assigned to the variable y. Calling a function is also an expression.

The body of a function is just a block. In Mun, not just bodies, but all blocks evaluate to the last expression in them. Blocks can therefore also be used on the right hand side of a let statement.

# pub fn main() {
#   foo();
# }
fn foo() -> i32 {
    let bar = {
        let b = 3;
        b + 3
    };
    // `bar` has a value 6
    bar + 3
}

Returning Values from Functions

Functions can return values to the code that calls them. We don't name return values in the function declaration, but we do declare their type after an arrow (->). In Mun, a function implicitly returns the value of the last expression in the function body. You can however return early from a function by using the return keyword and specifying a value.

fn five() -> i32 {
    5
}

pub fn main() {
    let x = five();
}

There are no function calls or statements in the body of the five function, just the expression 5. This is perfectly valid Mun. Note that the return type is specified too, as -> i32.

Whereas the last expression in a block implicitly becomes that blocks return value, explicit return statements always return from the entire function:

pub fn main() {
    let _ = foo();
}
fn foo() -> i32 {
    let bar = {
        let b = 3;
        return b + 3;
    };

    // This code will never be executed
    return bar + 3;
}