Hacker News new | past | comments | ask | show | jobs | submit login

Longjmp in standard C, swapcontext in POSIX C. I think GCC allows gotos to the containing function from a local function (but I never used GCC local functions).

Smalltalk, and I believe ruby, allow non local return from blocks.




longjmp simply restores the machine registers (including PC and SP) and thus doesn’t respect any sort of unwind-protect, a concept unknown in C.

unwind-protect is a more general form of the c++ raii (in fact c++ got raii from Common Lisp).


`longjmp` does not restore the machine registers on all platforms. The C standard guarantees only the contents of variables marked `volatile`: anything above that is implementation-dependent.

I think that MSVC++ longjmp actually does proper unwinding, calling destructors in C++ functions on the stack, but don't quote me on that. I think it is also dependent on the compiler flags.


Implementing exception handling (with cleanup on unwind) on top of setjmp was not uncommon. GCC still does on some targets. But I'm sure you know this more than I do.

I think that RAII is different from unwind-protect and other scope based cleanup (finally, defer, with) as it is tied to object lifetimes. The fact that automatic objects lifetimes are tied to scope is a nice feature, but RAII goes beyond that.


Well lisps don’t really have destructors in the sense C++ does; objects go out of scope and become unreachable. So unwind-protect makes cleanup (closing a file, for example) explicit, which it has to be.

I wouldn’t say RAII is “tied to object lifetime” except in its name; at least I think of it as every {} pair defining an unwind-protect with object creation/destruction being how it is effected by the programmer. Perhaps that is the same thing as you said, simply viewed from opposite sides.

I do like the automatic nature of RAII, though implementing a whole class for it always feels clumsy to me even after doing it for decades.


What I mean is that if I add an instance of a class with a non trivial destructor from a container and later remove it, the destructor is automatically invoked. This is different from an unwind protect.


The object is not exactly removed from the container. It is removed from existence. The object in that situation resides in the container physically. It is constructed there and destroyed. It does not exist outside of the container afterwards, or at any other time.

This is a low-level memory management strategy being conflated with object-orientation, that is alien to higher level languages.


Well yes, it works well in c++ because the language has a strong distinction between value semantics and reference semantics, so when you remove a value from a container it must clearly be destroyed. A GC'd language with aliased references can't call destructors in this case. At best can do it when the object is collected.

Yet I think an high level language could have the same value/reference distinction. Or you could do it with linear types.


Lisp uses UNWIND-PROTECT to implement that.

For example WITH-OPEN-FILE closes the file automatically when leaving its scope.

     (with-open-file (stream "hello.world")
       (read stream))
WITH-OPEN-FILE is a macro and expands into OPEN and CLOSE operations, protected by an UNWIND-PROTECT.

Thus anything where a destructor would automatically clean things up is done in Lisp behind the scenes automatically using an UNWIND-PROTECT form.


That's again an example of cleanup associated with scope. I understand what you can do it with unwind-protect and friends.

But the essence of RAII is more than that.

In c++ if I add an opened file object to some collection and later remove it or destroy the collection, it is implicitly cleaned up.

For example let's say you are implementing an n-way out of core merge: in c++ you would create a priority queue of files objects ordered by the front current from item. You pop the front file object read the item and, if the file is not empty, push it back into the queue. Cleanup is implicit by removing the file from the queue and not explicitly adding it back. On early exit (because of an error or exception) the queue is automatically destroyed and recursively all the file objects.

There might be a way to implement this recursively with unwind-protect, but I think it is less natural.

For example python has ExitStack, which in practice is an ad-hoc container that supports recursive cleanup of contained objects, but is still not as convenient as having all containers do proper cleanup.


>> in fact c++ got raii from Common Lisp

I know and understand this is true.

Can you point to any sources (projects, papers) that further substantiate this?


The concept of RAII has emerged gradually and it would be hard to pinpoint a moment for its appearance. I am not aware of any specific feature of RAII that has appeared in Common Lisp before other languages.

The concepts of constructors and destructors have been introduced by C.A.R. Hoare in November 1965, in "Record Handling", a proposal for the extension of Algol.

At that time, Hoare was using the Cobol terms, i.e. "record" for what later was named "object" and "record class" for what later, in Simula 67, was abbreviated to "class". All the "records" discussed by Hoare were allocated dynamically, in the heap.

Constructor (Hoare, 1965-11): "In order to bring records into existence in the first place, the record class identifier should be used as if it were a function designator"

Destructor (Hoare, 1965-11): "a standard procedure "destroy" is proposed, which takes as parameter a reference to a record, and which reverses the effect of having created that record"

The next step towards RAII has been done by Bjarne Stroustrup in "C with Classes" in April 1980, when he made the invocation of the destructors implicit at the exit from a block, by introducing the special member function "delete" for this purpose. Despite the name, the 1980 "delete" member functions corresponded to what later, in C++, were renamed as destructors.

So in 1980, RAII was complete, but it was not yet promoted as a universal strategy for managing resources.

In 1980, Common Lisp did not exist.

Most older Lisps did not have any concept similar with the Algol block, so it would have been impossible for them to invoke implicitly some cleanup functions at block exits. They relied only on the garbage collector, where there is no RAII in the Stroustrup sense, even if GC and RAII are alternative methods for avoiding the explicit invocations of "free", "close" and the like.


MDL had UNWIND in 1977 and Maclisp added UNWIND-PROTECT in 1978. I'll guess that Lisp Machine Lisp then got it, too. Common Lisp emerged 1981 onwards, largely based on the latter.

In the Lisp Machine OS there is a concept called RESOURCE for manual memory management. For example the CHAOS network stack has to deal with network packets. There is a macro USING-RESOURCE, which allocates/gets an object and on exit frees it. The macro expands into a form using also UNWIND-PROTECT to ensure freeing a resource on non-local exit. I would think that this is from around 1980, for the MIT CADR machine.


Thanks for pointing to MDL:

http://www.ifarchive.org/if-archive/infocom/info/MDL_Primer_...

Nevertheless, the MDL UNWIND and the later UNWIND-PROTECT are more limited in applications and they require much more work from the programmer than the mechanism introduced by Stroustrup in 1980.

With implicitly-invoked destructors, the destructor body is written once for each type of data, and normally there is no need to ever invoke it explicitly.

After writing correctly the constructors and destructors, the programmer's work becomes identical with using a garbage collector, because the objects are allocated explicitly, but they are never deallocated explicitly.

On the other hand, UNWIND is intended for handling exceptions. It can also be used as a normal cleanup strategy, but it still must be written every time for handling the exit from a block or from a hierarchy of nested blocks. In the latter variant, there is some economy in code writing, but the lazy deallocation is less efficient.

The UNWIND of MDL has little resemblance to RAII, but it resembles the UNWIND of Mesa (programming language used at Xerox, starting with 1976, which has introduced many innovations that have been included only much later in most programming languages).

It would be difficult to determine whether UNWIND has appeared first in MDL or in Mesa, or if both have taken it from another language, because experiments with exception handling were fashionable during those years and there were many places where various variants were tried.


UNWIND-PROTECT is a building block.

Resource management in the MIT Lisp OS ca. 1980, approximate example.

One would define a resource of arrays, where arrays can be allocated and deallocated. They will be managed via a pool.

    (defresource 2d-array (rows columns)
      :constructor (make-array (list rows columns)))
Now user code would use the USING-RESOURCE macro, where it spans a dynamic scope. Entering the scope allocates the resource. Inside the scope the resource is allocated. Leaving the scope will automatically deallocate the resource and put it back into the pool. A deinitializer may free memory as needed.

    (using-resource (my-array 2d-array 100 100) ; get me a 100x100 array from a pool
      (setf (aref my-array 42 42) 'the-answer)  ; setting the array
      (print (aref my-array 42 42)))            ; reading the array
To make sure that the resource gets deallocated, the above macro form will expand to something using UNWIND-PROTECT:

    ...
     (unwind-protect                                            

       (progn                                                   ; protected form
         (setf my-array (allocate-resource '2d-array 100 100))  ;   allocate the array
         (setf (aref my-array 42 42) 'the-answer)               ;   setting the array
         (print (aref my-array 42 42)))                         ;  reading the array

       (when my-array                                           ; exit form
         (deallocate-resource '2d-array my-array)))             ;   deallocate the array
    ...
This is an example of manual memory management using a pool of resources, where the DEALLOCATE is done always via UNWIND-PROTECT.

Thus the user will not explicitly use UNWIND-PROTECT, but some macros which use it in their expansion...


Thanks for the explanation.

I agree that this is pretty much equivalent to RAII.

Nevertheless, it is also obvious that this was not a source of inspiration for Bjarne Stroustrup.

He has started directly from the constructors and destructors of C.A.R. Hoare (1965-11) and Simula 67 (1968-05, Kristen Nygaard & Ole-Johan Dahl).

The only change is that in 1980 he has enhanced his compiler for "C with Classes" to generate automatically all the invocations to the appropriate destructors in all the block and function epilogues, relieving the programmer from this task.


No, but Tiemann might remember as I remember introducing him to unwind-protect (not that he invented RAII afaik, nor would claim to have)


Smalltalk only allows the return from the block's lexically enclosing method, not an arbitrarily specifiable context like Common Lisp does. It's discussed a bit on c2.com [1]

[1] https://wiki.c2.com/?SmalltalkBlockReturn


In Pharo or Squeak, you can just send the "return:" message to any context (except contexts that have nowhere to return).


swapcontext was in POSIX.1-2001; it was removed in 2008.

In relation to all this, in POSIX, there are often good reasons to use sigsetjmp and siglongjmp rather than setjmp and longjmp, because these also save and restore the signal mask. If you jump out of a context that locally disabled certain signals, you likely want them restored, like you would if that code returned normally. (It doesn't necessarily have to be a jump out of a signal handler!)




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: