Memory & String Runtime
Memory & String Runtime
Overview
Thagore uses a managed string runtime with manual memory management for raw pointers. There is no garbage collector — memory is managed through explicit allocation/deallocation and the runtime ABI.
String Management
How Strings Work
- String literals are stored as constant globals in the compiled binary
- String concatenation (
+) allocates new memory via the runtime - Substring extraction creates a new string copy
- String comparison uses byte-by-byte comparison
String Operations at Runtime
| Operation | Runtime Function | Allocation |
|---|---|---|
Literal "hello" | Static constant | None (compile-time) |
Concatenation a + b | __thg_str_add | Heap allocation |
| Substring | __thg_str_substr | Heap allocation |
| Length | strlen | None |
Comparison == | Byte comparison | None |
Memory Safety for Strings
The runtime handles string memory automatically for common operations. You don’t need to free strings manually:
func create_greeting(name: String) -> String: # This allocates a new string on the heap return "Hello, " + name + "!" # The intermediate concatenation results are managed by the runtime
func main() -> i32: let msg = create_greeting("World") print(msg) return 0Raw Pointer Operations
Pointer Types
| Type | Description |
|---|---|
ptr | Generic raw pointer (equivalent to void* in C) |
Core Pointer Functions
# Get a null pointerextern func __thg_ptr_null() -> ptr
# Array-style pointer accessextern func __thg_ptr_set(base: ptr, index: i32, value: ptr) -> voidextern func __thg_ptr_get(base: ptr, index: i32) -> ptrStandard Library Wrappers
import "std/core.tg" as core
let null = core.ptr_null() # Null pointercore.ptr_set(array_ptr, 0, value_ptr) # Set elementlet item = core.ptr_get(array_ptr, 0) # Get elementcore.mem_free(raw_ptr) # Free memoryHeap Allocation
Using malloc and free
extern func malloc(size: i32) -> ptrextern func free(p: ptr) -> void
func main() -> i32: # Allocate 1024 bytes let buffer = malloc(1024)
# Use the buffer...
# Free when done free(buffer) return 0Dynamic List Implementation
The std/list.tg module demonstrates heap management with pointer arrays:
# Internal allocation: capacity * 8 bytes (pointer size)let data = malloc(capacity * 8)
# Growth: double capacity when fullif (size >= capacity): let next_capacity = capacity * 2 let next_data = malloc(next_capacity * 8) # Copy existing data _copy_data(next_data, data, size) # Free old allocation free(data)Memory Testing Pattern
To verify your program doesn’t crash from memory issues:
func create_garbage() -> i32: let s1 = "Part 1 " let s2 = "Part 2" let res = s1 + s2 # Heap allocation print(res) return 0
func main() -> i32: let i = 0 while (i < 1000): create_garbage() # Repeated allocation i = i + 1 print("Finished without crashing!") return 0This pattern is used in the test suite (examples/memory_test.tg) to validate that the runtime handles repeated string allocations without crashing.
Null Safety
Check for null pointers using the is_null_ptr function:
import list
func safe_access(lst: List, index: i32) -> String: let item = list.get(lst, index) if (list.is_null_ptr(item) == 1): return "not found" return itemMemory Best Practices
- Close file handles — always call
fs.close(handle)after file operations - Dispose lists — call
list.dispose(lst)when done with a dynamic list - Free raw memory — pair every
mallocwith afree - Minimize string concatenation in loops — use
StringBuilderfor efficient string building - Test with memory_test patterns — stress-test with many iterations to catch leaks