the Stu programming language ([info]stu_lang) wrote,

RAINA - Resource Acquisition Is Not Allocation

C++ introduced the slogan RAII--Resource Acquisition Is Initialization--a misnomer since the important part of the idiom is _deacquisition_. The C++ idiom's viewpoint is roughly "we have a good model for initializing/deinitializing data as it is allocated/deallocated--constructors and destructors--and applying this to resource acquisition eases the task of deacquisition". So a more accurate slogan might be RAIA: Resource Acquisition Is Allocation. The thing is, the C++ metaphor is nice for lexically scoped resource acquisition, but otherwise it's pretty messy (auto_ptr and all its ilk). In fact, RAIA is important in the face of things like exception handling because C++ doesn't offer any other simple mechanisms for coping with exceptions.

RAIA does not, in fact, play nice with garbage-collected allocation; exposing finalization to users is fraught with peril. Hence Stu's slogan: Resource Acquisition Is Not Allocation. (Stu also strikes this pose because I am against viewing all problems as nails for the OO hammer.) What's needed is a different lexically-scoped resource acquisition scheme, one which deals with exceptions.

Stu introduces two mechanisms. The general flexible mechanism is the => statement delay operator, which causes the statement following it to be executed when the current lexical scope ends (or when an exception is thrown). So if we had a C-like file I/O library, we might write this:

somefunc(filename)
{
   f := fopen(filename, "r") => fclose(f);
   ... do stuff ...
   if [bad thing happens] then return none; endif;
   ...
   return result;
}

Note that this assumes that if fopen() fails it returns none and fclose() accepts none simply to simplify this sort of code. (The first thing the code will actually do is 'cast away' none, since fread()/fgets() sorts of codes probably won't accept none.)

The second mechanism I want to design is to make the above more automatic. We write a single wrapper function around fopen and fclose which returns a tuple containing the filehandle and a void(void) closure that will fclose it:

fopenlex(filename, mode)
{
   f := fopen(filename, mode);
   return (f, func() { fclose(f); } );
}

then in some other routine we can call fopenlex in some special way that causes the void(void) closure to automatically be treated the same as the =>-ified statement earlier. For example, maybe we assign it to a special operator:

somefunc(filename)
{
   (f, =>) := fopenlex(filename, "r");
   ...
}

But while => is a really cute operator for the original scenario, it's incredibly ugly here. The symbol "_" is already used to mean "ignore this". Suggestions? I'm also open to things that don't involve assignment to a magical special symbol; we could just prefix 'fopenlex()' with an operator which means 'return the first element of this tuple, and =>-ify the second element of it', although that's obviously less flexible.


  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    Your IP address will be recorded 

  • 4 comments

[info]huskyscotsman

September 6 2004, 11:29:30 UTC 7 years ago

This looks promising, but a couple of things make me wary. First, although the use of => guarantees that fclose will be called correctly at the end of the block, there's no obvious connection between that and the scope of f. Like, can I do this?
if (need_file)
{
    f := fopen("foo","r") => fclose(f)
}
c = fgetch(f)
Second, I don't think I just want a simpler syntax for the fopenlex thing, I think I want no syntax — I'd prefer not to have to be consciously aware about resource allocation at all, if I don't want to.

It seems like the C++ idiom works pretty well, in the way it ties together the scope of the resource lock with the scope of a variable. I like the idea of alternate solutions to the problem, but I don't see the big win of this one over RAII.

[info]stu_lang

September 6 2004, 13:01:19 UTC 7 years ago

Re: the end of block thing, no you can't do that (see note at end), but how would you do this in the RAIA model? The whole point is that it's NOT lexically scoped with the variable f.

It seems like the C++ idiom works pretty well, in the way it ties together the scope of the source lock with the scope of a variable.


It does this by pushing the constructor/destructor off into the definition of a placeholder class whose existence may be entirely irrelevant except for holding the destructor. For example, in the code generator for my Stu compiler, I have a register allocator which is, for the most part, stacklike. So at the beginning of a function generating an expression, I have a call, r := allocReg();. Then at the end of the function, I have freeReg(r). r itself is an integer.

The C++ model forces me to go write a new 'AllocatedRegister' class to get a lexically scoped destructor attached to this. The => operator just allows me to write the code inline in a natural yet bug-free way: r := allocReg(); => freeReg(r);

note at end:

A subtlety of Stu syntax here is that := is not the assignment operator, it is the declare-variable-and-assign-it operator. "f = 0" assigns; "f := 0" declares and assigns. So as written, the inner f would go out of scope immediately. (I'm going to warn on nesting variables with the same name to avoid bugs where you accidentally declare without meaning to.)

Anyway, to solve this, you would just write:

   if (need_file) f = fopen("foo", "r");
   => if (need_file) fclose(f);

   c = fgetch(f);


Except that to really Stu-ify this would be

   if need_file then f = fopen("foo","r"); endif
   => if need_file then fclose(f); endif

   c = fgetch(f);


and you could always abbreviate this to

   if need_file then f = fopen("foo","r"); endif
   => fclose(need_file ? f : none);

   c = fgetch(f);


So how would you do this in C++?

[info]huskyscotsman

September 6 2004, 17:20:57 UTC 7 years ago

Oh, that example wasn't something I actually wanted to do — I meant it as something dangerous that I might do accidentally, and would like the compiler to warn me about.

Using := would do the trick nicely, but you don't actually have to use it, right? What I was getting it was that the C++ idiom pretty much forces you to associate the resource object with a variable in the correct scope, which is a good thing.

I totally agree that defining an AllocatedRegister class would be a real pain compared to just using =>, but that's just a lame feature of C++'s syntax. The RAII concept itself is at least partly independent of the syntax... I think. (Hmm, is RAII exclusively a C++ thing?) Also, if you define AllocatedRegister once but use it 30 times, it starts to look more attractive.

What would rock would just be a cleaner way to define AllocatedRegister such that something along the lines of "r := AllocatedRegister()" has the same semantics as C++'s "AllocatedRegister r", but where the definition of AllocatedRegister is a trivial one-line function rather than a stupid ten-line class.

[info]stu_lang

September 6 2004, 17:41:32 UTC 7 years ago

well, right, hence the thing for which I'm looking for a nicer syntax:

   (r, >=) := allocRegSpiffy();
Create an Account
Forgot your login or password?
Facebook Twitter More login options
English • Español • Deutsch • Русский…