Functions

Table of contents

Overview

Functions are the core building block for applications. Carbon’s basic function syntax is:

  • parameter: identifier : expression
  • parameter-list: [ parameter , parameter , … ]
  • return-clause: [ -> _< expression _ auto > ]
  • signature: fn identifier ( parameter-list ) return-clause
  • function-definition: signature { statements }
  • function-declaration: signature ;
  • function-call: identifier ( [ expression , expression , … ] )

A function with only a signature and no body is a function declaration, or forward declaration. When the body is a present, it’s a function definition. The body introduces nested scopes which may contain local variable declarations.

Function definitions

A basic function definition may look like:

fn Add(a: i64, b: i64) -> i64 {
  return a + b;
}

This declares a function called Add which accepts two i64 parameters, the first called a and the second called b, and returns an i64 result. It returns the result of adding the two arguments.

C++ might declare the same thing:

std::int64_t Add(std::int64_t a, std::int64_t b) {
  return a + b;
}

// Or with trailing return type syntax:
auto Add(std::int64_t a, std::int64_t b) -> std::int64_t {
  return a + b;
}

Return clause

The return clause of a function specifies the return type using one of three possible syntaxes:

  • -> followed by an expression, such as i64, directly states the return type. This expression will be evaluated at compile-time, so must be valid in that context.
    • For example, fn ToString(val: i64) -> String; has a return type of String.
  • -> followed by the auto keyword indicates that type inference should be used to determine the return type.
    • For example, fn Echo(val: i64) -> auto { return val; } will have a return type of i64 through type inference.
    • Declarations must have a known return type, so auto is not valid.
    • The function must have precisely one return statement. That return statement’s expression will then be used for type inference.
  • Omission indicates that the return type is the empty tuple, ().
    • For example, fn Sleep(seconds: i64); is similar to fn Sleep(seconds: i64) -> ();.
    • () is similar to a void return type in C++.

return statements

The return statement is essential to function control flow. It ends the flow of the function and returns execution to the caller.

When the return clause is omitted, the return statement has no expression argument, and function control flow implicitly ends after the last statement in the function’s body as if return; were present.

When the return clause is provided, including when it is -> (), the return statement must have an expression that is convertible to the return type, and a return statement must be used to end control flow of the function.

Function declarations

Functions may be declared separate from the definition by providing only a signature, with no body. This provides an API which may be called. For example:

// Declaration:
fn Add(a: i64, b: i64) -> i64;

// Definition:
fn Add(a: i64, b: i64) -> i64 {
  return a + b;
}

The corresponding definition may be provided later in the same file or, when the declaration is in an API file of a library, in an implementation file of the same library. The signature of a function declaration must match the corresponding definition. This includes the return clause; even though an omitted return type has equivalent behavior to -> (), the presence or omission must match.

Function calls

Function calls use a function’s identifier to pass multiple expression arguments corresponding to the function signature’s parameters. For example:

fn Add(a: i64, b: i64) -> i64 {
  return a + b;
}

fn Run() {
  Add(1, 2);
}

Here, Add(1, 2) is a function call expression. Add refers to the function definition’s identifier. The parenthesized arguments 1 and 2 are passed to the a and b parameters of Add.

Functions in other features

Other designs build upon basic function syntax to add advanced features:

Alternatives considered

References