The "define" and "typedef" statements

The define statement in C3 is intended for aliasing identifiers. typedef is used like in C to create type aliases. The syntax is slightly different from C in both cases in order to increase clarity.

Defining a type alias

typedef <type alias> = <type> creates a type alias. Type aliases need to follow the name convention of user defined types (i.e. capitalized names with at least one lower case letter).

typedef CharPtr = char*;
typedef Numbers = int[10];

Function pointers must be aliased in C3. The syntax is somewhat different from C:

typedef Callback = fn void(int a, bool b);

This defines an alias to function pointer type of a function that returns nothing and requires two arguments: an int and a bool. Here is a sample usage:

Callback cb = my_callback;
cb(10, false);

Distinct types

typedef may also be used to create distinct new types. Unlike type aliases, they do not implicitly convert to any other type.

typedef Foo = distinct int;
Foo f = 0;
f = f + 1;
int i = 1;
// f = f + i Error!
f = f + (Foo)i; // Valid

Function and variable aliases

The define is used to create aliases for functions and variables.

The syntax is define <alias> = <original identifier>.

fn void foo() { ... }
int foo_var;

define bar = foo;
define bar_var = foo_var;

fn void test() 
{
  // These are the same:
  foo();
  bar();

  // These access the same variable:
  int x = foo_var;
  int y = bar_var;
}  

Using define and typedef to create generic types, functions and variables

Generic modules uses typedef and define to create aliases to parameterized types, functions and variables:

 import generic_foo;

 // Parameterized function aliases
 define int_foo_call = generic_foo::foo_call<int>;
 define double_foo_call = generic_foo::foo_call<double>;

 // Parameterized type aliases
 typedef IntFoo = Foo<int>;
 typedef DoubleFoo = Foo<double>;

 // Parameterized global aliases
 define int_max_foo = generic_foo::max_foo<int>;
 define double_max_foo = generic_foo::max_foo<double>;

For more information, see the chapter on generics.

Function pointer default arguments and named parameters

It is possible to attach default arguments to function pointer aliases. There is no requirement that the function has the same default arguments. In fact, the function pointer may have default arguments where the function doesn't have it and vice-versa. Calling the function directly will then use the function's default arguments, and calling through the function pointer will yield the function pointer alias' default argument.

Similarly, named parameter arguments follow the alias definition when calling through the function pointer:

typedef TestFn = fn void(int y = 123);

fn void test(int x = 5)
{
    io::printfn("X = %d");
}

fn void main()
{
    TestFn test2 = &test;
    test();        // Prints X = 5
    test2();       // Prints X = 123
    test(.x = 3);  // Prints X = 3 
    test2(.y = 4); // Prints X = 4
}