Skip to content

Volume 05: Functions

1. Introduction

This volume defines functions in the Rethon programming language. It describes function declarations, function names, parameters, type annotations, return behavior, arguments, generics, overloading, nested functions, closures, lambda expressions, function values, function scope, recursion, methods, constructors, and future extension points.

This document is part of the official Rethon Language Specification. It is normative rather than descriptive. Implementations, tooling, documentation systems, and other source-processing tools shall treat the rules in this volume as authoritative.

This volume does not define backend calling conventions, ABI layout, machine-level code generation, or runtime-specific implementation details. It defines the source-level and language-level behavior of functions as programmer-visible constructs.

Rethon is Python-inspired in language philosophy and Python-first in surface syntax where practical. The function model is statically typed, value-oriented, protocol-oriented, and designed for native performance and long-term maintainability. It shall therefore prioritize readability, explicit contracts, predictable scope, and language clarity while preserving the Python-like surface style that the language has already adopted.

Function declarations shall use def.

Example:

def add(a: int, b: int) -> int:
    return a + b

2. Function Declarations

A function declaration shall introduce a named callable entity.

A function declaration shall specify:

  • the function name
  • zero or more parameters
  • an optional return type annotation
  • a body

Function declarations shall be introduced with def.

Example:

def greet(name: str) -> str:
    return "Hello, " + name

A function declaration shall create a new function scope for its body.

A function declaration may appear at module scope, inside a type body as a method, or inside another function as a nested function, subject to the rules of this volume and the surrounding specification volumes.

A function declaration shall be a declaration in the sense defined by the declarations and bindings volume. It shall introduce a named binding or named callable entity according to the language's name-resolution rules.

A function body shall consist of one or more statements or expressions governed by the syntax rules of the language.

The body of a function shall be a block and shall use indentation-based structure.

3. Function Names

A function name shall be an identifier that names the function declaration.

Function names shall be case-sensitive.

Function names should normally use snake_case.

Examples:

def parse_text(input: str) -> str:
    return input

def calculate_area(width: float, height: float) -> float:
    return width * height

Function names shall be visible according to scope and visibility rules defined elsewhere in the specification.

A function name shall not be reserved solely because it names a function. Ordinary identifier rules shall apply, subject to keyword reservation and any later contextual restrictions defined by the language.

Function names should be chosen to express intent clearly. A function name shall usually describe an action, transformation, query, or constructor-like purpose.

Examples of preferable naming patterns include:

  • add
  • parse_text
  • format_value
  • compute_total
  • from_json
  • as_string

A function name should not obscure meaning through unnecessary abbreviation when a clearer name is available.

4. Parameters

A parameter is a named input declared by a function.

A function may declare zero or more parameters.

Parameters shall appear in the function declaration's parameter list.

Example:

def add(a: int, b: int) -> int:
    return a + b

Each parameter shall have a name and may have a type annotation.

The parameter list shall define the function's required and optional inputs according to the rules in this volume.

Parameters shall be ordered. When arguments are supplied positionally, their order shall determine how they are matched to parameters unless named arguments are used.

A parameter shall introduce a binding within the function body.

A parameter binding shall be in scope throughout the function body, including nested blocks inside the function body, subject to shadowing rules.

A parameter shall ordinarily behave as an immutable binding within the function body unless a later language rule explicitly allows reassignment to parameter bindings. The language shall not require parameter reassignment as part of ordinary programming style.

A parameter list may include required parameters, optional parameters with default values, variadic parameters, and parameter groups with special calling behavior as defined later in this volume.

5. Parameter Type Annotations

A parameter type annotation shall state the type of the parameter explicitly.

Example:

def add(a: int, b: int) -> int:
    return a + b

Parameter type annotations shall be allowed and encouraged for clarity.

Parameter type annotations shall be required for ordinary function declarations unless a later language rule explicitly defines a specialized form that permits omission under controlled circumstances.

Type annotations for parameters shall follow the rules of the type system volume.

A parameter annotation may use any valid type expression defined by the type system, including primitive types, value types, reference types, protocol types, nullable types, generic types, and type aliases.

Examples:

def print_name(name: str) -> None:
    pass

def scale(value: float, factor: float) -> float:
    return value * factor

def is_ready(flag: bool) -> bool:
    return flag

A parameter annotation shall be read as a contract on values accepted by the function.

A parameter annotation shall not imply how the value is stored or passed at the backend level.

A parameter annotation shall not be inferred from the function body in place of an explicit declaration unless a later language rule explicitly permits such inference.

6. Return Type Annotations

A return type annotation shall state the type produced by a function.

Example:

def add(a: int, b: int) -> int:
    return a + b

A return type annotation shall be allowed and encouraged for clarity.

A return type annotation shall be required for ordinary function declarations unless a later language rule explicitly defines a specialized form that permits omission under controlled circumstances.

The return type shall follow the arrow syntax ->.

Examples:

def greet(name: str) -> str:
    return "Hello, " + name

def log(message: str) -> None:
    pass

The return type annotation shall describe the type of value returned from the function.

A return type annotation shall use any valid type expression allowed by the type system.

When a function returns no usable value, its return type shall be None or the language's later-defined unit-like return form if one is adopted by the specification. Until a more specific unit convention is introduced elsewhere, None shall be used as the explicit return type for functions that do not return a meaningful value.

7. Return Statements

A return statement shall exit the current function and optionally provide a value to the caller.

Example:

def add(a: int, b: int) -> int:
    return a + b

A return statement shall be valid only within a function body.

A return statement shall not be used at module scope or in other non-function contexts unless a later volume defines a special construct that permits it.

A return statement may appear in any reachable location inside a function body, subject to the syntax and control-flow rules of the language.

If a function has a return type other than the no-value return type, the returned expression shall satisfy the declared return type.

If a function returns no value, a bare return shall be allowed where that form is meaningful.

Examples:

def exit_early(flag: bool) -> None:
    if flag:
        return
def square(value: int) -> int:
    return value * value

A function body shall be allowed to contain multiple return statements.

The language shall not require a single return statement per function.

8. Functions Without Return Values

A function that does not return a meaningful value shall declare that fact explicitly.

The preferred return type for such functions shall be None until and unless later specification work defines a distinct unit-like return type convention.

Examples:

def print_message(message: str) -> None:
    pass

def log_value(value: int) -> None:
    print(value)

A function with a no-value return type shall be used for operations whose purpose is side effects, output, mutation, or coordination rather than result production.

A no-value return type shall still be a type-level contract. It shall not mean that the function is incomplete or abstract.

A function declared to return no value shall not return an incompatible value.

A bare return shall be permitted in such functions where early exit is appropriate.

A function may also end naturally without an explicit return statement if the language semantics for that function type permit it and the resulting behavior is equivalent to a no-value return.

9. Default Arguments

A default argument is a function parameter that has a default value used when the caller omits the corresponding argument.

Default arguments shall be supported.

Example:

def greet(name: str, prefix: str = "Hello") -> str:
    return prefix + ", " + name

A default value shall be a compile-time valid expression according to the surrounding language rules.

Default arguments shall appear after required arguments in the parameter list unless a later syntax rule explicitly defines a more expressive arrangement.

A caller omitting an argument for a defaulted parameter shall cause the parameter to take its declared default value.

A default value shall be type-compatible with the declared parameter type.

Example:

def repeat(text: str, count: int = 2) -> str:
    return text + text

Default arguments improve convenience while preserving explicit contracts.

Default arguments shall not depend on hidden runtime mutation of parameter metadata. Their meaning shall be visible from the source declaration.

10. Named Arguments

Named arguments shall be supported.

A caller shall be able to pass an argument by associating it with a parameter name.

Example:

def greet(name: str, prefix: str = "Hello") -> str:
    return prefix + ", " + name

greet(name: "Rethon", prefix: "Welcome")

Named arguments improve readability when a function takes multiple parameters of similar type or when argument meaning is more important than positional order.

Named arguments shall use the parameter names defined by the function declaration unless a later language rule defines aliases or other call-site conventions.

Named arguments shall be especially useful for functions with several parameters of the same or similar types.

Examples:

def configure(width: int, height: int, title: str) -> None:
    pass

configure(width: 800, height: 600, title: "Main Window")

The language shall preserve the distinction between argument names at the call site and parameter names in the declaration.

11. Positional Arguments

Positional arguments shall be supported.

A positional argument shall be matched to a parameter based on order.

Example:

def add(a: int, b: int) -> int:
    return a + b

add(1, 2)

Positional arguments shall be the default calling style when no names are written at the call site.

Positional arguments shall be used when order is clear and readability is preserved.

The language may allow a mixture of positional and named arguments when that remains unambiguous and consistent with the function declaration's parameter rules.

Examples:

def draw_point(x: int, y: int, color: str) -> None:
    pass

draw_point(10, 20, color: "red")

The language shall define any restrictions on the ordering of positional and named arguments in a way that supports readability and predictable diagnostics.

12. Variadic Arguments

Variadic arguments shall be supported.

A variadic parameter shall accept zero or more arguments in a single position.

Example:

def sum_all(*values: int) -> int:
    total = 0
    for value in values:
        total = total + value
    return total

Variadic arguments shall be useful for functions that naturally operate on a sequence of inputs.

A variadic parameter shall introduce a collection-like view of the supplied arguments according to the type system and later syntax rules.

The exact internal representation of variadic arguments shall not be specified in this volume.

The language may support more than one variadic form if later specification work justifies it, such as positional variadic arguments and named variadic arguments. The initial specification shall prefer a simple and readable form.

Examples:

def print_all(*items: str) -> None:
    for item in items:
        print(item)

A variadic parameter shall appear in a position that keeps function declarations readable and unambiguous.

13. Generic Functions

Generic functions shall be supported.

A generic function is a function that declares one or more type parameters.

Example:

def identity<T>(value: T) -> T:
    return value

Generic functions shall integrate with the generic system defined in the type system volume and the generics direction ADR.

A generic function shall preserve type safety while allowing reusable abstractions over multiple types.

Generic functions shall support protocol-based constraints as defined by the type system.

Example:

def max<T: Comparable>(a: T, b: T) -> T:
    if a.compare_to(b) >= 0:
        return a
    return b

A generic function shall be readable and explicit in its type parameter declaration.

The language shall not require templates, metaprogramming tricks, or hidden inference machinery that obscures the generic contract.

14. Function Overloading

Function overloading shall be supported where it improves readability and clarity.

Function overloading means multiple function declarations sharing the same name while differing in parameter types, parameter count, or other disambiguating signatures permitted by the language.

Overloading shall be resolved statically according to the rules of the type system and call-site information.

Example:

def format(value: int) -> str:
    return "int"

def format(value: str) -> str:
    return value

Function overloading shall remain predictable and should not create ambiguous or surprising calls.

If a call cannot be resolved uniquely and safely, the language shall diagnose an error.

The specification may later refine the exact overload resolution algorithm, but the source-level principle shall remain: overloads are a convenience for clear APIs, not a mechanism for hidden runtime polymorphism.

15. Nested Functions

Nested functions shall be supported.

A nested function is a function declared inside another function.

Example:

def outer(value: int) -> int:
    def inner(delta: int) -> int:
        return value + delta

    return inner(1)

Nested functions shall obey lexical scope rules.

A nested function shall be visible only within the scope that contains it, unless a later volume defines a mechanism for exporting or returning function values.

Nested functions shall be useful for local helpers, composition, and closure capture.

A nested function shall not require special syntax beyond ordinary function declaration syntax, provided the surrounding indentation makes the nesting clear.

16. Closures

Closures shall be supported.

A closure is a function value that captures names from an enclosing lexical scope.

Example:

def make_adder(base: int) -> Callable[[int], int]:
    def add(delta: int) -> int:
        return base + delta

    return add

Closures shall preserve the value of captured names according to the language's lexical scope and memory model rules.

Captured names shall remain valid for as long as the closure requires them, subject to the language's memory safety and lifetime guarantees.

Closures shall be a natural result of nested functions and function values.

The specification shall define closure behavior in terms of observable semantics rather than backend implementation details.

17. Lambda Expressions

Lambda expressions shall be supported as a concise function form.

A lambda expression shall define an anonymous function expression.

Example:

lambda value: value + 1

Lambda expressions shall be suitable for short, local, and readable function definitions.

Lambda expressions should remain simple. The language should not turn lambda syntax into a hidden alternative full-function language with excessive special cases.

The exact syntax of lambda expressions shall remain consistent with the language's broader Python-inspired surface style while preserving clarity and static typing.

A lambda expression may be used where a function value is expected.

If a lambda requires more than a simple expression body, a named function declaration should usually be preferred for readability.

Example:

let increment = lambda value: value + 1

18. Function Values

Functions shall be first-class values.

A function value is a value that refers to a function, closure, lambda expression, or method value where such conversion is allowed by the language.

Example:

def add(a: int, b: int) -> int:
    return a + b

let operation = add

Function values shall be usable wherever a function-typed value is expected.

The type system shall define how function values are typed, compared, and constrained.

Function values shall support being passed as arguments, returned from functions, stored in variables, and captured by closures where the language permits.

The language shall preserve the difference between a function declaration and a value that refers to that function.

19. Function Scope

A function body shall introduce a new lexical scope.

Parameters and local declarations inside the function shall be visible within the function body according to lexical scope rules.

Example:

def greet(name: str) -> str:
    prefix = "Hello"
    return prefix + ", " + name

Names declared inside a function shall not be visible outside the function unless they are returned, captured, or otherwise exposed through a language-defined mechanism.

Function scope shall interact with nested scopes, closures, and name resolution as defined by the declarations and bindings volume.

Local names shall shadow outer names according to ordinary shadowing rules.

Function scope shall be lexical, predictable, and local.

20. Recursion

Recursion shall be supported.

A function may call itself directly or indirectly through other functions.

Example:

def factorial(n: int) -> int:
    if n <= 1:
        return 1
    return n * factorial(n - 1)

The language shall not require any special syntax to enable recursion.

Recursive functions shall obey the same typing, scope, and return rules as non-recursive functions.

The specification does not require tail-call optimization or any specific backend optimization strategy. Any such optimization, if provided, shall be an implementation detail rather than a source-level guarantee.

21. Methods

Methods shall be supported.

A method is a function declared within a type body and associated with that type.

Example:

struct Point:
    x: int
    y: int

    def magnitude(self) -> float:
        return 0.0

Methods shall be subject to the same general declaration, parameter, return, and scope rules as ordinary functions, with additional association to the containing type.

A method shall be able to access the members of its containing type according to the object model and access rules defined elsewhere in the specification.

The first parameter of an instance method may conventionally represent the receiver. The exact receiver naming convention shall be defined by the object model and syntax volumes.

Methods shall be able to participate in protocol conformance, overloading, generic constraints, and function values where the language permits.

22. Constructors

Constructors shall be supported.

A constructor is a special function used to create a new value of a type.

The canonical constructor syntax shall be Python-like in surface form where practical, but the language may define constructor semantics in a way that differs from Python when required by static typing, value orientation, or memory safety.

Example:

class User:
    def __init__(name: str) -> User:
        return User(name: name)

The exact constructor syntax and return behavior shall be interpreted consistently with the object model and type system volumes.

Constructors shall be used to establish a valid value or object state at creation time.

A constructor shall not be allowed to bypass type safety or invariants established by the type system.

The specification may later refine the exact relationship between constructors, allocation, initialization, and object identity. The source-level contract is that constructors create values of the containing type.

23. Future Extensions

This volume defines the current direction for functions in Rethon, but future specification work may extend the model in carefully controlled ways.

Possible future extensions include:

  • additional parameter-passing forms if they are justified by the type system and object model
  • richer callable type expressions
  • additional overload-resolution constraints
  • refined constructor syntax or initialization forms
  • more explicit support for method categories or receiver forms
  • future annotations for function purity, effects, or concurrency if the language later requires them

Any future extension shall preserve the current direction:

  • Python-inspired surface syntax
  • static typing
  • explicit type annotations where required
  • readable declarations
  • lexical scope
  • value-oriented and protocol-oriented design
  • maintainable APIs and abstractions

24. Summary

Rethon shall define functions with def and shall preserve a Python-like surface syntax wherever practical.

Functions shall support named parameters, type annotations, return annotations, default arguments, named arguments, positional arguments, variadic arguments, generic parameters, overloading, nested functions, closures, lambda expressions, function values, recursion, methods, and constructors.

The function model shall remain statically typed, readable, and explicit where clarity requires it. The language shall use primitive types such as int, float, bool, str, char, and bytes as ordinary type annotations for function signatures and examples.

The specification shall define functions as first-class language constructs while keeping the model simple enough to understand, flexible enough to support real-world code, and coherent with Rethon's Python-inspired but non-compatible identity.

If future changes are needed, they shall preserve the core principles of readability, static typing, native performance, memory safety, and long-term maintainability.