Type functions
Type functions are another new feature in C2.
Type functions are syntactic-sugar that makes code more readable.
The example below shows how it works:
type Point struct {
i32 x;
i32 y;
}
fn void Point.add(Point* p, i32 x) {
p.x = x;
}
fn void example() {
Point p = { 1, 2 }
// with type-functions
p.add(10);
// Non-sugared call
Point.add(&p, 10);
}
Rules
- Type functions only work on struct/union/enum types
- Type functions are defined in the same module as the struct (not necessarily the same file!)
- For a type type named
Foo
, type-functions must start withFoo.
prefix. - There cannot be a struct member
x
and a type-functionFoo.x
. - For struct/union: a (non-static) type-function is required to have '(const) Type*' as its first argument.
- For enum: a (non-static) type-function is required to have '(const) Type*' or '(const) Type' as its first argument.
- A static type-function has no argument requirements and can only be called on the type itself,
not an instance of the type.
Foo.add()
andf.add()
cannot both exist. - A type-function may also be assigned to variables of type Function with the correct prototype: callback = Foo.add;
- Sub-structs cannot have type-functions
Extra notes:
- It's possible to have public or non-public type-functions for a public struct.
- A type-function itself is a regular function and can also be used as such
- It's also possible to use a type-function call if the variable of struct type is a member of another struct
type Outer struct {
Inner inner;
}
type Inner struct {
// ...
}
fn void Inner.modify(Inner* inner, /* ... */) {
// ...
}
fn void example() {
Outer outer;
outer.inner.modify(/* ... */);
// translates to Inner.modify(&outer.inner);
}
- The following is OK, as type-functions implicitly dereference the object on which they are called:
Type* t = nil;
t.init();
for more examples, see the tests in c2compiler/test/Functions/struct_functions/
Auto conversions
Calling a type-functions in the short form (eg. f.init()) can auto-convert f from Type -> Type* or vice versa.
Examples:
fn void Foo.func1(Foo f) { ... }
fn void Foo.func2(Foo* f) { ... }
fn void test(Foo f) {
f.func1(); // no conversion needed
f.func2(); // auto-converted Foo -> Foo*
}
fn void test(Foo* f) {
f.func1(); // auto-converted Foo* -> Foo
f.func2(); // no conversion needed
}
Rules
Function \ Arg | Foo | Foo* | const Foo | const Foo* |
---|---|---|---|---|
Foo.func() | error | error | error | error |
Foo.func(Foo*) | auto | ok | error | error |
Foo.func(const Foo*) | auto | ok | auto | ok |
Foo.func(Foo) | ok | auto | ok | auto |
Foo.func(const Foo) | ok | auto | ok | auto |
Enum example
type Color enum u8 { Red, Green, Blue }
fn const char* Color.str(Color c) {
switch (c) {
case Red: return "Red";
case Green: return "Green";
case Blue: return "Blue";
}
return "";
}
fn void test1(Color c) {
printf("color = %s\n", c.str());
}
Complex example
See below a more complex example, which uses opaque pointers. The module inner
offers an API:
module inner;
import stdlib;
import stdio;
public type Shape struct @(opaque) {
u8 sides;
// ..
}
// a non-public type-function
fn void Shape.init(Shape* shape, u8 sides) {
shape.sides = sides;
}
// a static type-function, called as Shape.create(..)
public fn Shape* Shape.create(u8 sides) {
Shape* shape = stdlib.malloc(sizeof(Shape));
shape.init(sides);
return shape;
}
// a public, const type-function, first argument: const Shape*
public fn void Shape.print(const Shape* shape) {
stdio.printf("shape with %d sides\n", shape.sides);
}
// a public, type-function, first argument: Shape*
public fn void Shape.free(Shape* shape) {
stdlib.free(shape);
}
which is used by the module outer
:
module outer;
import inner local;
fn void example() {
Shape* s = Shape.create(3);
s.print();
s.free();
}
NOTE: outer
is not allowed to access Shape's regular members directly.