Fuzion Logo
fuzion-lang.dev — The Fuzion Language Portal
JavaScript seems to be disabled. Functionality is limited.

Variadic Functions

Variadic functions are functions of indefinite arity, i.e., functions that accept a variable number of actual arguments. The most famous example of a widely used variadic function is the printf function of the C standard library.

General

Motivation

Variadic functions can be extremely convenient for operations that combine several values whose exact number and types are only known by the caller of that function. In particular, string formatting or the creation of containers such as sets can often benefit from variadic functions.

Security

Variadic functions, especially those provided by the C and C++ languages, contribute to security vulnerabilities (e.g., Venerable Variadic Vulnerabilities Vanquished or Variadic Functions How they contribute to security vulnerabilities and how to fix them). The main surface of attack are

  • the lack of static type checking when processing the arguments, allowing arbitrary casts and memory accesses,
  • the lack of static argument count checking, allowing access of arbitrary memory on the runtime stack
  • , and
  • the possibility of external control for argument processing, e.g., by providing a string passed to a function of the printf family from external input as in str = read(); printf(str);

Basic approaches

There are basically three approaches to the implementation of variadic functions in current programming languages.

  • Unsafe: The responsibility for proper type safety and respecting the argument count is left to the developer.
  • Fixed type: Variadic functions are made type safe by permitting only a single type for variadic arguments, but the compiler ensures that the argument count at the call site matches the number of arguments that can be processed by the callee.
  • Generic: The language ensures that both, the argument count and the actual types of all arguments are available and enforced to be respected.

No Variadic Support

Some languages do without variadic arguments and provide some alternative mechanisms that give the feel of variadic arguments.

Rust

Rust uses macros and recursive macro evaluation to emulate variadic calls.

Fortran

Fortran offers optional arguments that may be used to give the feel of a variable length argument list.

Unsafe Approaches

Languages that support unsafe variadic arguments include C, C++

C/C++

In C/C++, intrinsic functions permit processing variable argument lists. Processing requires a local data structure of type va_list that is initiated using va_start, followed by an arbitrary number of calls to va_arg that extracts the next argument of a specified type (that is neither checked statically nor at run-time) and finished by a call to va_end.

Fixed Type Approaches

C#/Java/Scala/Go

In these languages, variadic arguments are merely syntax sugar at the call site for wrapping a list of values of the same type into an array. The actual call is done using a single argument whose type is an array. Arrays in these languages properly check the correctness of indices on accesses to their elements, so argument count checking is implicit. All variadic argument have to be of the same type, which becomes the type of the array elements and is checked with the language's type checker.

GoLang uses its re-sizable arrays called slice for variadic function arguments.

In Fuzion, we can use the wrapping of arguments in an array as well to create variadic functions, here is an example that concatenates the String representation of the arguments provided via a Sequence:

Generic Approaches

I am not aware of a programming language that permits compile time type checking and code specialization. Fuzion attempts to provide this.

Fuzion Approach

Basic Idea

Fuzion has open type parameters A type ... that are used to provide features like tuple, choice or Function that depend on an arbitrary, but fixed list of types. The idea is to use this to provide variadic functions. In fact, the constructor of a tuple is just that, it takes an arbitrary number of type parameters and value arguments:

public tuple(public A type...,
             public values A...
             ) : property.orderable,
                 property.hashable
is

However, originally, the values field of a tuple may only be accessed for a concrete tuple type using a select suffix like values.1:

Operation on arbitrary types: typed_applicator

Instead, Fuzion defines a base library feature typed_applicator as follows:

public typed_applicator(E type) is

  public apply(T type, e E, v T) B
  =>
    abstract

This permits to perform an arbitrary operation on a value v of an arbitrary type T.

Compile-time type checking

Since Fuzion permits compile-time type checks on type parameters using infix : as in if T : String then ..., we can provide different implementations variants within a redefinition of apply and have the compile create only code for the actual variant that matches the actual type parameter after monomorphization.

Folding values of arbitrary types: typed_fold

Calling an argument field whose type is an open type parameter returns an instance of a compiler-generated feature that provides a typed_fold operation as follows:

  public typed_foldf(E type, A type : typed_applicator E, e E, a A) E
  =>
     e2 := a.apply T1 e  v1
     e3 := a.apply T2 e2 v2
       ..
     e<n> := a.apply T<n> e<n-1> v<n>
     e<n>

Consequently, we can process all the elements as follows

The apply function we defined in concat will be called with the type parameter and the value of each of the actual arguments in the call.

We can now go one step further and check types against constraints to do more specific operations:

The actual code involves no reference type values, so no boxing is required and all code could be inlined by the compiler. Furthermore, the code of `apply` gets specialized for each actual type parameter, so the type constraints like T : String will be evaluated at compile time and the code of the large if will be be reduced to only one of the then blocks or the final else block.

We can provide helper features like container.typed_fold that avoid the need of declaring an inner feature like concat by inheriting as follows:

An alternative would be a helper container.variadic that does not use a res field, but instead a process feature:

Comparison

Language
Example
Fuzion Java C/C++ Python JavaScript
sum of f64/i32

  static double sum(Object... values)
  {
    var res = 0.0;
    for (var v : values)
      {
        if      (v instanceof Integer i) { res = res + i; }
        else if (v instanceof Double  d) { res = res + d; }
        else throw new Error();
      }
    return res;
  }

  System.out.println(sum(3.14, 32, 16.0, 8));
          

#include <stdarg.h>
#include <stdio.h>
#include <assert.h>

double sum(char *types, ...)
{
  va_list ap;
  double res = 0;

  va_start(ap, types);
  while (*types)
    {
      if      (*types == 'i') { res = res + va_arg(ap, int); }
      else if (*types == 'd') { res = res + va_arg(ap, double); }
      else                    { assert("unknown type"); }
      types++;
    }
  va_end(ap);
  return res;
}

int main(int argc, char* argv[])
{
  printf("%f\n", sum("didi", 3.14, 32, 16.0, 8));
  return 0;
}
          

def sum(*values):
  res = 0.0
  for v in values:
    res += v
  return res

print(sum(3.14, 32, 16.0, 8))
          

function sum(...values) {
  let res = 0.0
  for (arg of values) {
    res += arg
  }
  return res
}
console.log(sum(3.14, 32, 16.0, 8))
          
comparison

✅ argument count checked
✅ type safe at runtime
✅ type safety checked at compile time
✅ no dynamic type checks at runtime needed
✅ no boxing to heap objects
🟡 some boilerplate: redefining apply and inheriting container.variadic

✅ argument count checked
✅ type safe at runtime
❌ type safety not checked at compile time
🟡 dynamic type checks at runtime
🟡 boxing to heap objects
✅ very little boilerplate

❌ argument count not checked
❌ not type safe at runtime
❌ type safety not checked at compile time
❌ no dynamic type checks at runtime possible
❌ no boxing to heap objects possible
❌ no boilerplate, but very manual

✅ argument count checked
❌ not type safe at runtime
❌ type safety not checked at compile time
🟡 dynamic type checks at runtime
🟡 boxing depending on implementation (NYI: who knows how this is done?)
✅ absolutely no boilerplate

Conclusion

Variadic functions are poorly supported by existing languages, their use results in type safety issues that cannot be resolved at compile time.

In Fuzion, we provide two approaches:

  • First, using `Sequence` as the argument type and syntax sugar for the creation of arrays. This provides the same level of functionality and type safety as Java/C#/etc.
  • Alternatively, open type parameters provide full compile-time type checking and code specialization that avoids all runtime type checks and related runtime overheads for boxing and type checks.

What we might need is some more syntax sugar to reduce the boilerplate code. One approach would be to extends syntax sugar for `lambda` to support type parameters, see #5892.

last changed: 2025-09-09