Tags
The ideal programming language should not leak resources. Resources include not just memory, but also files, network connections, device locks, sound buffers, and anything needing to be acquired and released. Even an attentive programmer can forget to cleanup or be confused by a complicated API. For the vast majority of use cases resources should not be allowed to be lost.
Implications
The clearest implication is mandating an RAII (resource acquisition is initialization) pattern. Resources are to be allocated via a constructor to an object and released when the object is destroyed. It is very important that the resources are released as soon as possible, and not sometime later. Delaying resource release can introduce artificial scarcity in the system. This has strong implications for object management and garbage collection.
To be robust this mechanism must include memory. As the most frequently used resource it makes sense that memory is automatically managed. Thus you can’t really have any malloc and free functions. You can’t even really have new and delete functions. Well, you can have the allocation functions, you just need to ensure that the memory is automatically released like any other resource.
Naturally this all implies a good exception system. We already assume that the language must have exceptions. With respect to resources it implies that the cleanup mechanism during stack unwinding is very clear. Essentially, regardless of how a scope is left, the resources allocated in the scope must be released.
Exclusions
A determined programmer will always have a way to violate this rule in a language. Nothing can really prevent a cache of objects that always grows and never shrinks. Since people will always use caches and collections it is very important to provide a good assortment of them. This provides standard code instead of ad-hoc code. Weak pointers will also be needed, and perhaps other exotic pointer types to give flexibility to API authors.
Questions
How far this goes is a big quesiton. Scanning garbage collection is insufficient to guarantee that resources are released quickly. At the same time without such garbage collection a reference loop would go unnoticed. Resource loops are a big problem as they prevent simple reference counting. Reference counting can of course be used, and should be, but something more is needed. Optimally all resources should be freed immediately, including those inside reference loops. How this is to be done is an open question.
A problem with RAII is that it makes it difficult to support tail-call removal optimization, thus restricting the use of recursion.
See for instance this page on F#: http://blogs.msdn.com/b/fsharpteam/archive/2011/07/08/tail-calls-in-fsharp.aspx paragraph “Try…catch and try…finally” at the bottom.
I find it hard to believe that RAII objects would ever be used in a recursion situation. Just because a class has a destructor it doesn’t mean it is working with a system resource.
That aside, the article mentions the F# compiler in a particular circumstance. In the general case I see no reason why the optimizer couldn’t manage the calls to the destructors and constructors as to remove the recursion.
Never mind RAII and resources, the problem with tail-call optimization lies in destructors. Because they must be executed last, it means you can’t have your call in tail position.
You are right about the optimization. The compiler could in theory automatically modify the recursive function to use continuation-passing style, passing calls to the destructors as a continuation. I had forgotten about that.
That’s true, but I think I was considering something even simpler than that. If dealing with simple data, which is often the case in recursion, the destructors may be trivial, or very simple. Post-inlining the optimizer could make the determination that the destruction can actually happen at any time (since it involves either no code, or has no further dependencies in the function).