I hate to admit this, but it took me a long time to do, mostly because I was not experienced when I started writing code, and the code has been rewritten several times over to get it right.
First off is the code in [1]. It's technically under several licenses, but I can give it to you under the public domain because bounds checking in C is important enough that I'll give it away.
The `y_ARRAY_TYPE()` macro generates a struct that is an array with bounds. Use it like this:
typedef y_ARRAY_TYPE(uint32_t) uint32_t_arr;
`uint32_t_arr` is a bounded array of `uint32_t`'s.
You can then pass that array to functions and do bounds checks with the `y_i()` macro (for when the struct value is not a pointer) and the `y_ip()` macro (for when the struct value is a pointer).
To implement RAII, the code to do that is in the same repo, but it's...much more complicated: a full stack allocator. I just add the destructor as a parameter when allocating memory, and when the allocation is freed, the destructor is called when it's non-NULL. Then there are functions to allocate and free, but it is always done in a stack, to emulate the real stack.
By the way, the type of destructor is
void (*y_Destructor)(void*)
and it takes a pointer to the item to be destroyed.
Structured concurrency takes the idea of a thread-local stack and sort of generalizes it across threads. The idea is that each thread has a parent, and that parent is not joined until all of its child threads are joined. All threads form a tree, almost exactly like process trees in Linux.
This means that if you want to have another thread borrow an item, you just have to make sure that thread is a descendant of the thread it is borrowing the item from, and you have a guarantee that the item will never go out of scope before the borrowing thread does.
Sometimes you need to change your code to do that. You can "push" things "down the stack" by passing function pointer callbacks to callees who then create the item to borrow and call the callbacks. Inside the callbacks, the borrowed item will always exist.
There's more to it than that (you can turn a thread tree into a DAG [2], allowing you to turn threads that are not descendants of a thread into descendants), but that's probably good enough to start you off.
[1]: https://git.yzena.com/Yzena/Yc/src/commit/cf4c96b3560d/inclu...
[2]: https://lobste.rs/s/8msejg/notes_on_structured_concurrency_g...