Functions, calls, and staging
TinyFX functions are statically typed and may be overloaded. The checker binds each call to one declaration, maps authored arguments to parameters, applies defaults, records coercions, and passes that decision to every backend. This page explains source rules; the generated operation reference is the authoritative index of builtin and host operation signatures.
See types for function, trait, and resource types, and GPU programming for shader and compute-specific rules.
Declaring functions
An ordinary function uses fn. Parameters have types; a return type is
optional and is inferred from return statements when omitted.
fn dot2(a: float2, b: float2) -> float {
return a.x * b.x + a.y * b.y;
}
fn brighten(color: float4, factor: float = 1.1) {
return (color.rgb * factor, color.a);
}Several declarations may share a name when their parameter types make calls unambiguous. The checker selects the best matching overload; an equally good match is an error. Return type alone does not distinguish overloads.
Functions may be declared pub, but imports presently do not provide
file-scoped module visibility. pub is meaningful for struct/enum fields and
methods, which are otherwise private; see types.
Calls, defaults, and named arguments
Calls can use positional arguments, then named arguments in any parameter order. Positional arguments must come before the first named argument; no parameter may be supplied twice. Omitted parameters must have defaults.
fn orbit(
target: float3,
distance: float = 500.0,
yaw: float = 0.0,
pitch: float = 0.4,
) -> float3 {
// ...
return target;
}
let a = orbit((0.0, 0.0, 0.0));
let b = orbit((0.0, 0.0, 0.0), yaw: 1.2, distance: 320.0);Argument expressions still evaluate in authored source order, even when names reorder their receiving parameters. Defaults are evaluated by the callee in its own declaration context, rather than reconstructed by each caller.
Named arguments are a general function/method feature, not a guarantee that
every special form accepts labels. Function values and matrix constructors are
positional; drawAdvanced is named-only; and an operation can document its
own aliases or restrictions. Consult the operation reference
when calling a builtin or host operation.
Methods and static APIs
An impl method with self as its first parameter is an instance method; one
without self is a static method. self must be first.
struct Range { pub low: float, pub high: float }
impl Range {
pub fn contains(self, value: float) -> bool {
return value >= self.low && value <= self.high;
}
pub fn unit() -> Range {
return Range { low: 0.0, high: 1.0 };
}
}
let unit = Range::unit();
let ok = unit.contains(0.25);Fields and methods are private unless pub; code outside the owning impl
cannot bypass that rule. The same syntax powers namespace-style standard
library APIs: sdf::sphere, noise::fbm2, and matrix::lookAt are public
static methods on deliberately empty structs.
Arrow functions and function values
Arrow expressions are the function values in TinyFX. Their parameters are typed and their body is either one expression or a block.
let square: fn(float) -> float = (value: float) => value * value;
let shifted = (value: float) => {
return value + 1.0;
};
print(square(3.0));Named free functions and static methods are not first-class values: call them
with (...) rather than passing their name. A function-value call is positional
and requires exactly its declared parameters.
On CPU paths, arrows close over their surrounding values. In GPU code they have a narrower model: a literal arrow may be passed directly to a GPU-compatible higher-order call, where the compiler lifts and specializes it for that call site. Storing an arrow in a GPU local, passing an arrow variable, or forwarding a function value through another GPU call is not supported. Keep the literal at the GPU call site, and read captures and higher-order GPU code before designing a shader helper around it.
Execution stages
Functions have three annotations:
gpu fn(orgpu) promises that the function is GPU-compatible. It is validated as such and may also be called from CPU code.cpu fn(orcpu) is CPU-only and can never be reached from a shader or compute kernel.- An ordinary
fnhas no declared stage. It may be used from CPU code, and it is checked recursively for GPU compatibility when a GPU site reaches it.
gpu fn luminance(color: float3) -> float {
return dot(color, (0.2126, 0.7152, 0.0722));
}
cpu fn titleForFrame(frame: int) -> string {
return "frame=" + frame.toString();
}GPU compatibility includes the full reachable call chain, parameter and return
types, resource identities, and stage-specific operations. A cpu function,
dynamic collection, string operation, trait call, or incompatible capture makes
a GPU-reachable path invalid. GPU recursion is also rejected because WGSL
cannot represent it. Diagnostics include the call chain that introduced the
restriction.
Resource parameters are supported for reusable GPU helpers, but must resolve to a statically known capture, global-rooted path, or forwarded resource parameter at the GPU call site. The compiler specializes the WGSL helper for that resource vector; dynamic resource selection and returning a resource from a GPU-compatible function are rejected. See buffers and texture ownership.
Targets and output
A GPU-compatible function can be emitted for VM execution and WGSL when its uses need both. The generated TypeScript/WebGPU target is a separately lowered but broadly supported execution path, not a fullscreen-only prototype or VM bytecode translated to JavaScript. Precise generated-target coverage remains operation-specific; use the operation reference and compiler outputs when selecting a deployment path.
Related pages
- Language — program structure, metadata, imports, and control flow.
- Types — aggregates, enums, traits, and CPU/GPU data boundaries.
- GPU programming — shader calls, captures, resources, and compute.
- Runtime — lifecycle calls and host behavior.
- Operation reference — generated signature and target reference.