Types and data
TinyFX uses static, nominal types with local inference. An annotation supplies an expected type; otherwise an initializer, return expression, or call context can infer one. The compiler records the resulting type and every coercion once for all backends. For function calls and overload selection, read functions; for exact builtin and method signatures, use the generated operation reference.
Scalars, vectors, and matrices
The scalar types are int (signed 32-bit), uint (unsigned 32-bit), float
(32-bit), and bool. Numeric vector types are int2 through int4, uint2
through uint4, and float2 through float4. Matrix types are square,
column-major float2x2, float3x3, and float4x4 values.
let pixels: int2 = (1920, 1080);
let tint: float4 = (1.0, 0.6, 0.2, 1.0);
let transform = float4x4(
(1.0, 0.0, 0.0, 0.0),
(0.0, 1.0, 0.0, 0.0),
(0.0, 0.0, 1.0, 0.0),
(0.0, 0.0, 0.0, 1.0),
);Vectors support component access and swizzles such as .x, .rgb, .xy, and
.zyx. Matrix construction accepts columns, a source matrix, or the required
number of scalar components. Matrix and vector constructors are positional;
they are not named-argument APIs.
The only implicit numeric conversions are int to float, uint to float,
and int to uint, including element-wise conversions for equal-width
vectors. Use an explicit constructor such as float(value), int(value), or
float3(value) for every other conversion. This is especially important at
resource and enum boundaries.
Strings and CPU collections
string, dynamic arrays (T[]), maps (map<K, V>), Json, function values,
trait objects, meshes, models, fonts, camera input, microphone input, and video
handles are CPU-only values. They cannot become shader values, GPU uniform
captures, or fixed GPU data. Assets and media describes the
opaque host-backed handles.
let names: string[] = ["Ada", "Grace"];
let scores = map<string, int>();
scores.set("Ada", 10);
let saved = json::parse("{\"enabled\":true}");An uncontextualized array literal is dynamic; provide a fixed-array expected type when the value must be GPU-compatible:
let dynamic = [1.0, 2.0, 3.0]; // float[]
let taps: float[4] = [0.1, 0.2, 0.3, 0.4];
let filled = array<float>(4, 0.0); // float[4]Dynamic arrays and maps are mutable reference values. Their complete method sets and error behavior belong in the operation reference, not this narrative page. JSON supports both immutable dynamic JSON values and context-typed conversion; use that reference for conversion constraints and the exact accessor surface.
String operations are catalogued rather than duplicated here. Two details are worth calling out because they are easy to misread:
text.trim()removes whitespace;text.trim(characters)accepts its character set positionally or ascharacters:.text.substr(start, end)uses an exclusive end index, not a character count.
Structs and methods
Struct declarations define nominal aggregate types. Fields default to private;
mark a field pub to read or write it outside the struct's own methods. A
field can have an explicit type, a default expression that supplies its type,
or both. A defaulted field may be omitted from a struct literal.
pub struct Particle {
pub position: float3,
pub velocity: float3 = (0.0, 0.0, 0.0),
mass: float = 1.0,
}
let particle = Particle { position: (1.0, 2.0, 3.0) };
particle.position += particle.velocity;On the CPU, structs are reference values: assigning one struct to another
binding aliases its fields. The standard clone() operation makes a shallow
copy of the outer structure, so nested structs and collections remain shared;
a user-defined clone() method can deliberately replace that behavior. Do not
use CPU aliasing to infer GPU capture behavior.
Methods live in impl blocks. An instance method has self as its first
parameter and is called with .; a method without self is static and is
called with ::. Methods are private unless declared pub.
impl Particle {
pub fn advance(self, dt: float) {
self.position += self.velocity * dt;
}
pub fn stationary(position: float3) -> Particle {
return Particle { position: position };
}
}Static fields are declared with static in a struct. Every static field requires
an initializer, is read-only after initialization, is accessed through the type,
and follows the same member visibility rules. Empty structs with public static
methods are the standard-library namespace pattern, for example noise::fbm2
and matrix::lookAt.
Enums
Enums are nominal, signed-int-backed value types. An enum member has an
implicit increasing discriminant beginning at zero, or an explicit compile-time
int expression. Both names and discriminants must be unique and fit the
signed 32-bit range.
pub enum Quality {
low,
medium = 4,
high,
}
fn exposure(value: Quality) -> float {
if (value == Quality.high) { return 1.0; }
return 0.5;
}Use qualified members such as Quality.high. Equality and inequality only work
with the same enum type. Enums do not implicitly convert to an int or to a
different enum, and arithmetic, ordering, and bitwise operations on them are
rejected. int(value) explicitly extracts a discriminant.
On the CPU, Quality(rawInt) validates a dynamic int before constructing an
enum; statically invalid values are rejected by the compiler. Enums are legal
GPU data and lower to i32 in WGSL while retaining nominal checking in TinyFX
source. Inherent impl Quality { ... } methods are supported. Trait
implementations for enums are not currently supported.
The standard library uses enums for closed renderer/host options, including
Blend, DepthCompare, DepthConvention, TextureFormat, and
PaintComposite; see standard-library options.
Traits
Traits define a CPU-only dynamic-dispatch interface. A struct can implement a trait and then flow to a parameter, field, or local typed as that trait.
trait Area {
fn area(self) -> float;
}
struct Circle { pub radius: float }
impl Area for Circle {
pub fn area(self) -> float {
return 3.14159265 * self.radius * self.radius;
}
}
fn printArea(shape: Area) {
print(shape.area());
}Calls through a trait value dispatch at runtime. Trait objects, dynamic dispatch, and trait implementations are CPU-only; a trait cannot participate in shader or compute code. Only structs currently implement traits—use an inherent enum implementation for enum behavior.
GPU-safe data and resources
The GPU-safe data types are scalars, numeric vectors, matrices, enums, fixed
arrays of GPU-safe elements, and non-recursive structs whose fields are all
GPU-safe. A GPU-visible struct cannot contain string, a dynamic array, a map,
a trait, a function value, or an opaque CPU handle.
buffer<T>, texture2d<T>, and texture3d<T> are resources rather than
ordinary copied data. shader is a CPU-created shader value. Resource
identity, texture element restrictions, capture rules, and writable compute
semantics are covered by GPU programming. A particular GPU use can
be stricter than the basic type rule: uniform captures cannot contain bool,
for example, because WGSL uniform buffers cannot represent it.
Custom mesh attribute channels are currently supplied as arrays of float,
float2, float3, or float4 only. The matching shader attribute and mesh
layout rules are in shader declarations and attributes.
More detail
- Functions — function types, arrow closures, overloads, and stage annotations.
- GPU programming — captures, resource parameters, shader data, and compute restrictions.
- Standard-library reference — embedded modules and the option enums.
- Operation reference — generated signatures, overloads, parameter labels, CPU/GPU stages, and execution-target evidence.
Advanced storage types
workspace<T>, atomics<int|uint>, appendList<T>, counter<T>, and
slot<T> are the structured advanced-compute resource/capability types. Their
element and aliasing rules are documented in
Structured advanced compute. gpuCount is an opaque
producer-backed view inferred from .count; it cannot be named, constructed,
or converted from an integer or atomic buffer.