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
classmaps to a Go struct with reference semantics (*Tin Go).constructoris supported — compiles to aNewT()factory function.- Classical inheritance is not supported.
- Prefer composition, interfaces,
Embedding<{ ... }>(), andDefinedType<...>()over trying to recreateextends. - Use
#fieldfor private fields. Do not use TypeScriptprivateorprotected.
Field Visibility and Casing
Go uses initial letter case for visibility. Targo maps this automatically:
| Targo syntax | Go output | Visibility |
|---|---|---|
name: string | Name string | public (default — uppercased) |
#secret: string | secret string | private (package-internal) |
_internal: int | _Internal int | public (_ preserved, next letter uppercased) |
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(...):
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):
class Point { X: float64 = 0; Y: float64 = 0; }
let p = new Point(); // Go: new(Point)Struct literal initialization bypasses the constructor:
let u: User = { Name: "Bob" }; // Go: &User{Name: "Bob"}Constructor constraints:
- Single constructor only (Go has no function overloading)
- No
super()(no inheritance) DefinedTypeclasses cannot have constructors — useasor factory functions instead
Methods and Receivers
Methods default to pointer receivers (matching ~83% of Go stdlib methods):
class Counter {
Value: int = 0;
Increment(): void { // Go: func (c *Counter) Increment()
this.Value++;
}
}For value receivers, use explicit this: Val<T>:
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:
class Config {
Host: string = "localhost";
Port?: int; // Go: Port targo.Optional[int]
}Reading semantics:
config.Port !== undefined→ presence checkconfig.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
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:
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 inheritedDefinedType with Collections
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
asfor conversion:42 as UserId,id.underlying()for reverse - Cannot have a
constructor— useasor factory functions - Nested DefinedType is supported:
class OInt extends DefinedType<MyInt>() {} Underlying<T>constraint matches all types whose underlying type isT
Embedding
Embedding provides Go-style struct embedding. The embedded type's fields and methods are promoted to the outer struct.
Basic Usage
class Animal {
Name: string = "";
}
class Dog extends Embedding<{ Animal: Animal }>() {
Bark(): string {
return `${this.Name} says woof`; // Name promoted from Animal
}
}Multiple Embeddings
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.Namedirectly orthis.Animal.Nameexplicitly - 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:
class User {
/** @json "user_name" */
Name: string = "";
/** @json "email,omitempty" */
Email: string = "";
/** @json "-" */
#password: string = "";
}@tag full syntax for multiple tags (backtick-wrapped):
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
GoInterfacemarker for interfaces that model real Go interfaces - See
interfaces.mdfor full rules on GoInterface, class compatibility, and method shape requirements
Composition Patterns
| Need | Use |
|---|---|
| Reuse data layout | composition (field of another type) |
| Promote fields/methods from another struct | Embedding<{ T: T }>() |
| Distinct nominal type with type safety | DefinedType<T>() |
| Behavioral contract / substitutability | interface (+ 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/protectedinstead of#field - Treating two
DefinedTypeclasses with the same underlying type as interchangeable - Adding a
constructorto aDefinedTypeclass (not allowed) - Forgetting that
Embeddingpromotes fields, which can cause name conflicts - Expecting methods to be inherited through
DefinedType<Struct>()— only fields are inherited - Using
Ptr<User>whenUseris already a pointer (class instances are*Tby default)