Skip to content

Instantly share code, notes, and snippets.

@PeyTy
Last active February 2, 2023 22:52
Show Gist options
  • Save PeyTy/7983dd85026cc061980a6a966bc1afc2 to your computer and use it in GitHub Desktop.
Save PeyTy/7983dd85026cc061980a6a966bc1afc2 to your computer and use it in GitHub Desktop.

Hi! This is my idea about the feature I want to add into my own language Hexa* and especially Zig** (because it fits Zig very well!).

* not promoting Hexa here, really want Zig to fight Rust "monopoly" on safe memory management

** very unlikely that I will implement it myself, hopefully someone will be yakbaited

I was evaluating usage of Zig for my projects and never liked the absence of any assisted memory managemnt (defer is not really a solution to complex memory issues but it doesn't matter on the subject) (and Rust is too annoying to deal with).

Okay that's my short story. While coding in C, I was always amased how it lacks such a basic things like a "pointer to a single element" (AFAIK C has only "indexable" pointers and people tend to use only them). But while doing osdev, out of a sudden, realized that it also laks... stack pointers! Which would be very useful for any low and hi-level programming in general. I mean, with stack allocation you don't have to deal with memory management at all, things generally "just work" and it is very convenient to use (to pass parameters to function as structures with TypeName { a, b, ... } etc). Most of the time we don't need non-stack pointers (in Zig). This is very easy to validate with a most common Zig pattern: allocate, use and just defer free it, all in a single function scope.

So here is my idea.

Note that I'm not into vague static analisis, thus ideas like autimatic inference seems to be... well at best slowing compilation times and confusing at worst.

Introduce a stack pointers in addition to normal pointers, for example *stack T and operator of taking a pointer &stack value (actual syntax is up to your imagination).

Add a simple rule: you can assign normal pointer to a stack pointer, but not vise versa. The idea of not allowing stack pointers to be assigned to normal pointers makes sense, as it prevents stack objects from being freed manually and ensures that they will be automatically freed when they go out of scope. Yeah let's assume for now that this is a whole feature (some improvements would be described later).

So, say we have an object that we allocated manually and we definitely don't want anyone to deallocate it:

(I will use C-like syntax here because I'm more familiar with it)

void free(void* ptr); // Note this is a normal pointer

void demo(void* stack ptr) {
   // free(ptr); Error! Impossible to call this way!
   demo(ptr); // Can be passed to other functions that take *stack argument
}

{
  void* ptr = malloc();
  void* stack ptr_on_stack = ptr; // Normal pointers convertible to stack ones for convenience,
  // and because they are obviusly logically compatible 
  defer free(ptr); // Called on exit, pretty common pattern for Zig 
  demo(ptr); // Ok
  demo(ptr_on_stack); // Ok
  
  struct Demo example = {.value = 10}; // Allocate on stack
  demo(&example); // Safe to call!
  // We are sure nobody would call `free` on it or save it into dictionary/linked list/etc
  // & here is just normal pointer but it is compatible to call this way
  demo(&stack example); // Explicit stack pointer
  
  struct Demo *stack another = &example; // Safe to take multiple on-stack references
}

Well to me this design looks pretty natural and sadly no C nor Zig supports such a simple feature.

Level 2

Okay what about storing of the pointer somewhere? Well seem easy to support too!

We should assume that any structure taking a stack pointer should be possible to be referred to only with a stack pointers.

Secondly, it is not allowed to store stack pointers to fields of structures, whose (structures) are declared before the one we are pointing too. Sorry for my English.

This restriction would prevent stack pointers from referencing objects that have already gone out of scope, which would further enhance the safety of the language's memory management.

Note that this concept is very easy to implement in the compiler, because the only thing it checks is position in the code (aka something like line<=pointee_line && column<=pointee_column). A simple and efficient approach that could be easily implemented in the compiler.

struct Demo { // Inferred to allow only stack-only pointers to self
  struct Another *stack ptr;
};

{
  /* line 1 */ auto another = (Another) {...}; // Allocated on stack
  
  /* line 2 */ auto demo = (Demo) { ptr = another }; // Allocated on stack, cannot be allocated on the heap
  
  /* line 3 */ auto another_after_demo = (Another) {...}; // Allocated on stack
  
  demo.ptr = another_after_demo; // Error! another_after_demo is defined on line 3, while demo is on line 2
  // i.e. demo outlives another_after_demo
  
  // You may imagine more situations by yourself
}

I have more ideas but they a more Rust-like and not C-like and not feasible for Zig aestetics, so that's it! Likely would add an additional layer of safety and control over memory management in the language.

Probably forghot something (this idea came to me about half a year ago or so) and will update this gist if required.

Please share what you think and share to other people in the Zig community! Thanks for hangin' there! :P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment