www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - A programming language idea: no static code, explicit caller context

reply Marconi <soldate gmail.com> writes:
Hi everyone,

I’ve been thinking about designing a new programming language 
called Kite — something lightweight, but designed with the 
ambition to “fly high.”

The core idea is simple, but strict:

There is no static code. Everything is object-oriented.

No static methods, no global functions, no hidden global state.

🔹 Core model

Every method call has two explicit references:

this → the object whose method is being executed
caller → the object that initiated the call

This means execution always carries both identity and 
responsibility.

Nothing happens without an owner.

🔹 Why introduce caller?

Most languages implicitly lose track of who requested an 
operation.

By introducing caller, we make this explicit:

The callee knows who is asking
The caller retains control over resources and error handling

🔹 What this enables

1. Memory allocation controlled by the caller

Instead of allocating memory implicitly, the callee can request 
it:

buffer = caller.alloc(size)

This prevents hidden allocations and makes ownership explicit.

2. Explicit error propagation

Instead of throwing exceptions globally:

caller.error("Something went wrong")

Errors always return to the origin of the operation.

3. No hidden global state

With no static constructs, everything flows through objects and 
their relationships.

4. Strong ownership model

The programmer always knows:

who called the function
who owns allocated memory
where errors go

🔹 Philosophy

Kite is built around a few core principles:

The programmer owns the flow.
No hidden control. No hidden allocation. No hidden errors.
Everything has a context. Everything has a caller.

🔹 Base Object

A root Object class could provide minimal operations like:

printing
memory allocation (via caller or delegated components)
error reporting

But ideally avoiding turning it into a “god object”, possibly by 
delegating to:

allocator
error handler
execution context

I’d love to hear your thoughts:

Does this model provide real advantages over existing languages?
Are there languages that already explore something similar?
What potential pitfalls do you see?

Thanks!
Apr 29
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
What you described is actors.
Apr 29
parent reply Marconi <soldate gmail.com> writes:
On Wednesday, 29 April 2026 at 17:56:56 UTC, Richard (Rikki) 
Andrew Cattermole wrote:
 What you described is actors.
Kite and the Actor Model are similar: No hidden global state Clear ownership of behavior Encapsulation of logic inside entities (objects/actors) The key difference: The Actor model is primarily about concurrency and isolation. Kite is about control flow, ownership, and responsibility, even in single-threaded code. Actor model: You send a message to another actor You don’t know who called you You typically don’t return values directly Everything is asynchronous (or mailbox-based) Kite: You call a method and know exactly who called it (caller) You can return values normally You explicitly propagate: memory allocation errors execution context Actors remove the concept of a caller. Kite makes the caller central. Another way to see it Actor model → “No one owns the flow, messages drive everything.” Kite → “The caller owns the flow.” These are almost opposite design philosophies. A more accurate positioning of Kite would be: “An object-oriented language with explicit caller context and no hidden control flow.” Or: “Like OOP without static — where the caller is always in control.”
Apr 29
parent Bienlein <ffm2002 web.de> writes:
On Wednesday, 29 April 2026 at 18:16:11 UTC, Marconi wrote:

 Kite:
 You call a method and know exactly who called it (caller)
 You can return values normally
 You explicitly propagate:
 memory allocation
 errors
 execution context
Okay, and how do processes exchange data? Actors are a problem, because they are asynchronous. They have a mailbox which processes messages in the order of arrival. But it remains asynchronous programming to some extend with all its problems. Actors work well when exchanging messages between remote instances (see https://akka.io), because of the idea of supervision. If two actors that communicate with each other realize that somethin when wrong, some supervision actors resets them both and restards the operation. But back to Kite. You might want to have a look on concurrency in Go based on blocking takes on channels. There are various videos on Youtube that explain this. This is called "communicating sequential processes" and was invented by Tony Hoare. I think this is pretty much as good as it gets.
May 12
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Wed, Apr 29, 2026 at 05:50:08PM +0000, Marconi via Digitalmars-d wrote:
[...]
 The core idea is simple, but strict:
 
 There is no static code. Everything is object-oriented.
Why object-oriented? There are many programming problems that are better solved with other paradigms.
 No static methods, no global functions, no hidden global state.
Sounds like it will be far removed from the machine. How exactly do you plan to map the high-level code to machine code?
 🔹 Core model
 
 Every method call has two explicit references:
 
 this → the object whose method is being executed
 caller → the object that initiated the call
[...]
 🔹 Why introduce caller?
 
 Most languages implicitly lose track of who requested an operation.
 
 By introducing caller, we make this explicit:
 
 The callee knows who is asking
 The caller retains control over resources and error handling
Interesting concept. Curious to find out where you go with this, it looks like there might be some interesting consequences.
 🔹 What this enables
 
 1. Memory allocation controlled by the caller
 
 Instead of allocating memory implicitly, the callee can request it:
 
 buffer = caller.alloc(size)
 
 This prevents hidden allocations and makes ownership explicit.
So every caller must implement an allocator? And since I can't imagine every object implementing its own allocation scheme, I'd imagine the result would be that it would shunt calls to .alloc to *its* caller, etc., until it ends up in main() or whatever the top-level caller is?
 2. Explicit error propagation
 
 Instead of throwing exceptions globally:
 
 caller.error("Something went wrong")
 
 Errors always return to the origin of the operation.
I like this idea. Let the caller decide what to do when an error condition is encountered. But how do you handle the case when the caller doesn't have the context to know what to do? Manually bubble the error condition up the call chain? The further removed from the source of the error, the less likely the code will be able to recognize the error (even though it would probably have more context to know what to do with it). So either the top-level caller will have to specialize on every single possible error down its entire call-chain (an impractical proposal), or all errors eventually will turn into a generic failure condition because once you go past the first level of caller, nobody knows what to do with it anymore except treat it as an opaque generic failure. [...]
 🔹 Base Object
 
 A root Object class could provide minimal operations like:
 
 printing
 memory allocation (via caller or delegated components)
 error reporting
[...] Again, why objects? Experience shows that OO isn't the best model for many programming problems. It works well when the problem space maps well to the OO model, but when it maps poorly, you start to see code smells and antipatterns like singleton classes (basically global functions and state masquerading as members), needless indirection (boxing PODs and the resulting mess of incompatible types for the same values), downcasting, etc.. A simple example is standard math functions. These are global, stateless functions that don't allocate or raise error conditions. How would you represent them? Where would you put them, since you prohibit global functions? I guess in a singleton class? How would you prevent the user from creating multiple instances of this class? What would it mean for there to be multiple instances of a Math class? (I consider singleton classes an antipattern.) Also, how would you interact with the outside world? If you need to call an external library, for example, how would you represent the global state internal to that library? What about I/O? How would you represent global state change induced by an I/O operation, e.g., writing data to a file that alters OS behaviour? Wouldn't that be "hidden global state"? Unless you represent it explicitly somehow -- but how? As another singleton class? (Which again is an antipattern, because you've just reintroduced global state after so meticulously getting rid of it.) T -- Talk is cheap, because the supply is always greater than the demand.
Apr 29
parent reply Marconi <soldate gmail.com> writes:
On Wednesday, 29 April 2026 at 18:47:41 UTC, H. S. Teoh wrote:
 On Wed, Apr 29, 2026 at 05:50:08PM +0000, Marconi via T
Thanks, this is really helpful feedback — it made me realize I need to refine the idea quite a bit. On “why object-oriented?” I think “everything is object-oriented” was a poor way to express what I actually want. What I really need is: any code that can cause effects must have an explicit owner. So the role of OO here is not philosophical (“everything is an object”), but structural: effectful code must always have a responsible object. That’s why I need something like a base Object: to guarantee that every caller has a minimal set of capabilities (like allocation and error reporting). However, I don’t think this should forbid pure functions. Something like: sqrt(x) max(a, b) does not allocate, does not perform I/O, and does not need authority. So a better rule would be: pure code can be free; effectful code requires a caller. On allocation You’re right to question whether every object would need its own allocator. That’s not the intention. The idea is that every object has access to allocation capability, but not necessarily its own implementation. In practice, it would likely delegate: buffer = caller.alloc(size) internally becoming something like: caller.allocator.alloc(size) with the allocator coming from a root object or environment. The key point is: the callee cannot silently decide allocation policy. It must request it from the caller. On error handling I agree with your concern — the initial caller.error("...") example was too naive. I don’t expect the top-level caller to handle every possible low-level error. That would be impractical. A better model would be: callees detect and classify errors; callers decide the policy. So instead of just passing strings: return caller.error(FileNotFound(path)) Each layer can: handle the error locally (if it has enough context), transform it into a higher-level/domain error, propagate it unchanged. For example: on FileNotFound: useDefault() else: return caller.error(ConfigLoadFailed(err)) So errors don’t blindly bubble up to the top — they are progressively interpreted or transformed. The intent is not that the caller knows everything, but that: no callee unilaterally decides what an error means. A better way to phrase it might be: Callees know what failed. Callers decide what it means. On global state, I/O, and external systems I agree that replacing globals with singletons would defeat the purpose. A better direction is capability-based design. For example: caller.fs.open(...) caller.console.write(...) caller.memory.alloc(...) These are not globals — they are capabilities exposed by the caller/root environment. So instead of hidden global state, authority must be explicitly passed. Refining the idea Based on your feedback, I think the core idea is better expressed as: pure functions are allowed and free effectful operations require explicit authority caller represents authority/policy, not just context libraries can request resources but should not silently decide policies external effects are accessed through explicit capabilities, not globals So perhaps the real principle is: Pure code is free. Effectful code requires authority. Thanks again — your questions helped clarify what the idea actually is (and what it isn’t).
Apr 29
next sibling parent reply Marconi <soldate gmail.com> writes:
Thinking more about it, I believe `caller` may be a much better 
fit for resource authority than for error handling.

For allocation, filesystem access, logging, I/O, etc., the model 
feels natural: the callee should not silently decide policies, so 
it asks the caller for authority.

But for errors, the caller is not always the right entity to 
understand what happened. The immediate caller may have less 
context than the callee, and the top-level caller cannot 
reasonably know every possible low-level failure.

So I’m reconsidering the idea of `caller.error(...)`.

A better direction may be to keep errors as a language-level flow 
mechanism, closer to V’s `or` style:

     config = parse(input) or {
         return ConfigLoadFailed(err)
     }

That way, the callee still classifies what failed, each layer can 
translate the error into its own domain, and the caller decides 
policy only when it actually has enough context.

So perhaps:

     caller = authority for resources/effects
     error flow = handled by the language

This seems cleaner than forcing all error handling through 
`caller`.
Apr 29
parent Kapendev <alexandroskapretsos gmail.com> writes:
On Wednesday, 29 April 2026 at 21:23:15 UTC, Marconi wrote:
 For allocation, filesystem access, logging, I/O, etc., the 
 model feels natural: the callee should not silently decide 
 policies, so it asks the caller for authority.
OPINION ALERT! I think callers should not have full power over a callee because you don't know as a caller how the callee works. Less generic code might have more lines of code, but at least it's easier to reason about as a user. Anyway, ignore me. I am ranting about current coding trends (global context systems, allocator APIs, ...). Return to monkeyyy and write specific systems for specific problems.
Apr 29
prev sibling parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Wed, Apr 29, 2026 at 07:34:04PM +0000, Marconi via Digitalmars-d wrote:
[...]
 Based on your feedback, I think the core idea is better expressed as:
[...]
 caller represents authority/policy, not just context
I like this idea, at least conceptually. Not sure about the implementation, but this makes sense: callee often does not have sufficient context to know what to do with some resources that it requires, e.g. memory allocation. A linked-list container implementation for example needs to allocate / free memory, but should it use malloc/free, ARC, GC, per-frame allocation, or some other scheme? It can't know that. And linked-list algorithms are technically independent of allocation scheme. So it should be written agnostically of allocation scheme, and let the upper-level code handle the decision. OTOH, this can't be taken to extremes. If an algorithm needs, say, a quick scratch buffer for a couple of variables, like 32 or 64 bytes, it would be much faster to use a static buffer instead of paying for the overhead of calling (via indirection, no less!) an unknown caller-decided allocator. Static binding eliminates a ton of boilerplate that's not even needed in this case, and cuts out L1 cache misses so it can get the job done pronto, instead of dancing the indirection / abstraction dance and taking more time to allocate resources than it actually spends on making progress with computing its result. At a certain point, indirection / abstraction stops making sense, and the programmer should have the ability and freedom to bypass what isn't actually needed.
 libraries can request resources but should not silently decide
 policies
How would you implement a library that provides allocation policies?
 external effects are accessed through explicit capabilities, not
 globals
In principle I agree that impure code should be minimized... But there comes a point when you just have to face the fact that the world is impure. You can abstract the OS all you want, but at some point, a global OS setting is a global OS setting; changing it will have far-reaching consequences across unrelated code whether they expect / like it or not. You can dress an impure OS call in whatever pure clothes you want, but at the end of the day, a global setting in the OS is still a global setting, it isn't gonna go away just because you decided to purge your language of impure semantics. If anything, this impedance mismatch will come back to bite you later, when you discover that your language cannot express an impure effect the OS actually implements, so your code is unable to work with it (because it's not expressible).
 So perhaps the real principle is:
 
 Pure code is free. Effectful code requires authority.
[...] In spite of any flaws, it's interesting to see where you take this idea. I think there's something to it. In past projects I've written things like this before: ```d struct GuiConnection { void onEvent(Event ev, void delegate(GuiApi api) dg) { ... } } struct MyObject { this(GuiConnection conn) { sched.onEvent(Event.startup, (api) { api.openWindow(...); api.createWidget(...); }); sched.onEvent(Event.input, (api) { api.updateWidget(...); }); sched.onEvent(Event.close, (api) { api.deleteWidget(...); api.closeWindow(...); }); } } ``` Basically, GuiApi serves as a callback object for MyObject to access GuiConnection methods without requiring a circular dependency on GuiConnection, and without actually exposing said methods to code that shouldn't have access to it (the GuiApi object is only passed to code that actually needs it; you cannot get an instance or use its methods outside of that). This is basically D-speak for what you call "caller authority". Calling the `api.XXX` methods is essentially calling back the caller for various functions ("authority") the caller provides. Of course, in D you have to spell it out, so it's a bit more verbose than one would like. It would be nice to have some syntactic sugar to factor away the many references to `api. ...`, but the essence of it is there. The syntactic sugar becomes more important when you have multiple APIs that you want to expose to the callee. At some point it becomes too much boilerplate for every callee to take a GuiApi object, an Allocator object, a Filesystem object, a NetworkSocket object, a SystemTimer object, and a BusinessLogicDatabase object. Nobody is going to write code this way if you have to spell out these APIs every time you call a function. So language support becomes a big factor to enable this idiom. // As for why I'd even want to write code like this to begin with, there are several motivations: - Minimalistic code: code that doesn't do more than what it needs to, and doesn't use more resources than it needs to, to do its job. - Avoiding Schlemiel code: where you write a linked-list implementation for ints, and then another linked-list implementation for floats, and then another linked-list implementation for structs, all different from each other. Or where you write your app once for Windows, and then rewrite it for Linux, and then rewrite it again for Android, and then rewrite it yet again for Web, etc.. I don't want to have to rewrite stuff. I want the logic not to depend on the specifics of the environment / platform / OS it's running on, so that I can easily port it to whatever else there is out there. I want to rewrite only the platform-dependent parts of the app each time I port it, not rewrite it from scratch every time. Only the platform-dependent parts of the app should need rewriting each time I port it; I shouldn't have to comb through the entire codebase and rewrite it in 100 different places. This applies not just to porting or algorithms, but to anything environmental. Like malloc/free vs. ARC vs. GC, or local filesystem vs. network filesystem, or screen output vs. internal buffer for unittesting, etc.. The code shouldn't care what allocation scheme is in use, or what it's outputting to. As long as there's an API for it to get the memory it wants, or for it to write output to, that's good enough, it can do its job, it doesn't have to (and shouldn't) know the rest. - Reusability: if I write an algorithm for defeating the best AI chess player, it shouldn't be tied to the windowing system it was written on; I should be able to call the same function whether it's to play chess with the user sitting at the keyboard, or to interact with a network player, or to discover an unusual move combo in an internal game state that's never displayed (e.g., as a recursive part of itself exploring the state space to find the optimal move). Or it is being called from a unittest to verify that core logic hasn't broken with the latest feature addition. TL;DR: I don't want to have to rewrite code, ever. I want to write it once, debug it once, and use it forever -- for anything, including stuff I haven't thought of yet. On systems that haven't even been invented yet. T -- An army of toddlers came marching out of the daycare as their caretakers led them to their waiting parents. They're the infantry.
Apr 29
parent reply Marconi <soldate gmail.com> writes:
On Wednesday, 29 April 2026 at 22:51:59 UTC, H. S. Teoh wrote:
 T
Thanks, this is a very insightful response — it helped clarify several things for me. On not taking the idea to extremes I agree with your point about small, local allocations. It wouldn’t make sense to force every tiny temporary buffer through caller.alloc, especially when the compiler can place it on the stack efficiently. That would introduce unnecessary indirection and overhead. So a better distinction would be: local / temporary data → handled directly (stack, registers, compiler-managed) heap / long-lived / policy-dependent allocation → requires caller authority The goal is not to eliminate direct allocation, but to prevent hidden policy decisions in reusable code. On allocation policies as libraries Good question. I think the distinction is that a library that implements an allocation policy is not the same as a library that consumes allocation. A data structure (like a linked list) should be agnostic and request allocation: node = caller.alloc(Node) But an allocator library is itself providing that policy: class ArenaAllocator { alloc(size) { ... } } The choice of which allocator to use still belongs to the caller/root environment. So libraries don’t decide policies — but they can implement them. On the impure nature of the real world I completely agree here. The goal is not to pretend the world is pure — that would be unrealistic. The OS, filesystem, environment, and many APIs are inherently impure and global. What I’m aiming for is not to eliminate impurity, but to make it explicit. So instead of hidden global access, something like: caller.fs.open(...) caller.system.setTimezone(...) The operation is still impure, and may affect global state — but the authority to perform it is explicit in the code. So perhaps the idea is better framed as: not removing global effects, but removing hidden access to them. On your D example (GuiApi) That example is very close to what I had in mind. The GuiApi object being passed into the callback is essentially a capability — only code that receives it can perform GUI operations. That maps very well to the idea of “caller authority”. The main difference I’m exploring is whether the language could make this pattern less verbose. As you pointed out, once you start passing multiple APIs (GUI, allocator, filesystem, network, etc.), the boilerplate becomes significant. So I think language support would be essential here — otherwise this pattern becomes too heavy to use in practice. On the underlying motivation Your points about reusability and avoiding rewriting code resonate a lot with what I’m trying to achieve. The goal is exactly that: algorithms should not depend on allocation strategy logic should not depend on platform or environment code should be reusable across contexts (GUI, CLI, tests, network, etc.) In that sense, the idea is less about OO per se, and more about: separating logic from environment and policy. Refining the idea Based on your feedback, I think the core principles are better stated as: pure code is free local temporaries are free effectful operations require explicit authority libraries can request resources but should not silently decide policies real-world impurity exists, but access to it should be explicit language support is needed to avoid excessive boilerplate So perhaps a better summary is: Kite does not try to make the world pure — it tries to make authority explicit. Thanks again — this helped a lot in shaping the idea.
Apr 30
parent reply Marconi <soldate gmail.com> writes:
Hi all,

After the previous discussion, I’ve refined the idea quite a bit 
and changed direction in some important aspects. I think it’s 
easier to explain Kite by showing code instead of describing it 
abstractly.

The core idea is now:

No hidden behavior. No runtime magic. The programmer owns 
everything.

Instead of GC, exceptions, or implicit allocation, Kite uses 
ownership hooks defined at the top level.

🔹 Top-level owner (the root of responsibility)
```java
type app {
     ptr on_new(int size) {
         return allocator.alloc(size);
     }

     void on_delete(ptr p) {
         allocator.free(p);
     }

     void on_error(string e) {
         console.write("error: " + e);
     }

     void main() {
         file_reader r;
         string text = r.read("config.txt");

         console.write(text);
     }
}
```
on_new → called whenever heap allocation is needed
on_delete → called on deallocation
on_error → called when throw is executed

No GC. No hidden allocation. No implicit exception handling.

🔹 A regular type
```java
type file_reader {
     string read(string path) {
         file f = owner.fs.open(path);

         if (f == null) {
             throw "file not found";
         }

         return f.read_all();
     }
}
```
Important points:

owner is always available
it represents the responsible context (not just the caller)
throw "..." does not search for a random catch block

Instead:

it routes directly to the nearest owner.on_error(...)

🔹 Allocation model (the “bold” part)
```java
type node {
     int value;
     node next;
}
node n;
```

Here’s the key idea:

the compiler tries to place n on the stack
if it escapes (returned, stored, etc.), it is moved to the heap
when that happens, it calls:
owner.on_new(...)

So:

allocation is not implicit — it is owned

🔹 Error model

Errors are not values, and not part of the type system.

throw "file not found";
interrupts the current flow
does not return
does not require boilerplate
is handled centrally by the owner

This keeps the code focused on the success path:

string text = r.read("config.txt");

No noise. No wrapping. No propagation boilerplate.

🔹 Design principles (updated)
No global state (everything belongs to an owner)
No garbage collector
No hidden allocation
No implicit exception system
No forced “safe” model

Instead:

Kite gives full control — and full responsibility — to the 
programmer.

🔹 One-line summary

Kite is a C-like language where memory and errors are handled by 
explicit ownership hooks instead of runtime magic.

I’m still exploring trade-offs (especially around implicit stack 
→ heap promotion and performance), but I think this direction is 
much clearer.

Curious to hear thoughts — especially what breaks in this model.
Apr 30
next sibling parent reply Araq <rumpf_a web.de> writes:
On Friday, 1 May 2026 at 01:57:57 UTC, Marconi wrote:
 Curious to hear thoughts — especially what breaks in this model.
It's not about what "breaks" in this model, it's about what works in this model. What works: Hardly anything. How does the system know when to call on_delete now that the compiler is responsible for on_new? But since you used AI to write your post (easy to tell) you might as well use more AI to find all the flaws in your ideas. Good luck.
Apr 30
parent reply Marconi <soldate gmail.com> writes:
On Friday, 1 May 2026 at 02:55:40 UTC, Araq wrote:
 On Friday, 1 May 2026 at 01:57:57 UTC, Marconi wrote:
 Curious to hear thoughts — especially what breaks in this 
 model.
It's not about what "breaks" in this model, it's about what works in this model. What works: Hardly anything. How does the system know when to call on_delete now that the compiler is responsible for on_new? But since you used AI to write your post (easy to tell) you might as well use more AI to find all the flaws in your ideas. Good luck.
You’re right — on_delete was a mistake. We only need explicit deallocation: delete obj; Kite may hide where memory is allocated, but it never hides when it is freed. It separates allocation from lifetime, which is different from: C (everything manual) GC (everything automatic) Rust (everything tracked) Kite is a different hybrid model. If you forget delete, you get a memory leak — by design.
May 02
parent reply Marconi <soldate gmail.com> writes:
```c
import kite.io.file;

type node {
     int value;
     pointer node next; // manual pointer, can be 0 (null)
}

type file_reader {
     string read(string path) {
         file f = owner.fs.open(path);

         if (f == 0) {
             // throws error, does NOT return
             // control jumps to owner.on_error(...)
             throw "file not found";
         }

         return f.read_all();
     }
}

type list {
     node head;

     void add(int v) {
         node n;
         // n is created here
         // if it does NOT escape this scope → stack
         // if it escapes (like below) → heap via owner.on_heap()

         n.value = v;

         // n escapes here because it's stored in a field
         // compiler promotes it to heap
         // owner.on_heap(...) is called internally
         n.next = head;
         head = n;
     }
}

type app {
     pointer on_heap(int size) {
         // called when an object needs to go to heap
         return allocator.alloc(size);
     }

     void on_error(string e) {
         // central error handling
         console.write("error: " + e);
     }

     void main() {
         list l;

         l.add(10);
         l.add(20);

         node current = l.head;
         // current is a reference (never null)

         while (current != 0) { // pointer comparison
             console.write(current.value);

             current = current.next;
         }

         // Example of object lifetime:

         node temp;
         temp.value = 42;
         // temp does NOT escape → stays on stack
         // automatically destroyed when leaving scope

         node leaked;
         leaked = l.head;
         // leaked references a heap object (because it escaped 
earlier)

         // heap objects must be freed manually
         delete leaked;
         // if not deleted → memory leak (by design)
     }
}
```
May 02
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Sat, May 02, 2026 at 01:42:11PM +0000, Marconi via Digitalmars-d wrote:
 ```c
[...]
     void add(int v) {
         node n;
         // n is created here
         // if it does NOT escape this scope → stack
         // if it escapes (like below) → heap via owner.on_heap()
 
         n.value = v;
 
         // n escapes here because it's stored in a field
         // compiler promotes it to heap
         // owner.on_heap(...) is called internally
         n.next = head;
         head = n;
     }
[...] Wow, this makes code incredibly hard to follow. Instead of being like C, where you know everything must be manually freed, or a GC language where you know everything is automatically cleaned up, here you have to carefully read through every line of a function just to determine whether an object was allocated on the stack or on the heap. The declaration doesn't help because the declared type does not differentiate between stack or heap allocation. Not only so, the caller has to know this in order to know whether or not it needs to deallocate the object. Meaning that to determine whether the caller has a memory leak, you need to recursively scan every callee and keep track of whether something was allocated on the stack or heap. For every single object declaration. And this has to be done for *every single caller*. And good luck if you're calling a 3rd party library function for which you have no access to source code. I can already see from a mile away that this is going to be a major source of bugs. T -- "Do you know what's the price of a chimney?" "Let me guess: nothing, because it's on the house." "No, on the contrary, it's through the roof!"
May 02
next sibling parent reply Marconi <soldate gmail.com> writes:
On Saturday, 2 May 2026 at 14:11:07 UTC, H. S. Teoh wrote:
 On Sat, May 02, 2026 at 01:42:11PM +0000, Marconi via T
Thanks — that’s a very good point, especially about lifetime visibility. The direction I’m moving towards is that the owner is responsible for deciding what to do with heap objects. When an object escapes and is allocated via on_heap, the owner can keep track of it: ```c pointer on_heap(int size) { pointer p = allocator.alloc(size); heap_objects.add(p); // owner keeps track return p; } ``` Then the programmer can decide when to free objects: delete obj; Or even clean everything in one place: ```c void cleanup() { int i = 0; while (i < heap_objects.length) { delete heap_objects[i]; i = i + 1; } } ``` So the idea is: allocation may be implicit, but ownership and lifetime decisions are explicit and centralized in the owner. That said, I agree with your concern — the model must make lifetime clear enough without requiring the programmer to mentally simulate escape analysis. Also, I’ve significantly updated the language since this post (simplified memory model, removed on_delete, refined ownership, etc.), **and a lot of that came from feedback like yours — thanks for that** 🙂 I’ve opened a new thread with the current design, it would be great to continue the discussion there: Kite: a pretty C with explicit control
May 02
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On Saturday, 2 May 2026 at 14:28:00 UTC, Marconi wrote:
 I’ve opened a new thread with the current design, it would be 
 great to continue the discussion there:
 Kite: a pretty C with explicit control
I’ve removed this new thread as it’s nearly identical to this one. Please refrain from simply feeding all the responses into an LLM and posting. Future posts will be moderated as well. -Steve
May 03
prev sibling parent reply Marconi <soldate gmail.com> writes:
On Saturday, 2 May 2026 at 14:11:07 UTC, H. S. Teoh wrote:
 On Sat, May 02, 2026 at 01:42:11PM +0000, Marconi via 
 Digitalmars-d wrote:
 [...]

 Wow, this makes code incredibly hard to follow.  Instead of 
 being like C, where you know everything must be manually freed, 
 or a GC language where you know everything is automatically 
 cleaned up, here you have to carefully read through every line 
 of a function just to determine whether an object was allocated 
 on the stack or on the heap.  The declaration doesn't help 
 because the declared type does not differentiate between stack 
 or heap allocation.

 T
After thinking and thinking and thinking... You're right! ```c node n; // heap by default -> call on_heap method stack node n; // explicit stack ``` That's it. Thanks! I think I've gone crazy. :-D Note: With the help of chatgpt 5.5, I'm writing a transpiler for C, so we'll be able to test it soon.
May 03
parent reply Dejan Lekic <dejan.lekic gmail.com> writes:
On Sunday, 3 May 2026 at 18:46:08 UTC, Marconi wrote:
 I think I've gone crazy. :-D

 Note: With the help of chatgpt 5.5, I'm writing a transpiler 
 for C, so we'll be able to test it soon.
PS. you do not need AI to speak to humans on this forum. Use your own brain to compose words into sentences and write them here. :)
May 04
parent reply Marconi <soldate gmail.com> writes:
On Monday, 4 May 2026 at 18:40:59 UTC, Dejan Lekic wrote:
 On Sunday, 3 May 2026 at 18:46:08 UTC, Marconi wrote:
 PS. you do not need AI to speak to humans on this forum. Use 
 your own brain to compose words into sentences and write them 
 here. :)
No way! :-) just kidding I change this language a lot since post one. [Github](https://github.com/soldate/kite) AI: Kite is an experimental “pretty C” language: Java-like object references, C-like control, no GC, and no hidden ownership model. Its main idea is that dangerous choices are explicit: heap allocation goes through program-owned hooks, stack storage uses `stack`, nullable/raw access uses `pointer`, and cleanup uses `delete`. ```c // Kite code lives inside `type` or `special type` blocks. // // `special type memory` is the program-owned memory policy. // Normal object and array heap allocations pass through `on_heap`. // This is not a GC: the program still decides when to release memory. special type memory { list heap_objects; pointer on_heap(int size, int type) { pointer p = allocator.alloc(size); // The compiler exposes stable type ids such as `node.id`, // so the owner can track allocations by type if it wants. if (type == node.id) { heap_objects.add(p); } return p; } void on_delete(pointer p, int type) { if (type == node.id) { heap_objects.remove(p); } allocator.free(p); } void clean_heap() { heap_objects.delete_all(); } } // A normal user-defined type. // Object variables are references, not C-style stack structs. type node { int value; pointer node next; // explicit nullable/raw pointer; can be 0 void init(int v) { value = v; next = 0; } } special type main { void main() { // Heap object by default. // This calls memory.on_heap(...), then node.init(10). node a = node(10); // References are non-null by default. // `b` refers to the same object as `a`, like a Java reference. node b = a; b.value = 20; // Stack storage must be requested explicitly. // This is still a non-null node reference, but backed by local storage. stack node local = node(30); // Arrays follow the same rule: // without `stack`, storage is heap storage and goes through memory policy. int[] dynamic_numbers = [1, 2, 3]; int[3] fixed_numbers; fixed_numbers[0] = dynamic_numbers[0]; fixed_numbers[1] = dynamic_numbers[1]; fixed_numbers[2] = fixed_numbers[0] + fixed_numbers[1]; // C-style control flow, required semicolons, fixed-size primitive types. for (int i = 0; i < fixed_numbers.length; i = i + 1) { console.write(fixed_numbers[i]); } // Manual lifetime. // `delete` calls memory.on_delete(...) when the owner defines it. delete a; // Owner-level cleanup can also be explicit. memory.clean_heap(); } } ``` Beautiful, isn't it?
May 05
next sibling parent Dejan Lekic <dejan.lekic gmail.com> writes:
On Tuesday, 5 May 2026 at 12:20:10 UTC, Marconi wrote:
 Beautiful, isn't it?
You call this "beautiful"?: ``` special type main { void main() { ``` Please spare me this nonsense.
May 05
prev sibling parent Israel Matos <israel.sm yahoo.com> writes:
On Tuesday, 5 May 2026 at 12:20:10 UTC, Marconi wrote:
 On Monday, 4 May 2026 at 18:40:59 UTC, Dejan Lekic wrote:
 On Sunday, 3 May 2026 at 18:46:08 UTC, Marconi wrote:
 PS. you do not need AI to speak to humans on this forum. Use 
 your own brain to compose words into sentences and write them 
 here. :)
No way! :-) just kidding I change this language a lot since post one. [Github](https://github.com/soldate/kite) AI: Kite is an experimental “pretty C” language: Java-like object references, C-like control, no GC, and no hidden ownership model. Its main idea is that dangerous choices are explicit: heap allocation goes through program-owned hooks, stack storage uses `stack`, nullable/raw access uses `pointer`, and cleanup uses `delete`. ```c // Kite code lives inside `type` or `special type` blocks. // // `special type memory` is the program-owned memory policy. // Normal object and array heap allocations pass through `on_heap`. // This is not a GC: the program still decides when to release memory. special type memory { list heap_objects; pointer on_heap(int size, int type) { pointer p = allocator.alloc(size); // The compiler exposes stable type ids such as `node.id`, // so the owner can track allocations by type if it wants. if (type == node.id) { heap_objects.add(p); } return p; } void on_delete(pointer p, int type) { if (type == node.id) { heap_objects.remove(p); } allocator.free(p); } void clean_heap() { heap_objects.delete_all(); } } // A normal user-defined type. // Object variables are references, not C-style stack structs. type node { int value; pointer node next; // explicit nullable/raw pointer; can be 0 void init(int v) { value = v; next = 0; } } special type main { void main() { // Heap object by default. // This calls memory.on_heap(...), then node.init(10). node a = node(10); // References are non-null by default. // `b` refers to the same object as `a`, like a Java reference. node b = a; b.value = 20; // Stack storage must be requested explicitly. // This is still a non-null node reference, but backed by local storage. stack node local = node(30); // Arrays follow the same rule: // without `stack`, storage is heap storage and goes through memory policy. int[] dynamic_numbers = [1, 2, 3]; int[3] fixed_numbers; fixed_numbers[0] = dynamic_numbers[0]; fixed_numbers[1] = dynamic_numbers[1]; fixed_numbers[2] = fixed_numbers[0] + fixed_numbers[1]; // C-style control flow, required semicolons, fixed-size primitive types. for (int i = 0; i < fixed_numbers.length; i = i + 1) { console.write(fixed_numbers[i]); } // Manual lifetime. // `delete` calls memory.on_delete(...) when the owner defines it. delete a; // Owner-level cleanup can also be explicit. memory.clean_heap(); } } ``` Beautiful, isn't it?
Thing is: all the langugages you mentioned fix problems in different ways. D fixes most of C++ problems...but can still blow you up if musised. Rust make sure that you wont blow up 90% of the time and that's revolutionary. (unless unsafe {} bullshit). C++ tries to stop the timer of the bomb but you trip on a ton of wires and blow up. C you set up the bomb yourself and blow you up. You __can__ have a hybrid model. But only if you can infer the mode you're currently working in, if you handle everything, then the compiler does, but also some undefined behavior too because why not? Makes it even harder to solve it. If you develop a language that: 1. You handle everything + memory. 2. It has a "hybrid" memory model // can have it, but rules are a must have 3. It's OOP // kinda makes sense for the first iteration but ... essentially actors now, like in Elixir. 4. The program cant free memory alone, unless you do it manually. 5. But it also frees memory automatically sometimes 🤡 If you go manual, you go all the way up and then maybe add a GC-ish compiler. You're basically trying to make Smalltalk with a pretty syntax and adding pointers and undefined behavior and a lazy GC. And since you want OOP-ish state you aren't making just a bomb, you're making both a memory hog and a nuke. What if theres a 1000 instances of the same object? All of them with their own authorities? At this rate, they are essentially actors or message based concurrency. You could theroetically do it with a mother object at the start of the program, and design heavily in and hierarchy based system. Sorry if that felt harsh, but its getting inconsistent and hard to reason about. Intent does not prevent bugs. You essentially have something closer to Java/Python/Nim/Elixir syntactically...but behaves just like C/C++ and tries to be Java. With OOP-ish state, well its hard without a global GC. Right now since its the first stage of your project, I think you should focus on one: the programmer frees it, the GC frees it or RAII frees it or the objects frees it.
May 11
prev sibling parent Israel Matos <nospam nomail.com> writes:
On Friday, 1 May 2026 at 01:57:57 UTC, Marconi wrote:
 The core idea is now:
 No hidden behavior. No runtime magic. The programmer owns 
 everything.
So I know a way that maybe will stick better with the idea. It is a very old concept that existed before Rust (and a lot of us here). But you will need to redesign your language idea I think. **Design by Contract** is a model where you essentially unit-test everything as you go. You can make it declarative fairly easily and have a guaranteed no-null, no dangling pointer, no use-after-free and segfaults, as long you follow the rules. It really matches the slogan of responsibility and identity. Instead of bothering in the mental gymnastics of owner/caller/callee, errors are simply contract violations that the compiler catch and yells at you. Memory safety is achieved by guarantees of the programmer to the compiler (with proof). But it's better for a compiled language. I dont recommend using it in an interpreter. You **NEED** the compiler to check for contracts otherwise the contract is dead weight and undefined behavior. With enough tweaks you can make a safe programming language with C speed. The **Eiffel language** used it and actually proved the concept and guaranteed correctness(that in 1985). And there are people saying the borrow checker is something new. But its just DbC with extra steps.
May 20
prev sibling parent user1234 <user1234 12.de> writes:
On Wednesday, 29 April 2026 at 17:50:08 UTC, Marconi wrote:
 Hi everyone,

 [...]

 Does this model provide real advantages over existing languages?
 Are there languages that already explore something similar?
 What potential pitfalls do you see?

 Thanks!
After reading the whole thread and with my modest experience what I can tell is that you will be faced soon or later to the problem of "make it concrete". Everyone has ideas. Very fews are able to implement them.
May 05