Skip to content

Structs and Composition

Targo supports class, but its semantics are Go-first. Think "structs with methods and reference semantics", not "full JavaScript class runtime".

Core Rules

  • class maps to a Go struct with reference semantics (*T in Go).
  • constructor is supported — compiles to a NewT() factory function.
  • Classical inheritance is not supported.
  • Prefer composition, interfaces, Embedding<{ ... }>(), and DefinedType<...>() over trying to recreate extends.
  • Use #field for private fields. Do not use TypeScript private or protected.

Field Visibility and Casing

Go uses initial letter case for visibility. Targo maps this automatically:

Targo syntaxGo outputVisibility
name: stringName stringpublic (default — uppercased)
#secret: stringsecret stringprivate (package-internal)
_internal: int_Internal intpublic (_ preserved, next letter uppercased)
typescript
class User {
  name: string = "";       // → Name string (public)
  #password: string = "";  // → password string (private)
}

Access #field only within the class. External access requires getter/setter methods.

Constructors

When a class has a constructor, new T(...) compiles to NewT(...):

typescript
class User {
  Name: string;
  constructor(name: string) {
    this.Name = name;
  }
}

let u = new User("Alice");  // Go: NewUser("Alice")

When a class has no constructor, new T() compiles to new(T) (Go zero-value allocation):

typescript
class Point { X: float64 = 0; Y: float64 = 0; }
let p = new Point();  // Go: new(Point)

Struct literal initialization bypasses the constructor:

typescript
let u: User = { Name: "Bob" };  // Go: &User{Name: "Bob"}

Constructor constraints:

  • Single constructor only (Go has no function overloading)
  • No super() (no inheritance)
  • DefinedType classes cannot have constructors — use as or factory functions instead

Methods and Receivers

Methods default to pointer receivers (matching ~83% of Go stdlib methods):

typescript
class Counter {
  Value: int = 0;

  Increment(): void {       // Go: func (c *Counter) Increment()
    this.Value++;
  }
}

For value receivers, use explicit this: Val<T>:

typescript
class Point {
  X: float64 = 0;
  Y: float64 = 0;

  Distance(this: Val<Point>): float64 {  // Go: func (p Point) Distance() float64
    return Math.sqrt(this.X * this.X + this.Y * this.Y);
  }
}

Method visibility follows the same casing rules as fields — all methods are public in Go output (uppercased).

Optional Fields

Use ? for optional fields. These compile to a presence-aware holder, not a simple *T:

typescript
class Config {
  Host: string = "localhost";
  Port?: int;  // Go: Port targo.Optional[int]
}

Reading semantics:

  • config.Port !== undefined → presence check
  • config.Port ?? 8080 → fallback on absence

This is different from comma-ok patterns ([T, bool]) used by Map.get() and Array.find().

DefinedType

DefinedType creates a distinct Go defined type — one of Targo's most important patterns for type safety.

Primitive DefinedType

typescript
class UserId extends DefinedType<int64>() {}
class Temperature extends DefinedType<float64>() {}

let id: UserId = 42 as UserId;       // Go: UserId(42)
let raw: int64 = id.underlying();    // Go: int64(id)

Two DefinedType classes with the same underlying type are NOT interchangeable.

Struct-Based DefinedType

Inherits fields but not methods from the underlying struct:

typescript
class Point {
  X: int = 0; Y: int = 0;
  Distance(): float64 { return Math.sqrt(this.X * this.X + this.Y * this.Y); }
}

class Location extends DefinedType<Point>() {
  ToString(): string { return `(${this.X}, ${this.Y})`; }
}

let loc = new Location();
loc.X = 10;          // ✅ field inherited
loc.ToString();      // ✅ own method
// loc.Distance();   // ❌ methods not inherited

DefinedType with Collections

typescript
class UserList extends DefinedType<slice<User>>() {}
class Config extends DefinedType<map<string, string>>() {}

DefinedType Rules

  • Creates a nominal Go type, not a TypeScript type alias
  • Use as for conversion: 42 as UserId, id.underlying() for reverse
  • Cannot have a constructor — use as or factory functions
  • Nested DefinedType is supported: class OInt extends DefinedType<MyInt>() {}
  • Underlying<T> constraint matches all types whose underlying type is T

Embedding

Embedding provides Go-style struct embedding. The embedded type's fields and methods are promoted to the outer struct.

Basic Usage

typescript
class Animal {
  Name: string = "";
}

class Dog extends Embedding<{ Animal: Animal }>() {
  Bark(): string {
    return `${this.Name} says woof`;  // Name promoted from Animal
  }
}

Multiple Embeddings

typescript
class ReadWriter extends Embedding<{ Reader: Reader; Writer: Writer }>() {}

Embedding Rules

  • Key names must match type names: { Reader: Reader } ✅, { Foo: Reader }
  • Only structs and interfaces can be embedded (not slice, map, chan, or primitives)
  • Promoted fields: access this.Name directly or this.Animal.Name explicitly
  • Name conflicts: if two embedded types share a member name, it is not promoted — qualify access explicitly
  • Embedding is composition, not inheritance — no polymorphic dispatch

Struct Tags

Use JSDoc comments to add Go struct tags to class fields.

@json shorthand for simple JSON tags:

typescript
class User {
  /** @json "user_name" */
  Name: string = "";

  /** @json "email,omitempty" */
  Email: string = "";

  /** @json "-" */
  #password: string = "";
}

@tag full syntax for multiple tags (backtick-wrapped):

typescript
class Product {
  /** @tag `json:"id" db:"product_id"` */
  Id: string = "";

  /** @tag `json:"name" xml:"Name" db:"product_name"` */
  Name: string = "";
}

Interfaces

  • Interfaces are Go-style behavioral contracts
  • Use GoInterface marker for interfaces that model real Go interfaces
  • See interfaces.md for full rules on GoInterface, class compatibility, and method shape requirements

Composition Patterns

NeedUse
Reuse data layoutcomposition (field of another type)
Promote fields/methods from another structEmbedding<{ T: T }>()
Distinct nominal type with type safetyDefinedType<T>()
Behavioral contract / substitutabilityinterface (+ GoInterface for Go boundaries)

Common Mistakes

  • Assuming constructor support means JavaScript OOP patterns are preferred — think Go-first
  • Reaching for extends-like hierarchy instead of composition
  • Using TypeScript private / protected instead of #field
  • Treating two DefinedType classes with the same underlying type as interchangeable
  • Adding a constructor to a DefinedType class (not allowed)
  • Forgetting that Embedding promotes fields, which can cause name conflicts
  • Expecting methods to be inherited through DefinedType<Struct>() — only fields are inherited
  • Using Ptr<User> when User is already a pointer (class instances are *T by default)