If you write modern C++, most of Keal's memory model is familiar: shared_ptr everywhere, weak_ptr for cycles, destructors that run deterministically. What is gone is the rest of the surface — no templates to instantiate by hand, no overload resolution, no headers, no undefined behaviour.
| Concept | C++ | Keal |
|---|---|---|
| Shared object | shared_ptr<T> | the default |
| Weak reference | weak_ptr<T> | weak var f: T? |
| Destructor | ~T() { } | proc deinit() { } |
| Value struct | struct P { double x; }; | record P(val x: Float) |
| Template | template<class T> T f(T) | func f<T>(x: T): T |
| Concept | template<Ord T> | func f<T: Ord>(x: T) |
| Optional | std::optional<T> | T? |
| Lambda | [&](int x){ return x*2; } | { x -> x * 2 } |
| Exception | throw / catch | throw / catch |
| Operator overload | operator+ | trait Add |
| Namespace | namespace a { } | one flat namespace, for now |
Generics are checked once at the declaration, then monomorphised. A generic that type-checks compiles at every instantiation — no pages of errors from inside a header.
Operators come from traits, and a name means one thing in a scope. It costs some expressiveness and buys back the ability to read a call site.
Pass a .cpp on the keal build line: it compiles under c++, the generated C stays C11, and only the link is shared.
deinit runs at the next statement boundary, youngest first — the same order a stack of RAII objects unwinds in.