Borrow-checking without type-checking
Key point
How to handle borrow-checking at runtime in a dynamically typed language
Details
In a toy language with dynamic typing, inline values, stack allocation, interior pointers, and single ownership, this implements borrow-checking at runtime without static types.
Following the perspective of Julia and Zig, which layer static type checking on top of dynamic type checking, removing dynamic checks wherever provable and leaving only the parts that can't be proven statically. In Zest, dynamic code and static code are explicitly switched between to preserve areas like REPL, live reloading, and runtime code generation.
boxis a reference that places a value on the heap, and*is dereference.- Default copy preserves value semantics, but since this is unrealistic for large values, owned references disallow default copy.
^is move,!is borrowed reference, and&is shared reference.- A borrowed reference returns the value to its original location when dropped, while a shared reference keeps the original but prevents mutation.
- Closures use explicit capture instead of implicit capture, desugaring captured state into a tuple and a function.
- The safety rules are: box cannot hold a borrowed/shared reference, an additional borrow is blocked while a borrow is active, and re-borrowing is only allowed after a drop.
- The implementation uses stack-based, non-atomic reference counting only in dynamic frames, and raises a runtime error pointing to the exact value when a violation occurs.
In conclusion, this is an experiment in borrow-checking that tries to preserve value semantics and mutable semantics together without static typing. However, it is less expressive than Rust and comes with more constraints.