Skip to content

Structs & Traits ​

Structs and traits are Zenoscript's primary tools for organizing data and behavior. Structs define data structures, while traits define interfaces and contracts that types can implement.

Structs ​

Basic Struct Declaration ​

typescript
// Simple struct
struct User {
  id: string;
  name: string;
  email: string;
  age: number;
}

// Creating struct instances
let user: User = {
  id: "123",
  name: "Alice",
  email: "alice@example.com",
  age: 30
}

// Struct with optional fields
struct Config {
  host: string;
  port?: number;
  ssl?: boolean;
  timeout?: number;
}

let config: Config = {
  host: "localhost"
  // Optional fields can be omitted
}

Generic Structs ​

typescript
// Generic struct
struct Container<T> {
  value: T;
  timestamp: Date;
  metadata?: any;
}

// Usage with different types
let stringContainer: Container<string> = {
  value: "hello",
  timestamp: new Date()
}

let numberContainer: Container<number> = {
  value: 42,
  timestamp: new Date(),
  metadata: { source: "api" }
}

// Multiple type parameters
struct KeyValue<K, V> {
  key: K;
  value: V;
  created: Date;
}

let kvPair: KeyValue<string, number> = {
  key: "count",
  value: 100,
  created: new Date()
}

Nested Structs ​

typescript
struct Address {
  street: string;
  city: string;
  state: string;
  zipCode: string;
  country: string;
}

struct Person {
  name: string;
  age: number;
  address: Address;
  contacts: {
    email?: string;
    phone?: string;
  };
}

let person: Person = {
  name: "Bob",
  age: 25,
  address: {
    street: "123 Main St",
    city: "Springfield",
    state: "IL",
    zipCode: "62701",
    country: "USA"
  },
  contacts: {
    email: "bob@example.com"
  }
}

Struct Inheritance and Composition ​

typescript
// Base struct
struct Entity {
  id: string;
  createdAt: Date;
  updatedAt: Date;
}

// Extending with intersection types
struct User = Entity & {
  name: string;
  email: string;
  role: "admin" | "user" | "guest";
}

// Composition with embedded structs
struct BlogPost {
  title: string;
  content: string;
  author: User;
  tags: string[];
  metadata: Entity;
}

Traits ​

Basic Trait Declaration ​

typescript
// Simple trait
trait Drawable {
  draw(): void;
  isVisible(): boolean;
}

// Trait with default methods
trait Logger {
  log(message: string): void;
  
  // Default implementation
  logError(error: string): void {
    this.log(`ERROR: ${error}`)
  }
  
  logInfo(info: string): void {
    this.log(`INFO: ${info}`)
  }
}

// Trait with properties
trait Identifiable {
  id: string;
  getName(): string;
}

Generic Traits ​

typescript
// Generic trait
trait Serializable<T> {
  serialize(): T;
  deserialize(data: T): this;
}

// Generic trait with constraints
trait Comparable<T extends Comparable<T>> {
  compareTo(other: T): number;
  equals(other: T): boolean;
}

// Generic trait with multiple parameters
trait Converter<From, To> {
  convert(input: From): To;
  canConvert(input: From): boolean;
}

Trait Inheritance ​

typescript
// Base trait
trait Shape {
  area(): number;
  perimeter(): number;
}

// Extending traits
trait ColoredShape extends Shape {
  color: string;
  setColor(color: string): void;
}

// Multiple trait inheritance
trait MovableShape extends Shape {
  x: number;
  y: number;
  move(dx: number, dy: number): void;
}

trait AnimatedShape extends ColoredShape, MovableShape {
  animate(): void;
}

Implementation Blocks ​

Basic Implementation ​

typescript
struct Circle {
  radius: number;
  center: { x: number; y: number };
}

// Implementing methods for a struct
impl Circle {
  // Constructor-like static method
  static create(radius: number, x: number = 0, y: number = 0): Circle {
    return {
      radius,
      center: { x, y }
    }
  }
  
  // Instance methods
  area(): number {
    return Math.PI * this.radius * this.radius
  }
  
  perimeter(): number {
    return 2 * Math.PI * this.radius
  }
  
  move(dx: number, dy: number): void {
    this.center.x += dx
    this.center.y += dy
  }
}

// Usage
let circle = Circle.create(5, 10, 20)
let area = circle.area()
circle.move(5, -5)

Trait Implementation ​

typescript
// Implementing a trait for a struct
impl Drawable for Circle {
  draw(): void {
    console.log(`Drawing circle at (${this.center.x}, ${this.center.y}) with radius ${this.radius}`)
  }
  
  isVisible(): boolean {
    return this.radius > 0
  }
}

impl Serializable<string> for Circle {
  serialize(): string {
    return JSON.stringify({
      type: "circle",
      radius: this.radius,
      center: this.center
    })
  }
  
  deserialize(data: string): Circle {
    let parsed = JSON.parse(data)
    return {
      radius: parsed.radius,
      center: parsed.center
    }
  }
}

// Usage
let circle = Circle.create(5)
circle.draw()
let serialized = circle.serialize()

Generic Implementation ​

typescript
struct List<T> {
  items: T[];
}

impl<T> List<T> {
  static empty<T>(): List<T> {
    return { items: [] }
  }
  
  static from<T>(items: T[]): List<T> {
    return { items: [...items] }
  }
  
  push(item: T): List<T> {
    return { items: [...this.items, item] }
  }
  
  pop(): [T | undefined, List<T>] {
    if (this.items.length === 0) {
      return [undefined, this]
    }
    
    let last = this.items[this.items.length - 1]
    let rest = this.items.slice(0, -1)
    return [last, { items: rest }]
  }
  
  map<U>(fn: (item: T) => U): List<U> {
    return { items: this.items.map(fn) }
  }
  
  filter(predicate: (item: T) => boolean): List<T> {
    return { items: this.items.filter(predicate) }
  }
}

// Generic trait implementation
impl<T> Serializable<T[]> for List<T> {
  serialize(): T[] {
    return [...this.items]
  }
  
  deserialize(data: T[]): List<T> {
    return List.from(data)
  }
}

Advanced Patterns ​

Associated Types ​

typescript
trait Iterator {
  type Item;
  
  next(): Option<this.Item>;
  hasNext(): boolean;
}

struct NumberIterator {
  current: number;
  end: number;
}

impl Iterator for NumberIterator {
  type Item = number;
  
  next(): Option<number> {
    if (this.current <= this.end) {
      let value = this.current
      this.current++
      return { type: :some, value }
    }
    return { type: :none }
  }
  
  hasNext(): boolean {
    return this.current <= this.end
  }
}

Trait Objects ​

typescript
// Using traits as types
let shapes: Drawable[] = [
  Circle.create(5),
  Rectangle.create(10, 20),
  Triangle.create(3, 4, 5)
]

// All shapes must implement Drawable
shapes.forEach(shape => shape.draw())

// Function accepting trait objects
let drawAll = (drawables: Drawable[]) => {
  drawables
    .filter(d => d.isVisible())
    .forEach(d => d.draw())
}

Conditional Implementation ​

typescript
// Implement trait only for certain types
impl<T> Comparable<List<T>> for List<T> where T: Comparable<T> {
  compareTo(other: List<T>): number {
    let minLength = Math.min(this.items.length, other.items.length)
    
    for (let i = 0; i < minLength; i++) {
      let cmp = this.items[i].compareTo(other.items[i])
      if (cmp !== 0) return cmp
    }
    
    return this.items.length - other.items.length
  }
  
  equals(other: List<T>): boolean {
    return this.compareTo(other) === 0
  }
}

Design Patterns ​

Builder Pattern ​

typescript
struct HttpRequest {
  url: string;
  method: "GET" | "POST" | "PUT" | "DELETE";
  headers: Record<string, string>;
  body?: string;
}

impl HttpRequest {
  static builder(): HttpRequestBuilder {
    return new HttpRequestBuilder()
  }
}

struct HttpRequestBuilder {
  private url?: string;
  private method: "GET" | "POST" | "PUT" | "DELETE" = "GET";
  private headers: Record<string, string> = {};
  private body?: string;
}

impl HttpRequestBuilder {
  setUrl(url: string): this {
    this.url = url
    return this
  }
  
  setMethod(method: "GET" | "POST" | "PUT" | "DELETE"): this {
    this.method = method
    return this
  }
  
  addHeader(key: string, value: string): this {
    this.headers[key] = value
    return this
  }
  
  setBody(body: string): this {
    this.body = body
    return this
  }
  
  build(): HttpRequest {
    if (!this.url) {
      throw new Error("URL is required")
    }
    
    return {
      url: this.url,
      method: this.method,
      headers: { ...this.headers },
      body: this.body
    }
  }
}

// Usage
let request = HttpRequest.builder()
  .setUrl("https://api.example.com/users")
  .setMethod("POST")
  .addHeader("Content-Type", "application/json")
  .setBody(JSON.stringify({ name: "Alice" }))
  .build()

Visitor Pattern ​

typescript
trait Visitor<T> {
  visitNumber(value: number): T;
  visitString(value: string): T;
  visitArray(value: any[]): T;
  visitObject(value: object): T;
}

trait Visitable {
  accept<T>(visitor: Visitor<T>): T;
}

struct JsonValue {
  value: any;
}

impl Visitable for JsonValue {
  accept<T>(visitor: Visitor<T>): T {
    return match this.value {
      v when typeof v === "number" => visitor.visitNumber(v)
      v when typeof v === "string" => visitor.visitString(v)
      v when Array.isArray(v) => visitor.visitArray(v)
      v when typeof v === "object" => visitor.visitObject(v)
      _ => throw new Error("Unknown type")
    }
  }
}

// Concrete visitor
struct JsonPrinter {
  private indent: number = 0;
}

impl Visitor<string> for JsonPrinter {
  visitNumber(value: number): string {
    return value.toString()
  }
  
  visitString(value: string): string {
    return `"${value}"`
  }
  
  visitArray(value: any[]): string {
    let items = value.map(item => 
      JsonValue({ value: item }).accept(this)
    )
    return `[${items.join(", ")}]`
  }
  
  visitObject(value: object): string {
    let entries = Object.entries(value).map(([k, v]) =>
      `"${k}": ${JsonValue({ value: v }).accept(this)}`
    )
    return `{${entries.join(", ")}}`
  }
}

Best Practices ​

1. Prefer Composition over Inheritance ​

typescript
// Good - composition
struct User {
  profile: UserProfile;
  permissions: Permissions;
  settings: UserSettings;
}

// Avoid - deep inheritance
struct AdminUser extends SuperUser extends User {
  // Complex inheritance hierarchy
}

2. Keep Traits Focused ​

typescript
// Good - single responsibility
trait Readable {
  read(): string;
}

trait Writable {
  write(data: string): void;
}

// Can combine when needed
trait ReadWritable extends Readable, Writable {}

// Avoid - too many responsibilities
trait FileOperations {
  read(): string;
  write(data: string): void;
  delete(): void;
  copy(dest: string): void;
  move(dest: string): void;
  compress(): void;
  encrypt(): void;
}

3. Use Default Implementations ​

typescript
// Good - provide sensible defaults
trait Cache<K, V> {
  get(key: K): Option<V>;
  set(key: K, value: V): void;
  
  // Default implementations
  getOrDefault(key: K, defaultValue: V): V {
    return match this.get(key) {
      { type: :some, value } => value
      { type: :none } => defaultValue
    }
  }
  
  has(key: K): boolean {
    return match this.get(key) {
      { type: :some } => true
      { type: :none } => false
    }
  }
}

4. Design for Immutability ​

typescript
// Good - immutable operations
struct Point {
  x: number;
  y: number;
}

impl Point {
  move(dx: number, dy: number): Point {
    return {
      x: this.x + dx,
      y: this.y + dy
    }
  }
  
  scale(factor: number): Point {
    return {
      x: this.x * factor,
      y: this.y * factor
    }
  }
}

// Avoid - mutating operations
impl Point {
  moveInPlace(dx: number, dy: number): void {
    this.x += dx  // Mutation
    this.y += dy  // Mutation
  }
}

Structs and traits in Zenoscript provide a powerful foundation for organizing code in a functional programming style while maintaining the flexibility and expressiveness needed for complex applications.

Released under the MIT License.