docs / guide / The Cajeta Guide

Chapters — 10. Allocation
  1. Introduction
  2. 0. Introduction
  3. 1. Installation
  4. 2. Kick the tires
  5. 3. Your first project
  6. 4. Running your application
  7. 5. Debugging
  8. 6. Keywords
  9. 7. Comments
  10. 8. Native types
  11. 9. Type kinds
  12. 10. Allocation
  13. 11. Ownership & borrowing
  14. 12. Control flow
  15. 13. Strings & formatting
  16. 14. Templates & wildcards
  17. 15. Lambdas & captures
  18. 16. Operator overloading
  19. 17. Inheritance
  20. 18. Annotations & synthesis
  21. 19. DI & aspects
  22. 20. Error handling
  23. 21. Reflection
  24. 22. Differentiation
  25. 23. Code coverage
  26. 23. Profiling
  27. 24. Notebooks

10 — Allocation

Every class instance is created with an explicit placement prefix: stack or heap. There is no new. Omitting the prefix at a constructor call is a compile error — placement is always visible at the allocation site.

public class Point {
    public int32 x;
    public int32 y;

    public Point(int32 x, int32 y) {
        this.x = x;
        this.y = y;
    }

    public int32 distSq() {
        return this.x * this.x + this.y * this.y;
    }
}
Point onStack = stack Point(3, 4);    // this frame; dropped at scope exit
Point onHeap  = heap  Point(5, 12);   // heap block; freed by the drop chain
Point named   = stack Point { x: 1, y: 2 };   // aggregate initializer

Run it: AllocationDemo.

One type, either storage

Point is the same type wherever it lives. The storage mode is a property of the value, not the type — the borrow checker tracks lifetime as metadata. A method that takes Point accepts both:

public class Meter {
    public int32 measure(Point p) {
        return p.distSq();
    }

    public void run() {
        Point s = stack Point(3, 4);
        Point h = heap Point(5, 12);
        int32 a = this.measure(s);
        int32 b = this.measure(h);
    }
}

There is no separate pointer or value flavor of a class, and no slicing: class instances always pass and return by reference.

When to pick which

  • stack when the value lives and dies inside the current method or block. No allocation call; the instance drops at the closing }.
  • heap when the value must outlive the frame: it is returned to the caller, stored in a field or collection, or handed to another owner with # (next chapter). The heap block is freed automatically when its owner goes out of scope — there is no delete.

The compiler enforces the boundary. Returning a stack-allocated local from a method is a compile error — the frame it lives in is gone before the caller can look at it:

public Point make() {
    Point p = stack Point(1, 2);
    return p;               // ERROR — stack value cannot escape its frame
}

Heap values never leak either way: if nothing transfers ownership out, the drop chain reclaims the block at the owner’s scope exit. Cleanup on both the normal and the exception path is covered in chapter 11.

A third placement, shared, exists inside GPU kernels for workgroup-shared memory; it is covered with the compute material. Full semantics: MemoryModel and UnifiedClasses.

Next: Ownership & borrowing.

Source: docs/guide/10-allocation.md · 1 min · 281 words