The previous page showed how to call functions as part of an expression. Now it is time to show how to declare and implement functions. In fact, all logic (including expressions) must be defined as part of the implementation of a function (or method).

Let's start with a simple example of a function definition and its use:

// Define the 'square' function
fn square(a f32) f32
	return a*a

// Use square to calculate the area of a circle	
area = Float.pi * square(r)

This example shows the key parts of an function definition:

Parameter Variables

The declaration of a functions's parameters is a comma-separated list of variable names, enclosed in parentheses. Each parameter variable corresponds on a one-to-one basis to the value arguments passed to the function when it is called. They must match in both number and type:

// Define a method that returns the addition of its two parameters
fn add(a i32, b i32) i32
	return a+b

add(2,3)  // returns 5, since a is set to 2 and b to 3
add(2)    // ERROR! add() needs exactly two values, not one or three
add(2.0, 4.0)  // ERROR! add() needs integers, not floating point numbers

Parameters are variables, usable within the function. The function's signature effectively declares the parameter variables in the same format as any variable declaration. In the example above, the permission is not specified and therefore imm is assumed. That restricts the function's logic from changing the value of these passed parameters.

To allow a parameter's value to be changed, precede the name with mut. Mutation of the parameter's value has no effect on the caller since the parameter's value is a copy of the caller's value. Changes to one value do not affect the other.

fn weird(a i32, mut b i32)
	a = 34    // ERROR! a is immutable and may not be changed
	b = 4     // b may be changed since it is declared 'mut'

number = 3
weird(5, number)
// number is still 3, despite weird() changing b to 4

Parameter Defaults

If desired, default values may be specified for any parameter value using the assignment operator '='. The default value is what we want the parameter to have if the caller provides no value for it. The default value can only be a literal value.

fn next(nbr i32, incr i32 = 1) i32
	return nbr + incr

next(5,2) // returns 7
next(4)   // returns 5 (using incr's default value of 1)
next()    // ERROR! no default value for nbr

Implementation Block

The function's implementation block is represented by a sequence of indented statements that perform the function's logic. Generally, each statement is performed in order.

Most statements are just expressions. A statement could also be a local variable declaration, some nested block, or one of a set of special statements (e.g., 'return').

Local Variable Declarations

Any variable declared wihin a function (including its parameters) has a scope that is local to that function. It cannot be referenced or used outside of that scope.

Local variables provide a working state for the function. Every time a function is called, space is allocated on the execution stack for all its declared local variables. When the function is finished and returns to its caller, its local variable space is automatically freed from the stack.

fn summult(a f32, b f32) f32
	imm sum = a + b    // local variable declaration
	imm mult = a * b   // local variable declaration
	return sum / mult

This means that local variables are exclusive to that call. Two identically-named local variables in different functions, or even two calls to the same function, will not collide with each other.

Return Statement

A return statement may be placed at the end of any block. When encountered, execution of the function ceases and the comma-separated values specified after return are returned to the caller. The number and types of all return values must match the return types declared on the function's signature.

fn ceil(x i32) i32, i32
	if x > 6
		return x, 6
	return x, x

mut a,b = ceil(8)   // returns 8,6
a,b = ceil(3)       // returns 3,3

Implicit returns

A function does not have to specify a return statement at the end of its main block. If the function signature does not declare a return value, the function just returns after the last statement is performed.

If the function signature declares that values must be returned, an attempt is made to matching the values on the last statement or block of the function:

Should the correct number of type-matching return values not be found as described above, a compile error will result.

Recursive Functions

A function may call itself recursively:

fn factorial(x i32, prod i32 = 1)
	return prod if x<=1
	factorial(x-1, prod*x)

If the function returns a single value calculated by any function, this may be "tail-call" optimized. This optimization improves performance and reduces the risk of execution stack overrun with recursive calls.

extern

It is possible to use of functions that have been implemented in a different language (e.g., C). Such functions still need to be properly declared to Cone. This is done by preceding the function declaration with extern. No implementation block should be specified, since its logic is implemented elsewhere.

extern fn printFloat(nbr f32)

An extern block may be used when multiple external functions require declaration. Externally initialized global variables may also be declared this way.

extern
	fn printFloat(nbr f32)
	fn printInt(nbr i32)
	mut indentation

Related Capabilities

Other language features offer a similar capability as functions:

_