docs / guide / The Cajeta Guide

Chapters — 13. Strings & formatting
  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
  28. 25. Writing XPU Kernels

13 — Strings & formatting

String (in cajeta.lang) is a class, not a primitive: immutable UTF-8 text. Every transformation returns a fresh String; nothing mutates in place. Equality is content-based. Like every collection, a String reports its element count with count() — codepoints here — while size() is the byte length.

String s = "  Hello, Cajeta!  ";
String trimmed #= s.trim();
boolean has = trimmed.contains("Cajeta");
boolean hello = trimmed.startsWith("Hello");
String sub #= trimmed.substring(7, 13);            // "Cajeta"
String swapped #= trimmed.replace("Cajeta", "World");
String loud #= trimmed.toUpperCase();
int64 codepoints = trimmed.count();
int64 bytes = trimmed.size();
boolean eq = trimmed.equals("Hello, Cajeta!");    // byte-for-byte

Run it: StringDemo.

Literals, owned strings, and windows

One type, three storage shapes underneath:

  • A literal is a view of the program’s static data — no allocation, never freed.
  • A string a method builds — replace(), toUpperCase(), a + concatenation, StringBuilder.toString() — owns a heap buffer. Building from your own bytes works too: heap String(#buf, n) takes ownership of the buffer (the # transfers it; the String’s drop frees it).
  • substring() and trim() are zero-copy: the result is a windowed view sharing the source’s root buffer — no byte copy, whatever the length. Small-string-optimized sources are the exception; a window into an inline buffer would dangle, so those materialize a copy.

Transforming methods are declared #String (they transfer ownership, chapter 11), so receive the result with #= — String trimmed #= s.trim(); — and the drop chain reclaims it at scope exit. The # goes on the store, never on the local’s type; a plain = on a #String result is CAJETA_ERROR_OWNED_RESULT_NEEDS_TRANSFER. Windows follow the slice rules from chapter 11: a window that escapes its scope keeps the root buffer alive — the root is promoted to the shared state and freed when the last view drops. You never free a string in any of the three shapes.

substring(begin, end) is byte-indexed and half-open; indices clamp to the valid range. Full method list: String reference.

Concatenation with +

+ concatenates and stringifies its operands — numbers, booleans, and class values (via toString()) all work:

int32 version = 1;
String banner = "Cajeta v" + version + ", pi ~ " + 3.14;

Each + copies the whole accumulator, so building a long string with repeated s = s + chunk in a loop is O(N²). Accumulate in a StringBuilder instead.

StringBuilder

A growable byte buffer that materializes once. Short builds (up to 64 bytes) stay in an inline buffer — a stack StringBuilder() build that small does no heap allocation at all until toString():

StringBuilder sb = stack StringBuilder();
sb.append("Hello, ");
sb.append("world");
int32 soFar = sb.count();          // bytes accumulated
String built #= sb.toString();      // owned #String

Format templates in println

System.stdout.println(fmt, args...) treats {} in the format string as a placeholder; each one consumes the next argument in order. Arguments dispatch through a per-type formatter — integers, floats, String, and class values via toString():

String name = "Cajeta";
int32 version = 1;
float64 pi = 3.14159;
System.stdout.println("hello {}, version {}", name, version);
System.stdout.println("{} + {} = {}, pi ~ {}", 2, 3, 2 + 3, pi);

Run it: FormatStringDemo. Full string design: String spec.

Next: Templates.

Source: docs/guide/13-strings.md · 2 min · 413 words