Type System
Targo's type system is Go-first even when the syntax looks familiar to TypeScript developers.
Use this page when you need precise rules or practical boundaries, not a learning narrative.
Core Rules
numberis not recommended; it maps tofloat64. Prefer explicit Go numeric types.bool,string,byte,rune,complex64,complex128, andbigintare supported.nullandundefinedare part of the language model; both map to Go'snil.- Conditions must be explicitly
bool. Do not rely on JavaScript truthy/falsy behavior. - Mixed-type arithmetic is a compile error — use explicit conversions (
f as int). - Untyped numeric literals adapt to the surrounding typed context, matching Go's untyped constant rules.
Numeric Types
All Go numeric types are available as first-class Targo types.
| Type | Size | Notes |
|---|---|---|
int | platform (32/64-bit) | default integer type |
int8 | 1 byte | −128 … 127 |
int16 | 2 bytes | −32 768 … 32 767 |
int32 | 4 bytes | −2³¹ … 2³¹−1 |
int64 | 8 bytes | −2⁶³ … 2⁶³−1 |
uint | platform (32/64-bit) | unsigned counterpart of int |
uint8 | 1 byte | 0 … 255 |
uint16 | 2 bytes | 0 … 65 535 |
uint32 | 4 bytes | 0 … 2³²−1 |
uint64 | 8 bytes | 0 … 2⁶⁴−1 |
float32 | 4 bytes | IEEE 754 single precision |
float64 | 8 bytes | IEEE 754 double precision |
complex64 | 8 bytes | two float32 components |
complex128 | 16 bytes | two float64 components |
byte | 1 byte | alias for uint8 |
rune | 4 bytes | alias for int32, Unicode code point |
bigint | arbitrary | maps to Go *big.Int |
let count: int = 42;
let port: uint16 = 8080;
let pi: float64 = 3.14159;
let huge: bigint = 123456789012345678901234567890n;Integer literals without a decimal point default to int. Literals with a decimal or exponent default to float64.
Value and Reference Semantics
Targo distinguishes value types from reference types, matching Go's model.
class → pointer by default. A class User variable is *User in Go. You never write * yourself.
let u = new User(); // Go: u := &User{} (type *User)
u.Name = "Alice"; // auto-dereferencedVal<T> for explicit value semantics. When you need a struct value (not a pointer), wrap the type:
let v: Val<User> = zero<User>(); // Go: var v UserPtr<T> for primitive pointers. Primitives are values by default. Use Ptr<T> when you need a pointer to a primitive:
let np: Ptr<int> = ref(42); // Go: np := new(int); *np = 42
np.value = 100; // Go: *np = 100ref() — take address. Converts a value to a reference/pointer:
let np: Ptr<int> = ref(42); // primitive → Ptr<int>
let uRef: User = ref(userVal); // Val<User> → User (*User)deref() — dereference. Converts a reference back to a value copy:
let copy: Val<User> = deref(u); // Go: copy := *uzero<T>() — typed zero value (non-null):
let n = zero<int>(); // 0
let s = zero<string>(); // ""
let u = zero<User>(); // Val<User>, all fields zeroed| Targo | Go | Semantics |
|---|---|---|
User (class) | *User | reference (pointer) |
Val<User> | User | value (struct copy) |
Ptr<int> | *int | pointer to primitive |
ref(x) | &x / new(T) | take address |
deref(p) | *p | dereference |
zero<T>() | var x T | zero value |
Nullability and Nil Safety
T | null marks a type as nullable. Without | null, a variable cannot be nil.
let user: User | null = null; // Go: var user *User = nil
let name: string = ""; // cannot be nullGo mapping: User | null → *User (can be nil), slice<T> | null → []T (can be nil), map<K,V> | null → map[K]V (can be nil), Ptr<int> | null → *int (can be nil).
Safe access operators. ?. (optional chaining) and ?? (nullish coalescing) work as expected:
let name = user?.Name ?? "unknown";unsafeNil<T>() — pass nil where the type signature doesn't allow null. Some Go APIs accept a non-nullable type but treat nil as a valid value (e.g., nil *Config means "use defaults", nil []byte means "no input"). Since Targo's type system won't let you pass null to a non-nullable parameter, unsafeNil<T>() is the escape hatch:
// Go: quick.CheckEqual(f, g, nil) — nil config means default
// Targo signature: CheckEqual(f, g, config: quick.Config) — no | null
const err = quick.CheckEqual(f, g, unsafeNil<quick.Config>());
// Go: hasher.Sum(nil) — nil []byte means don't append
const hash = hasher.Sum(unsafeNil<slice<byte>>());Only use unsafeNil<T>() when you know the Go API accepts nil for that parameter. It is restricted to reference types (slice, map, chan, Ptr, GoInterface, function, class).
Strings
string.lengthfollows Golen()— byte length, not Unicode character count.- Use
runes(s)to get aslice<rune>for character-level iteration.
Type Conversions
Targo uses as for both type conversions and type assertions, and instanceof for safe type checks.
Numeric conversions use as:
let f: float64 = 3.14;
let n = f as int; // Go: int(f) — truncates
let big = n as int64; // Go: int64(n)String ↔ byte/rune slice:
let b = s as slice<byte>; // Go: []byte(s)
let r = s as slice<rune>; // Go: []rune(s)
let s2 = b as string; // Go: string(b)Type assertions from interfaces:
let r: Reader = getReader();
let f = r as File; // Go: r.(File) — panics if wrongType assertions can panic at runtime. Prefer instanceof for safe checks.
instanceof — safe type checking:
if (r instanceof File) {
// r is narrowed to File in this branch
r.Close();
}Consecutive instanceof checks compile to a Go type switch.
Generic Constraints
Targo provides built-in constraint types that map to Go generic constraints.
| Constraint | Includes | Go equivalent |
|---|---|---|
comparable | types supporting == / != | comparable |
Ordered | all numeric + string types (supports < >) | cmp.Ordered |
Signed | int, int8 … int64 and underlying | ~int | ~int8 | … | ~int64 |
Unsigned | uint, uint8 … uint64, uintptr and underlying | ~uint | ~uint8 | … | ~uint64 | ~uintptr |
Integer | Signed | Unsigned | all integer types |
Float | float32, float64 and underlying | ~float32 | ~float64 |
Complex | complex64, complex128 and underlying | ~complex64 | ~complex128 |
Numeric | Integer | Float | Complex | all numeric types |
Underlying<T> | all types whose underlying type is T | ~T |
function abs<T extends Signed>(x: T): T {
if (x < 0) return -x as T;
return x;
}
function maxOf<T extends Ordered>(a: T, b: T): T {
return a > b ? a : b;
}The any Type
any maps to Go's interface{}. Unlike TypeScript's any, it is type-safe — you cannot do anything with an any value without narrowing it first.
Allowed: assignment, passing to any parameters, == / != comparison, as T assertion, instanceof check.
Forbidden: property access, method calls, arithmetic, indexing, iteration, spread, unary ops.
function process(val: any): string {
// val.toString() — ❌ forbidden
// val + 1 — ❌ forbidden
if (val instanceof string) {
return val; // ✅ narrowed to string
}
return val as string; // ✅ assertion (may panic)
}Think of Targo's any as behaving like TypeScript's unknown: narrow before use.
Practical Selection
| Need | Prefer |
|---|---|
| General integer logic | int |
| Explicit width / protocol layout | concrete Go numeric type (int32, uint16, …) |
| Floating-point math | float64 (or float32 when memory matters) |
| Arbitrary-precision integers | bigint |
| Nullable result at a boundary | T | null or tuple return |
| Struct by value | Val<T> |
| Pointer to a primitive | Ptr<T> |
Common Mistakes
- Using
numbereverywhere out of TypeScript habit — preferintorfloat64. - Assuming truthy/falsy behavior in conditions (
if (str)instead ofif (str != "")). - Forgetting that
string.lengthis byte length, not character count. - Mixing numeric types in arithmetic without explicit conversion.
- Using
anyand expecting TypeScript-style property access — narrow first. - Using
unsafeNil<T>()when the API actually acceptsT | null— just passnulldirectly. - Forgetting that
classinstances are already pointers —Ptr<User>is almost never what you want. - Treating
Val<User>andUseras interchangeable — they have different semantics (value copy vs shared reference).