Skip to content

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

  • number is not recommended; it maps to float64. Prefer explicit Go numeric types.
  • bool, string, byte, rune, complex64, complex128, and bigint are supported.
  • null and undefined are part of the language model; both map to Go's nil.
  • 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.

TypeSizeNotes
intplatform (32/64-bit)default integer type
int81 byte−128 … 127
int162 bytes−32 768 … 32 767
int324 bytes−2³¹ … 2³¹−1
int648 bytes−2⁶³ … 2⁶³−1
uintplatform (32/64-bit)unsigned counterpart of int
uint81 byte0 … 255
uint162 bytes0 … 65 535
uint324 bytes0 … 2³²−1
uint648 bytes0 … 2⁶⁴−1
float324 bytesIEEE 754 single precision
float648 bytesIEEE 754 double precision
complex648 bytestwo float32 components
complex12816 bytestwo float64 components
byte1 bytealias for uint8
rune4 bytesalias for int32, Unicode code point
bigintarbitrarymaps to Go *big.Int
typescript
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.

typescript
let u = new User();   // Go: u := &User{}  (type *User)
u.Name = "Alice";     // auto-dereferenced

Val<T> for explicit value semantics. When you need a struct value (not a pointer), wrap the type:

typescript
let v: Val<User> = zero<User>();  // Go: var v User

Ptr<T> for primitive pointers. Primitives are values by default. Use Ptr<T> when you need a pointer to a primitive:

typescript
let np: Ptr<int> = ref(42);  // Go: np := new(int); *np = 42
np.value = 100;               // Go: *np = 100

ref() — take address. Converts a value to a reference/pointer:

typescript
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:

typescript
let copy: Val<User> = deref(u);  // Go: copy := *u

zero<T>() — typed zero value (non-null):

typescript
let n = zero<int>();       // 0
let s = zero<string>();    // ""
let u = zero<User>();      // Val<User>, all fields zeroed
TargoGoSemantics
User (class)*Userreference (pointer)
Val<User>Uservalue (struct copy)
Ptr<int>*intpointer to primitive
ref(x)&x / new(T)take address
deref(p)*pdereference
zero<T>()var x Tzero value

Nullability and Nil Safety

T | null marks a type as nullable. Without | null, a variable cannot be nil.

typescript
let user: User | null = null;   // Go: var user *User = nil
let name: string = "";          // cannot be null

Go mapping: User | null*User (can be nil), slice<T> | null[]T (can be nil), map<K,V> | nullmap[K]V (can be nil), Ptr<int> | null*int (can be nil).

Safe access operators. ?. (optional chaining) and ?? (nullish coalescing) work as expected:

typescript
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:

typescript
// 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.length follows Go len() — byte length, not Unicode character count.
  • Use runes(s) to get a slice<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:

typescript
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:

typescript
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:

typescript
let r: Reader = getReader();
let f = r as File;           // Go: r.(File) — panics if wrong

Type assertions can panic at runtime. Prefer instanceof for safe checks.

instanceof — safe type checking:

typescript
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.

ConstraintIncludesGo equivalent
comparabletypes supporting == / !=comparable
Orderedall numeric + string types (supports < >)cmp.Ordered
Signedint, int8int64 and underlying~int | ~int8 | … | ~int64
Unsigneduint, uint8uint64, uintptr and underlying~uint | ~uint8 | … | ~uint64 | ~uintptr
IntegerSigned | Unsignedall integer types
Floatfloat32, float64 and underlying~float32 | ~float64
Complexcomplex64, complex128 and underlying~complex64 | ~complex128
NumericInteger | Float | Complexall numeric types
Underlying<T>all types whose underlying type is T~T
typescript
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.

typescript
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

NeedPrefer
General integer logicint
Explicit width / protocol layoutconcrete Go numeric type (int32, uint16, …)
Floating-point mathfloat64 (or float32 when memory matters)
Arbitrary-precision integersbigint
Nullable result at a boundaryT | null or tuple return
Struct by valueVal<T>
Pointer to a primitivePtr<T>

Common Mistakes

  • Using number everywhere out of TypeScript habit — prefer int or float64.
  • Assuming truthy/falsy behavior in conditions (if (str) instead of if (str != "")).
  • Forgetting that string.length is byte length, not character count.
  • Mixing numeric types in arithmetic without explicit conversion.
  • Using any and expecting TypeScript-style property access — narrow first.
  • Using unsafeNil<T>() when the API actually accepts T | null — just pass null directly.
  • Forgetting that class instances are already pointers — Ptr<User> is almost never what you want.
  • Treating Val<User> and User as interchangeable — they have different semantics (value copy vs shared reference).