Wednesday, May 29, 2013

The Vindication of Joel Spolsky

A long time ago, my friends I would often frequent bars and clubs with one express purpose: getting completely black-out drunk and going home with some easily-duped girl.  As it is when you are a group of twenty-something engineers going to a club, usually one of either two things happen: you don't get admitted at all, or, if you do, you are escorted out before the end of night due to an unacceptable B.A.C.  Ah, it was the blurst of times...

Around the same time, in one of the more notable posts by one of the more notable software bloggers, Joel Spolsky explained his stated policy to "never throw an exception of [his] own".  Those words were almost immediately pounced upon by programming world at large, some going as far to muse that perhaps his site had been hacked or he was under the influence of some sort of narcotic while writing.  He compared them unfavorably to a goto statement, and they compared him to an out-of-date stick-in-the-mud.  And the war between exceptions and status codes raged on.

This is sad, because it shows just how completely mystified software developers are when it comes to error handling. No, not just Mr. Spolsky, and no, not just the blogosphere, but pretty much everybody on both sides of the war.  It's no wonder why, after reading that post, PC software is mostly garbage.  Sadly, both groups are dead wrong about everything.  The correct answer when someone asks you if you prefer exceptions or status codes is neither.

Ironically, had all programmers involved spent more time indulging in vice instead of waxing pedantic about error handling mechanisms, the world would be a much better place.  Fewer bugs, more features, happier customers, happier programmers, happier managers, and more regretful young women trying to sneak out the next morning.  Everybody wins, except of course the regretful young women.

If you go all the way back to 1976 and read the seminal paper on exception handling [Exception Handling: Issues and a Proposed Notation], it groups the ways in which a subprogram can fail into two basic categories: Domain failures and Range failures.  A domain failure occurs when "an operation's inputs fail to pass certain tests of acceptability", whereas a range failure occurs when "an operation either finds it is unable to satisfy its output assertion, or decides it may not ever be able to satisfy its output assertion".  In the modern lexicon, we normally call them preconditions (conditions that must be met for the subprogram to start) and postconditions (things that must be true for the subprogram to have completely successfully).

Those are sort of abstract definitions for our purposes, so let's put things into a more relatable sense.  For me and my reprobate friends, the operation is, of course, Get_Laid.  The precondition is Admitted_To_Club, and the postcondition is P_In_V. The function body would look something like this pseudo-code:

begin
   while Is_Sober loop
      Consume_Martini;
   end loop;

   while Rejected loop
      Talk_To_Girl;
      Consume_Martini;
   end loop;

   Take_Girl_Home;
end;

Easy enough, right?  But anyone who's tried this particular approach knows it's an idealized version of a very complex task.  As mentioned before, we often either get stopped by the bouncer at the door, or tossed out mid-subprogram due to loud, uncouth, or generally lecherous and lascivious behavior.  If we never get into the club at all, a precondition has failed and the operation never starts at all.  If we get escorted out halfway through, a postcondition cannot be met and the operation ends early.

But what brought death into our programming World, and all our woe, is that the difference between domain failures and range failures is in the eye of beholder.  If we don't get admitted the club, then clearly we are not getting laid tonight, the output assertion cannot be met, and as such could be considered a range failure.  Similarly, we might say that staying reasonably sober is a precondition to going out, and that someone getting ejected means a domain failure has occurred.

Take, for instance, the venerable strtol() function, which converts a given string to its integer representation.  This has the obvious requirement that the supplied string represent a valid in-range integer for the subprogram to convert.  So, supposing that the client supplies a string that doesn't represent an integer, do we consider this a domain failure or a range failure?
  
On the one hand, you can take the overly-narrow view that an interface contract is something defined exclusively by the language.  The function declaration requires a const char *, and that's all there is to it (or, as a coworker once argued, "anything else is a comment, not a contract").  So long as the client supplies a valid pointer to an array of characters, the interface has been pedantically met, and the input assertions have passed.  If the string cannot be converted, you've got yourself a range failure.

Though, on the other hand, you could take the overly-broad view that an interface contract is something conceptual defined by the developer.  The domain of the function is not simply an array of characters, but characters representing a valid integer.  A language that is not expressive enough to establish this condition (apart from a comment in the header) is a failure of the language, not the code.  If the string cannot be converted, it's because a precondition wasn't met, and you've got yourself a domain failure.

But don't think for a moment that this is just a semantic debate.  Depending on your point of view, the same exact failure has completely opposite ramifications.
  
The consequences of a domain failure are quite simple: there is a programming error in the calling code.  It was the client programmer's responsibility to ensure the condition was met before the call, but that wasn't done, and so the calling subprogram is at fault and must be fixed.  Simply put, there is a bug in the calling subprogram.  Unless your code has become self-aware, the only thing to do about it is crack open the source, fix the flawed subprogram, recompile it, and redistribute it.  This means reporting the issue back to the caller is, generally speaking, useless.  After all, we know the code cannot be trusted to properly verify its inputs, so we certainly can't expect it to properly verify the outputs (though you would be shocked how often programmers expect this...).
  
The consequence of an output failure is a horse of a much different color: the subprogram failed because of some condition that could not be detected until the middle of the call itself.  In this case, the caller doesn't know the condition exists yet, and so it must be reported back so they can evaluate the failure and resolve it.  This is not necessarily a bug in the code, since presumably the calling subprogram can (potentially) fix the problem and try the operation again.  We are essentially returning a status back to the caller, which may be good, bad, or indifferent: the onus is on them to decide what to do.  The form of this status can be either a return value, an exception, an ON ERROR handler, a 'long jump', or any number of other forms.  The point is that we must alert the caller somehow so he may take corrective action.

And this is where things get very, very complex.  Suppose, as before, that someone passes a string to strtol that doesn't represent an integer.  Is that a bug in the calling code?  Or is that a condition that has to be reported back to them so they can fix it?  Is the program executing as expected, or do we have undefined behavior?  Are things ok or not?  How is it that the same condition can mean two totally opposite and opposing things?!

So we start to see two competing methodologies emerge.  We've got the pessimistic "assert style" domain failures, that should never occur in a properly written program and, if they do, indicate a static programming error that can never be handled at runtime ('handled' in the sense of 'fixed').  Then we've got the optimistic "status style" range errors, which involve returning information to the client (in some unspecified form) so that he can take corrective action and attempt the operation again.  And back in the primitive days of C, this was how programs were written: assert macros checked for domain errors and status codes indicated range errors.

But assert macros had the unpleasant side effect of dumping the entire program, all at once.  No warning, no chance to clean up, no ability to continue other tasks or log errors, continue in a reduced functionality mode, or event present an apologetic message to the user.  It just ends.  And while it's true that a domain error can never be fixed at runtime, we still might want to carefully avoid the error and keep other isolated portions of the program running, log some sort of error, continue with partial functionality, or other casualty action.

So if you are writing some general-purpose and reusable library or framework, you can't very well assert on anything since, after all, you don't know what action the application programmer wants to do.  If they are using your math library for keeping an aircraft in the air, an assert is a very bad idea.  So in the interest of generality and usability, most libraries and frameworks avoided asserts in favor of status codes.  Or, in other words, they turn all domain errors into range errors.  The intention wasn't that the client should always be able to "fix" the problem, the intention is to let the client decide the appropriate action which, in most cases, is just to assert.

But if the road to hell is paved with good intentions, it's also lined with status codes.  For whatever the reason, most programmers just followed the precedent set in the libraries, and kept returning status codes within their own programs.  Maybe it was from hasty, unplanned attempts at component-based development.  Maybe it was just cargo-cult mimicking of what other software already did.  Maybe it really was an honest attempt to write reliable code.  But in any case, the idea of a "domain error" became all but extinct.

So when C++ added exceptions, most just figured it was a better way to return a status code.  After all, one of annoying parts of using status codes is that you use up your one return value.  And low and behold, an exception lets you return both a status and a result, with a minimum of fuss.

But they were wrong.  The proper use of an exception is not for returning range errors, but a structured mechanism for returning domain errors.  It's like an assert with some semblance of control.  It's one (small) step up from a crash.  It's a better way to end a malfunctioning program, emphasis on malfunctioningAn exception is just a way to shut down a broke program.

The key to creating safe, bug-free programs that work is to reverse this trend: turn all range errors back into domain errors.  Looking for a better way to return status information to the client misses the forest for the trees: eliminate the need to return the information. The tricky part about this, however, is that it's not just some syntax icing to smear over your already-written subprogram: it normally involves a complete redesign of the program in question, if not the entire architecture of the system.

Let's revisit the code for our night of debauchery from above, and refactor it a little bit.  Currently, we are considering "getting thrown out of the club for being drunk and disorderly" as a range error.  The question is not "how do we report this?", but instead "how do we stop this from happening?"  The obvious way is the have a designated sober person to keep the alcohol intake of the rest of the group in check, so we can simply add a precondition to the start of the subprogram:

if not Group_Has_Designated_Chaperone then
    raise NO_CHAPERONE;
end if;

(Note, of course, these days you ought to use the 'Pre' aspect, but I write it like this to accommodate those still unfortunate enough to deal with C++.)

Note that we are still raising an exception, but it means something entirely different.  One of the preconditions for getting laid is to make sure the group has a designated sober person before we go out.  So long as this is true, there is no chance of us getting kicked out for being drunk, and everyone gets laid.

And no matter how many reasons you can come up with for not getting laid, there is always a way to avoid the situation.  Maybe the girls don't like our clothes?  Let's add a precondition of Dressed_To_The_Nines.  Maybe we can't find girls to talk to?  We have a precondition of Club_Has_Fat_Chicks.  Just keep adding preconditions until there is no possible way that the subprogram can fail to produce a result.  Our post condition (P_In_V) is always met!

Or, in other words, write a subprogram that can't ever possibly fail, so long as its preconditions are met.  Error handling is a proactive process, not a reactive one.  Engineering, software or otherwise, is about not having exceptional conditions.  It's about the system doing exactly what you expect, and nothing else.  A properly coded program should raise an exception at every possible point, but never ever have it happen.

This makes the error-handling debate a moot point, since there won't be any more errors to handle.  Once the client calls Get_Laid (with the appropriate conditions met), there is never any chance of it failing.  We can just call the subprogram, confident we will always get laid, and forget all about checking to make sure it did.  Moreover, if it does fail, recall from above that this indicates a static programming error, either in the client or the server, and so the only appropriate thing to do is clean up and end the program (or task, etc), which an unhandled exception will do for us automatically.

For a more practical example, consider the following code abortion:

begin
   loop
      read_next_byte;
      process_byte;
   end loop;
exception
   when End_Of_File => Put_Line "Success!";
end;

I suppose this is a valid way to code, but any project that tries is likely doomed.  If we want to read a file till its end, then reaching the end is clearly not an exceptional condition!  It's what's supposed to happen!  It's a required condition!  Contrast it with the following:

while not Is_EOF loop
   read_next_byte;
   process_byte;
end loop;
Put_Line("Success!");

Remember the difference between domain errors and range errors?  In the first case, reading past the end of the file is an ambiguous range error; it could mean the successful completion of the program, or it could mean we are stuck in an infinite loop because of some coding error.  In the second case, however, reading past EOF is always bug.  The task needs to shut down immediately, because the program is broken, and exceptions not only shut things down but tell you the line number of why.  It's as if the compiler is doing the debugging for you!

It's at this point that someone always tries to point out a situation where this isn't possible, and where you just have to use an exception.  These people are wrong.  That's not to say there are conditions where it's more convenient to use an exception, or where you can rationalize using an exception, or where you are forced to use an exception to interface with some other component, but that's a far cry from it being impossible.

A common one is a heap allocation. You use new to allocate a chunk of memory, but if there isn't enough room, you get a Storage_Error.  There's no way to see if there is enough room on the heap before you make the call, so apparently we have to return this to the caller to let him deal with it after the fact.

But that's not really true, is it?  The default heap has no mechanism to ascertain the remaining amount of space, but who says we have to use the default heap?  Is it impossible to write our own user-defined storage pool and add a Remaining_Bytes subprogram so that clients can ensure there is enough room before trying the allocation?  Not exactly easy, but then again who said writing safe code was easy?

Another supposed example is multithreading.  If we want to have multiple threads access our fancy new custom heap, adding the requirement to have enough room creates a nasty race condition: two threads both pass the precondition, and then one of them gets the last remaining few bytes, while the other fails.  (Note, however, that this is exactly what is supposed to happen: the exception alerts us to a static programming error as soon as it happens, instead of trying to diagnose the race condition by hand).  But this simply means our fancy heap must include a Lock_Heap and Unlock_Heap subprogram.

So yes, all this means that instead of writing this:

x := new Integer'(42);

it balloons into this:

Lock_Heap;
if Remaining_Bytes > Integer'Size/8 then
   x := new Integer'(42);
else
   Do_Something_Else;
end if;
Unlock_Heap;

Like before, an exception in the first case is ambiguous.  Does a Storage_Error indicate a misbehaving memory leak that should never happen, or is it a "successful" detection of a low memory condition?  In the first case, there's no way to tell.  Sometimes it's success, sometimes it's failure.  But it's a moot point in the second, because it never happens.  We don't react to low memory conditions, we prevent them before they happen.

So was Joel Spolsky right?  Far from it.  He's just a pragmatic programmer, fed up with decades of C++ programmers crying wolf, trying to fake success by misusing a mechanism designed to indicate failure.  But like he said, you do need to be able to read the code.  Exceptions are invisible, they do obfuscate the control flow, and they are a precarious way to return information to the user.  They are just like a goto, but that's okay, because the only place it's supposed to go to is directly to the end

So yes, use exceptions.  Throw everywhere you can, but don't ever catch. You'll be surprised by not only how reliable your programs start to become, but how much more successful you can be at picking up chicks.  And really, isn't that what it's all about?

Saturday, April 20, 2013

The curious case of the coextension


In earlier posts, we saw how the so-called "anonymous" access parameters and discriminants provide a safe mechanism for passing around a shorter-lived object to a longer-lived subprogram, by essentially making it limited for the duration.  The syntax allows the client to provide an access value of any type, of any accessibility level, whereas the server must renounce all knowledge of what the actual is.

But this opens up an interesting syntax loophole.  An allocator (i.e. a new statement) dynamically allocates a chunk of memory from the heap, and returns an access value to that memory.  And since Ada is an expression-based language, nothing is stopping us from writing something like this:

procedure P (x : access Integer);

P(x => new Integer'(42));

At first glance, your gut reaction is likely to sound the memory leak alarm.  We allocate a new integer off the heap, and pass it into P as an access parameter.  Being that it's an access parameter, we can be certain that no copies have been saved off during the execution of P.  And since we never assigned it a variable in the calling code, we have no way to reference it after P completes and the argument x disappears forever.  The preceding code, if operating as assumed, would always be an incorrect memory leak.

The language designers noted this, too.  They realized that "Unchecked" prefixes Unchecked_Allocation only because there is no guaranteed way to know how many references of that allocation have been made, and thus no way to ensure that no references still exist.

But like we just saw in the legal code above, we can guarantee that no references to the allocation exist after the subprogram call.  We know, 100% of the time, that it's no longer unchecked to deallocate that integer.  In fact, from a programmer standpoint, there is never a situation in which they could even have a reference to even try.  That integer is statically known to be totally inaccessible.

And so buried way in the back of the Ada95 reference manual is a tiny little bit of advice most Ada programmers have no idea exists:

13.11-25 - A storage pool for an anonymous access type should be created at the point of an allocator for the type, and be reclaimed when the designated object becomes inaccessible.

Or, simply stated, automatically reclaim the memory for an allocator of an anonymous access type when it goes out of scope.

So in essence (and usually in practice), that "allocation" from above:

P(x => new Integer'(42));

would actually be compiled to something like this behind your back:

declare
   temp : aliased Integer := 42;
begin
   P(x => temp'Access);
end;

which is, for the record, what you would (and probably should) have written anyway.

The same holds true for an access discriminant as well.  For instance, given

type T (x : access Integer) is null record;

o : T (x => new Integer'(42));

the same thing happens (or, at least, is advised to happen; remember that implementation advice is only that).  You end up with something akin to this:

declare
   temp : aliased Integer := 42;
   o    : T (x => temp'Access);
end;

And for a long time, these so-called anonymous allocators were nothing more than a little syntax sugar that nobody really knew about, and fewer people used.  Furthermore, the keyword new is almost universally associated with a heap allocation, and so using an anonymous allocator just obfuscated the code while providing no real additional functionality.

But that changed in Ada 2005.  One of the major improvements to the 2005 revision was the refinement of limited types.  Thus far, the restriction of 'copying' limited types also applied to their initialization since, technically speaking, an aggregate or a function return constituted assignment (i.e. a temporary object is constructed, and then "assigned" into the result).

The new "build-in-place" mechanism fixed this, but added many new subtle problems to deal with.  Recall that in Ada95, any type with access discriminants was required to be limited, which as we just saw precludes it from being constructed via an aggregate.  The only way to return a limited type was to declare a local (uninitialized) value and return that, which in the case of an anonymous allocator would result in a inevitable runtime check failure.  For instance, the following is illegal, since limited aggregates are a no-no:

function F return T is
begin
   return T'(x => new Integer'(42)); -- Ada95 illegal!
end F;

whereas we can do this:

function F return T is
   Result : T (x => new Integer'(42); --legal, but dubious...
begin
   return Result;
end F;

but this will be guaranteed to raise a Program_Error, since our anonymous allocator "allocates" our integer as a local stack value to F (a good compiler should offer a warning).

But now with Ada2005's "build in place" mechanism, the first version becomes legal.  This begs the question of where exactly the integer should be allocated.  Our previous rule is not really appropriate, being that the "point of the allocator" is still inside the subprogram F, which is different than where the return object itself it.  The return object is really being "built into" the result from the calling subprogram, so we might be tempted to say the anonymous allocator should be declared in the previous stack frame.  But alas, this has complications as well.  For example, we are alright if the previous subprogram is simple:

Foo : T := F;  --stack frame is okay

but things get tricky if we change it to this:

Foo : T_Ptr := new T'(F);  -- stack frame is no good!

Now our 'outer' object (type T) is being "built into" the heap, so if we were to put our Integer on the stack of the calling program, our outer type might outlive out inner type, and we suddenly have a dangling reference! 

So to help clarify things, Ada 2005 created a special name for an anonymous allocator of an access discriminant: the coextension (not to be confused with regular tagged type extension).  The rules were updated as follows:

The storage pool used for an allocator of an anonymous access type should be determined as follows:
  • If the allocator is defining a coextension of an object being created by an outer allocator, then the storage pool used for the outer allocator should also be used for the coextension; 

  • For other access discriminants and access parameters, the storage pool should be created at the point of the allocator, and be reclaimed when the allocated object becomes inaccessible;

  • Otherwise, a default storage pool should be created at the point where the anonymous access type is elaborated; such a storage pool need not support deallocation of individual objects.

So we can see the original definition there as our second criteria, but we also have two new ones.  The third relates to the new Ada2005 "stand alone" access types (and will not be discussed, but note there is not any deallocation!), but the first specifically addresses this new situation of nesting an anonymous allocator of an access discriminant within a return statement of a function being used in an aggregate of an allocator (what a mouthful!).

It states that in this unique situation, the 'inner' coextension should be placed in same place as the 'outer' allocation; if you use the stack for the outer one, then the inner one goes on the stack.  If you use the heap for the outer, the inner goes on the heap.  If you use a custom storage pool for the outer, the inner goes in the same custom storage pool.  You get the idea.

But more to the point, regardless of where it's stored, the inner is finalized at the same time as the outer (See 3.10.2~14.2/2).  This can actually provide some unique and powerful opportunities if you know how to use it.

As an example, let's suppose we want to create an OOP-style object that sends messages to another computer over a TCP/IP socket.  Externally, we give it a Send_Message operation, so the user can simply call it with a string, which gets packaged up and sent over a socket to the other computer.  During the initialization, it opens up a socket to use for subsequent operations, and during finalization it closes it.

But if put on our reusability hats on, we quickly see a problem.  We saw that we have to create a new socket as part of the initialization, so we might jump to this conclusion:

type Connection is record
   Socket : BSD_Socket_Type;
end Connection;

function Make_Connection return Connection is
begin
  return Connection'(Socket => Make_BSD_Style_Socket (Port_Number => 1000));
end Make_Connection;

This would work, of course, except there are many more styles of sockets than just the conventional BSD.  Perhaps we might want to use Winsock, or GNAT sockets, or any number of other implementations.  We could organize these into a Socket'Class hierarchy, but that doesn't help us much since we have to call a specific concrete constructor function with our desired port number.

Normally we solve this conundrum using a 'factory' object.  Apart from our Socket'Class hierarchy, we also have a Socket_Factory'Class for each corresponding socket type.  We pass in the factory object, use that to produce the classwide version of the socket, and our connection is now general and reusable enough to handle all sockets, past and present, even at runtime.  So we try this approach:

type Connection is record
   Socket : Socket'Class;  -- Oops!
end Connection;
 
function Make_Connection (SF : Socket_Factory'Class) return Connection is
begin
  return Connection'(Socket => SF.Build(Port_Number => 1000));
end Make_Connection;

But right away we find another problem: classwide types are indefinite, and so there is no way to nest them within a record if the compiler doesn't know the size.  In other languages (and in previous versions of Ada), the only answer to this problem was to use the heap:

type Connection is record
   Socket : Socket_Class_Ptr;
end Connection;
 
function Make_Connection (SF : Socket_Factory'Class) return Connection is
   s : Socket_Class_Ptr := new Socket'Class'(SF.Build(Port_Number 1000));
begin
  return Connection'(Socket => s);
end Make_Connection;

And this works well enough, except now we have to address unpleasant lifetime considerations.  We can add a deallocation to a finalize routine, but any experienced programmer will tell you this a sure-fire way to produce memory leaks.  If an exception is thrown during our constructor function, we have to add complicated spaghetti code to conditionally deallocate it based on when the exception occurs.  Moreover, the only way to get rid of a heap allocation is with an Unchecked_Deallocation which is, of course, Unchecked (and often disallowed in safety critical software).  Plus, this precludes us from using this software on embedded processors without a heap, or when allocations must come from a custom storage pool.

A marginally better way is to use a so-called "smart pointer" or "auto pointer" that deallocates the object when it goes out of scope, but aside from still unnecessarily forcing the program onto the heap, that's exactly what an anonymous allocator already does for you.  For instance, we can rewrite it like so:

type Connection (Socket : access Socket'Class) is null record;
 
function Make_Connection (SF : Socket_Factory'Class) return Connection is
begin
  return Connection'(Socket => new SF.Build(Port_Number => 1000));
end Make_Connection;

Now we have the best of both worlds (or, depending on your opinion, a weird hybrid mix of both worlds).  We are still "allocating" our socket, but not necessarily from the heap.  Our socket is saved in the same area as our connection object, so it can work equally well on the stack, the heap, or even custom storage pools.  No unchecked mechanisms are needed, and so we are guaranteed to be dangling-reference free and memory leak free.

But this isn't the only situation in which we can benefit.  We added a discriminant to our Connection object, which has the unfortunate and often maddening side-effect of making the record itself indefinite (despite not being variant in any way).  This means we will have the same problem if we try to compose other objects out of this one (this also applies to the de facto standard mechanism of using unknown discriminants for the public view of a private type to force initialization).  For instance, this is problematic:

type Database is record
   x : Connection;  -- No longer definite!
end Database;

But it's coextensions to the rescue again.  We can apply the same logic, all the way up:

type Database (x : access Connection) is null record;

function Make_Database return Database is
begin
   return Database'(x => new Connection'(Make_Connection(...)));
end Make_Database;

And, like before, we get to use our indefinite types without resorting to the heap and its inevitable memory leaks, dangling references, and unchecked mechanisms. 

For what it's worth, this isn't an especially elegant way to achieve the desired effect: anonymous allocators are probably one of the most "expert friendly" constructs in the entire language, and very little documentation addresses them outside of the AI's and a couple references in the rationale.  The fact that so few programmers utilize them means that compilers are mostly untested, and there are often bugs that crop up under certain circumstances.  Plus the syntax itself (with the new statement) is almost universally associated with the heap, which is undeniably contrary to Ada's readability goals

But for all it syntax woes, the capability cannot be impugned.  For better or worse, there are many circumstances where indefinite types (e.g. classwide types and those with unknown discriminants) can force a programmer onto the heap, usually unnecessarily so.  Coextensions (and the new aliased parameters, hopefully to be discussed later) provide a safe, checked, and reliable way to deal with these situations while still leaving the decision of storage pool up to the client.

Sunday, March 17, 2013

Ada access types, part IV


Thus far we've seen just about all of the "normal" uses of Ada95 general access types, which all boil down to basically two things: using subtypes to placate the compiler into ensuring local pointers cannot be copied out into the world, and using so-called 'anonymous' access discriminants and parameters to safely copy them out if required.

We've also seen two special circumstances where the static rules are insufficient for ensuring lifetime control and thus require runtime checks inserted by the compiler to keep you honest.  The first was a function that returned an record containing access discriminants, since nothing in the syntax prevented you from returning a pointer to a local object.  The second was an explicit typecast of an access parameter which, while valid, is dangerous and not recommended.

However, there is one final circumstance (in Ada95) that requires a runtime check, and while you are unlikely to stumble across this situation through normal everyday programming, it's important in all situations to know when your program can never fail, and when it might.  This final circumstance involves generics.

Suppose we have two packages, where one passes the address of a 'global' object to the other, which saves it into a global pointer:

package P1 is

   type Int_Ptr is access all Integer;
   Global_Pointer : Int_Ptr;
  
   procedure Save (Arg : Int_Ptr) is
   begin
      Global_Pointer := Arg;
   end Save;

end P1;

package P2 is

   Global_Object : aliased Integer := 42;
  
   procedure Store is
   begin
     P1.Save (Arg => Global_Object'access); -- pass
   end Store;


end P2;

By all accounts, this is safe code.  The static check compares the lifetime of the object in question (Global_Object) with that of the access type being created (Int_Ptr), and finds they are both at the top-most library level.  Obviously the object is going to be there until P2 is finalized, which is essentially the entire life of the program, so we can copy pointers to it wherever we please.

Now suppose we make P2 generic for some reason (the reason does not matter):

generic
   type T is (<>);
package P2 is

   Global_Object : aliased Integer := 42;
  
   procedure Store is
   begin
     P1.Save (Arg => Global_Object'access);
   end Store;

end P2;

This complicates matters greatly, since we don't know where the package is going to be instantiated.  If we instantiate P2 as its own, stand-alone library package then the code is as before, Global_Object is a library-level object, and everything is hunky-dory.  However, there is nothing stopping us from doing, say, this:

procedure P is

   type Some_Type is range 0 .. 100;
   package Dangly is new p2 (T => Some_Type );
 
begin

  Dangly.Store;
end P;


Now the package is actually nested inside a procedure, so everything is one level deeper, including our previously "global" object.  When P ends, our package ceases to exist along with the our now-no-longer-global object, and P1 contains a dangling pointer.

Checking for this is complicated further by the requirement of separate compilation for generic bodies.  Some compilers implement the so-called "macro expansion" model, in which each instantiation basically recompiles the entire generic soup-to-nuts.  An alternative model, 'shared generics', compiles the body once and then reuses the same code.  In a shared generic model, there would no way to know during the compilation of the body where the instantiation would be, and therefore no way to know what the accessibility level of the object would be during the check.  Indeed, we might instantiate this generic any number of places at any number of levels, so some might be safe while others might fail.

The only way out of this problem is to insert a runtime check that verifies the actual level of our 'global' object is not deeper than the pointer being created.  If this check fails, like other checks, Program_Error is raised.  Note that any worthy "macro expansion" compiler should note the inevitable accessibility violation (since it knows where the generic is instantiated) and report a warning (to its credit, GNAT 2012 correctly generates both the warning and the exception).

And this about does it for the wild and wacky ways that Ada95 deals with general access types.  One static check, access parameters, access discriminants, and three runtime checks (as needed).  Correct and judicious use of them can only make your programs safer and better.

Friday, March 15, 2013

Ada access types, part III

Thus far we've seen how accessibility rules "imprison" a local pointer at the scope in which it was declared, but how access parameters and access discriminants provide a safe mechanism for passing them around when needed.  But there is trouble in paradise, and it involves a brief side trip into a seemingly unrelated topic: OOP.

(It should be noted that this post is mostly my own opinions, moreso than usual.  I was obviously not there when they designed the language, nor do I know all the reasons for the design that was chosen.  In fact, much of the 'why' below is different than the 'official why', so please make your own judgements and take everything with a grain of salt.  Or ask your friendly neighborhood ARG member.)

Aside from general access types (and many other things), Ada95 also added support for so-called "object-oriented programming".  I can't possibly go into all the details, suffice to say that it is a programming model that relies on dynamic dispatching to select the correct subprogram at runtime, thus making programs much less brittle.

For example, suppose we want to create an abstract "file stream" parent, that allows us to read a single byte from a file.  This will be implemented by two child types, Normal_File and ZIP_File, where the former will simply read bytes from disk as expected, but the latter will do the messy work of decompressing the bytes 'on the fly'.  The first one will likely be quite simple (and just ferry calls to whatever the underlying disk is), whereas the second will be much more complicated, involving all manner of caching, maintaining file pointers, and so on.  We might establish our types like so:

type File is abstract tagged null record.
function Read (This : in out File) return Byte;

But right off the bat, we find an issue: functions cannot have out parameters.  It's an almost certainty that the ZIP_File will need to modify its own state during a read, since it will likely have to decompress a large chunk of data, and then return each byte from inside the cache.  But by being restricted to 'in' parameters, this is a non-starter.

Before Ada2012 solved the problem once and for all, there were two main workarounds to this problem.  The first, which was so widely used that it still exists in almost all Ada code, is to replace the function with a procedure and use an 'out' parameter, like so:

procedure Read(This : in out File, That : out Byte);

Of course this is not a general solution, since there are cases where only a return value will do (unconstrained arrays, discriminated records, etc).  The other method is, of course, to use an access value.  The only requirement is that the parameter itself not be updated, so we can modify anything we wish simply by adding an '.all'.  That means our function now looks like so:

function Read (This : File_Ptr) return Byte;

But while this is a step forward, it's also two steps back.  Remember that dynamic dispatching only happens for object types, not access types.  Read is no longer a dispatching primitive operation, which was the whole point to begin with!

And you can't call your language "object oriented" if, in a practical sense, you can only have dispatching on your procedures.  You can't just add named access values to the list of primitive operations either, since it would be a nightmare deciphering what overrides what (not to mention restrict you from using those fancy new general access types).  So as a kludge to the kludge, access parameters were added to the list of primitive operations, since they have the nice property of being able to accept any named type.  They said this was a "convenience" to avoid having to write '.all' at every place, since OOP is pointer heavy, but I don't buy that.  This was just a way to save face and avoid admitting fault vis a vis function out parameters.  But for whatever the reason, dispatching could now occur for access types, so long as the function was declared with an access parameter:

function Read (This : access File) return Byte;

Of course, this has nothing to do with the original intent of the access parameter (for accessibility control), and everything to do with getting yourself an 'out' parameter for a function.  If you peruse some Ada95-style "OOP" frameworks (e.g. GtkAda), you will find a rash of access parameters used in this manner.  Again, it's about dispatching, not accessibility.

But again, this created more problems than it solved.  Access parameters allowed you to get the needed dispatching, but you were also forced to take an anonymous access type.  Remember, all the same accessibility rules from the previous post still apply, whether you want them or not.  That means within the function body, there is no copying of the 'This' pointer to anywhere, or passing it to other subprograms.  It's imprisoned just the same, like it or not.

This puts you in a bind if you actually want to copy the pointer (and obviously do your work on the heap to avoid dangling references).  Suppose some fancy version of the Read subprogram is done so that if a certain byte-sequence is read, it inserts itself onto a linked list somewhere, or registers itself as an observer, or some other pattern that involves saving an access value.  Now you are stuck, since your Faustian bargain signed away any possibility of copying the object to gain dispatching ability.

So, as a kludge to the kludge to the kludge, Ada95 put in a loophole so that you could "get your type back" after being forced to cast it aside.for dispatching: access parameters can be typecasted.  Doing so, however, incurs a runtime check that raises an exception if the accessibility rules are violated (i.e. if the actual pointer in question was a local, and you saved it off somewhere longer lived).

The bottom line, however, is just don't do this.  There were only isolated cases in Ada95 and Ada2005 where this would have been necessary, and Ada2012 solved the problem once and for all by adding out parameters for functions.  There is no reason to ever try and cast an access parameter, and you are foolish to try.  If you need to copy a pointer, use a named access type (actually, you are even more foolish to to use a named access type anywhere in a public interface, but that's another post).  Moreover, there is no reason to even dispatch on an access value anymore, so start converting your OOP frameworks to use nothing but normal types.  In fact, as we will see later, Ada has evolved to a point where even the use of access parameters in the 'normal' sense (i.e. accessibility) is mostly antiquated.

So that's a lot of history about a feature that was bad to begin with and should never be used.  But it's just as important, if not moreso, to know how not to use the language; especially when it's confusing and misleading in the first place.

Thursday, March 14, 2013

Ada access types, part II


In the previous part, we took a look at the accessibility rules that Ada implements for access values, specifically local ones, to prevent the so-called 'dangling reference' problem.  The main point was that, for better or worse, these rules are mostly static and checked at compile-time.  The trade-off we all make for that particular benefit is that the rules are in many cases overly strict.  As mentioned several times, Ada restricts you from creating a pointer to any value that might potentially ever become a dangling reference, even if your code is written in a way where it does not.

As demonstrated earlier, the only way you can create a local access value is to organize your types so that the access type can't exist for longer than the object (i.e. if there is no longer-lived object to copy the value into, then there can be no dangling reference).  By declaring the access value as a shorter-lived type, we 'imprison' it locally to the scope in which it was created.

But this isn't the end of the story.  Imprisoning an access value within a procedure is often of very little value in a large, complex program.  More often is the case that we must pass the pointer out to another subprogram (usually one not under our control), which presents a catch-22.  Consider the following example:

package K

   type Int_Ptr is access all Integer;

   procedure Q (arg : Int_Ptr) is
   begin
      Ada.Text_IO.Put_Line(Integer'Image(arg.all));
   end Q:

   procedure P is
      x : aliased Integer := 42;
      x_ptr1 : Int_Ptr := x'access;  -- accessibility failure
      type Local_Ptr is access all Integer;
      x_ptr2 : Local_Ptr := x'access;  -- accessibility pass...
   begin
      Q(x_ptr2);  -- but type conversion failure
   end P;

end K;

Just like before, the accessibility rules are doing what they are designed to do.  Both our attempts to copy the pointer outside of P are thwarted for the rules we studied before.

Note in this case, however, that there is no dangling reference.  Nothing 'bad' happens inside Q.  There are no attempts to copy the parameter elsewhere, or any other sort of nefarious, dangling-reference creating behavior.  As written, this code is perfectly safe.

But alas, the pessimistic language rules prevent this from ever compiling.  Q is always at a higher scope than x, so we have no recourse apart from relocating Q inside P (which is rarely possible in production code), or perhaps redefining Q to take an Integer instead unnecessarily using a pointer (assume for example's sake that we cannot).

This is a shame, because there is nothing inherently wrong with this code.  The only problem is that the compiler can't "see into" Q to verify nothing sketchy is taking place, and therefore must assume the worst.  But what if a language construct existed to let Q 'promise' the access value would never be copied to a longer lived type during its execution?  The compiler could then allow P to pass x into Q, confident that no dangling references would be created inside.

To this end, alongside the accessibility rules and 'Access attribute, Ada95 introduced the idea of the anonymous access type.  Like the name suggests, instead of declaring a named type to use for the access value, the declaration is simply put 'in line' where the type would normally be.  For instance, we can rewrite Q from above as follows:

procedure Q (arg : access Integer) is  -- Access Parameter
begin
   Ada.Text_IO.Put_Line(Integer'Image(arg.all));
end Q:

Note the 'type' of arg is now of an anonymous nature, known as an access parameter.  Remember, though, an "access parameter" is a special construct with special rules, which is much different than a parameter of a named access type.

"Anonymizing" our parameter has several important consequences on our program.  Most importantly, we (as the programmers of Q) have essentially "stripped" the parameter of its type information.  Within the body of Q, we have no idea what the 'real' type of arg really is, only that it is of some type of access value that points to an Integer.

But remember, assignment in Ada requires that both types match.  By removing all the type information from our parameter, the object is now incompatible with all types!  If we try to copy arg into a global Int_Ptr, it will fail because, after all, we're not sure arg is an Int_Ptr.  If we try to pass arg as a (named) parameter to some other procedure, it will fail because they are not the same type.  In fact, there isn't a single place we can ever copy arg to, except another access parameter (which, of course, can't copy it anywhere except another access parameter, and so on).

In addition, we clearly don't want Q reassigning the parameter, either, since this would have the unpleasant effect of changing the underlying type of the actual named type to something else.  For this reason, access parameters are defined to be constant (in) parameters.

Now we have turned the tables.  Q can now accept any access value (provided it's to an integer), of any type, with any scope, heap and stack values alike.  P, on the other hand, can be confident no dangling references will exist, since Q will never know the actual type, and thus won't be able to copy it anywhere (this even includes even local values, which would obviously be safe, but that's the trade-off we have to make).


So to recap, access parameters are a mechanism that can be used to safely pass shorter-lived access values to longer-lived subprograms, by ensuring the subprogram can't copy it.  Given that, it's almost always preferably to use an access parameter in lieu of a named access type, assuming you don't need to copy the access value, since it allows your subprogram to work with both general and pool-specific access values.  Unfortunately, the aforementioned confusion, fear, uncertainty, and doubt about accessibility has created a environment where developers refuse to use access parameters, and then complain that the accessibility rules inhibit them.  Such is life, i suppose.

Of course, if you a glass-is-half-empty programmer, you might have noticed the example was deliberately convoluted.  After all, even the least experienced Ada programmer would know better than to use an access value of any kind for Q, and simply used an Integer.  One of the joys of working with Ada is that as mentioned in the first post, you don't have to use pointers for everything like in C.

The majority of the time access values are required, it's for complex data structures (linked lists, etc) which embed the access value within an enclosing record.  An access parameter will not help us with that, since the parameter is not actually an access value.  For instance, let's suppose our integer pointer is within a larger record containing some other meaningless data:

package K

  type Int_Ptr is access all Integer;

  type Big_Record is
    record
      e1 : boolean;
      e2 : Some_Enum;
      e3 : Int_Ptr;
    end record;

  procedure Q (arg : Big_Record ) is
  begin
     Ada.Text_IO.Put_Line(Integer'Image(arg.e3.all));
  end Q:

  procedure P is
     x : aliased Integer := 42;
     y : Big_Record := (foo, bar, x'Access); -- failure!
  begin
     Q(y); -- but type conversion failure
  end P;

end K;

So we have the same situation, except that x is enclosed within a bigger record.  Now we are in trouble, since we must have a type to declare our Big_Record, but such a type would always end up being of a higher scope, resisting any attempt at filling e3 with a local pointer.  This is still a shame, since Q continues to do nothing dubious with the pointer.

In keeping with the same philosophy of access parameters, what if there was a way to provide a kind of "anonymous record element"?  That solution worked for parameters, and might seemingly work for this situation too.  All we want to do is make sure that Q does not copy e3 out to somewhere and create a dangling pointer.

Unfortunately, things are much more complex for record types.  We don't just need to prevent the copying of e3, because copying the entire record type itself implies copying each element, including the pointer.  Similarly, it still needs to be constant so that we cannot change the type to something else, yet records can't have constant elements.

Luckily for us, both constructs already exist in Ada for doing both: limited, discriminated records.  Recall that a record type marked as limited cannot be assigned (or reassigned), which would prevent the copying of the entire record (and consequently the access value embedded inside).  And while Ada does not have the concept of 'constant' record elements, a record's discriminants do have this unique property.

And so along with access parameters, Ada95 also included access discriminants.  They are defined similarly to access parameters, except in a limited record declaration:

 type Big_Record (e3 : access Integer) is limited
   record
     e1 : boolean;
     e2 : Some_Enum;
   end record;

It should be noted at this point that qualifies as an 'abuse of notation' since the record is not, in the strict sense, discriminanted.  The discriminants are simply playing double-duty as normal elements that need to be constant.  Perhaps a better name would have been constant access elements, but i suppose it's a little late for could-of's.

In any case, access discriminants provide the same type of restrictions as access parameters, along with the same benefits.  We can now write our example using local access values, like so:

package K

 type Big_Record (e3 : access Integer) is limited
  record
    e1 : boolean;
    e2 : Some_Enum;
  end record;

 procedure Q (arg : Big_Record) is
 begin
    Ada.Text_IO.Put_Line(Integer'Image(arg.e3.all));
 end Q:

 procedure P is
    x : aliased Integer := 42;
    y : Big_Record := (x'Access, foo, bar); -- pass!
    Q(y);
 end P;

end K;

Just like access parameters, access discriminants give us the ability to pass around local access values to longer lived subprograms, while remaining certain no harm (in the form of dangling references) will come to them.  If Q tries to copy the pointer, it will fail because of the same 'anonymous' type failures as access parameters.  If it tries to copy or reassign the whole record, it will fail because of the limitedness. 

But there is one last problem:  Thus far we've assumed that we will be passing values into subprograms, which for access parameters was obviously true.  But an access discriminant is part of a declared object that can be equally as well passed out of a subprogram (i.e. as a return value), instead of going in.  This has obviously dire consequences, since the scope will be going the wrong way.  For instance

function F return Big_Record is
  x : alised Integer := 42;
begin
  return (x'Access, foo, bar);
end F;

is the textbook example of a dangling reference (x ceases to exist when the function returns, yet the return value has a reference to it).

Solving this conundrum requires one of the few runtime checks in the Ada accessibility rules (specifically mentioned in 6.5~21/3).  This code will compile and run, yet raise Program_Error upon executing.  Or, at least, it should: most versions of GNAT (and GNAT-based compilers) completely blow this discriminant check, and such buggy implementations produce code that actually does have dangling references.  Hopefully a supported GNAT Pro user will use their clout to get such problems fixed (they don't seem to listen to me).  Such is life.

So between access parameters and access discriminants, a programmer has the mechanisms to work safely with local access values while being statically (in most cases) guaranteed that pointers won't be left dangling.  The only situations where the preceding methods won't work are those in which you want to copy a pointer, which is, of course, the exact thing the rules are trying to prevent.  If you do need to copy a pointer somewhere longer-lived, you need to use a named access type (in actuality, you need to use a shared pointer, but that's a different post).

Yet all is not well.  Next time, we will see how a simple idea like access parameters got way, way out of hand.

Sunday, March 10, 2013

Ada access types, part I

On the surface, access values in Ada are pretty straightforward stuff:

A value of an access type provides indirect access to the object or subprogram it designates (LRM 3.10/1). 

All variables are stored in RAM and every byte of RAM has an address, so, like most languages, Ada lets us store the address of one variable in another variable.  This is colloquially known as a pointer, often the bane of many a collegiate freshman and professional programmer alike.  Pointers are notoriously prone to human error, given their multiple levels of indirection, low-level nature, and all-to-often habit of trying to address values in memory that no longer exist.

So back in 1983, Ada made it a point to avoid all this pointer-fussing business.  In fact, Ada83 didn't have pointers, as a C programmer might understand them.  C has the "address operator" (&), which can be liberally applied to any object or subprogram, which resolves into the address at which it is stored.  Ada, on the other hand, had been designed to eliminate many of these unnecessary uses of pointers, most notably arrays and pass-by-reference semantics.  Pointers were therefore seen as mostly superfluous, except for the case of heap allocations, which cannot be implemented any other way.  Consequently, the only way to create a pointer (or 'access value' in the Ada parlance) was through a heap allocation (i.e. 'new').

This also circumvented those thorny lifetime problems, also known as dangling pointers.  An object only exists for the time it is "in scope".  A variable local to a subprogram (or a nested package) is only valid for the time the subprogram is executing, after which said variable ceases to exist.  In the canonical example, a subprogram saves the address of a local variable into a global pointer, which is then accessed after the subprogram ends, with unpleasant results.  This is particularly hard to diagnose, since there is no indication anything is wrong until the object is overwritten, which may not even happen unless the conditions are right.  A heap allocation, on the other hand, is there forever and always, unless specifically deallocated (which, in Ada, is always unchecked).

But as well-intentioned as the 'no pointer' philosophy was, this was found to limiting.  Ada95 therefore added the ability to generate access values to locally declared objects via the 'Access attribute (and its counterpart, 'Unchecked_Access).  However, dangling pointers were of course still undesirable, and it was decided that rules should be in place to prevent these from occurring.

These rules, known as the accessibility rules, have arguably become the most complex, confusing, and contentious part of the entire language.  Very little literature even addresses them, and few programmers even try to understand them.  Many simply avoid the problem altogether by using the heap or the 'unchecked' mechanisms.  Frankly, this is a shame, because the rules are just not that complex.  There are many edge cases and esoteric constructs that can cause heap-spinning conundrums, but these are concerns for the ARG, and not the everyday programmer.  Learning about how and why the accessibility rules exist can make your programmers much safer, more functional, less reliant on the heap, and guaranteed free of dangling pointers.

Lifetime is inherently dynamic in nature, and the only 'true' way to prevent dangling references is via a runtime check at every dereference.  Such checks would be expensive both in size and speed, it is far more desirable to check for these statically, when compiling, rather than have to deal with an exception at runtime (which, as mentioned before, might not happen until the program is in the hands of the user).  To this end, Ada takes a perhaps surprising approach to accessibility: it doesn't prevent dangling pointers, it prevents the creation of pointers that might ever dangle.

This is the key point to understanding the accessibility rules and leveraging them in your programs, so re-read it a few times until it sinks in.  Ada prevents the creation of any pointer that might ever dangle, that even has the chance it might dangle at some point, even if it never does.  Consequently, programmers are often at odds with the compiler, which staunchly refuses to accept completely safe code.  We will have much more to say about the ramifications of this later, but for now let's look at how (and why) this works, and what we can do about it.

The crux of the problem is that once an access value is created, the compiler cannot be counted on to keep track of what happens to it.  We could, in theory, ensure that an access value of a local object is not assigned into a global variable, but things are much more complex than just that.  We might, for instance, assign the pointer to a local object (which is okay), but then try and assign that local object to a global object (which may or may not be okay).  Furthermore, we might use an access value as a parameter to another subprogram, which might already be compiled and could clearly not be checked at all.

So, the one and only chance a compiler has at preventing a dangling reference is at the point it is created: that is, when the 'Access attribute is applied to an object.  This is the one and only place the accessibility rules are checked (apart from a few special runtime cases discussed later).  After the access value is (successfully) created, no more checks are performed, and that pointer can be safely passed around anywhere.  How is it then, that a static check can gaurentee the behavior of the dynamic nature of lifetime?

The accessibility rules hinge on just one obvious programming rule: to declare an object of a certain type, you have to be able to 'see' that type.  Ada is block-structured, which means that code at a 'higher' level cannot use declarations at a 'lower level'.  For instance, a subprogram within a package can use types, objects, and subprograms declared in the enclosing package, but declarations within the package cannot access types, objects, and subprograms declared within its subprograms (they are 'local').  Since subprograms can be nested within subprograms, this extends all the up (and down) the hierarchy.  For instance:

procedure P1 is
 
   type T1 is ...;
   x : T1 := ...;
 
   procedure P2 is
      y : T1 := x  -- Legal!  Can see T and x outside in P1
      type T2 is ...;
      z : T2 := ...;
   begin
      null;
   end;
 
   Bad : T2 := z;  -- Error!  Cannot see T2 or Z inside of P2
 
begin
   null;
end;

Here we have declared a type T and an object x of type T locally within procedure P1, as well as another nested procedure P2.  Within procedure P2, we can access the type T1 and object x, and for that matter anything else declared up in P1.  However, we cannot "see into" P2 from P1 and use its local declarations.  This makes intuitive sense, since the declarations within P2 don't conceptually exist unless P2 is executing (and cease to exist after it ends).

This is the key to the accessibility rules.  Creating a dangling reference involves copying the pointer into an object that is longer lived (either directly with an assignment, or by passing it as a parameter to a subprogram that performs the assignment, which for our purposes is the same).  However, declaring that object obviously requires a type which must be visible.  Or, looking at it another way, if you can't see the type, you can't create the object.

This presents a nice, neat, logical trap: we can be absolutely certain that no objects of a given type exist outside of (i.e. higher than) the block where that type is defined.  Revisiting the example above, the only objects of type T2 that can ever possibly exist are within P2 itself, or else within other procedures that are themselves within P2.

So, applying this to access values, we know that there can never be dangling pointers so long as the type of the access value being created is not declared outside of where the object is declared.  There can be no object to copy the value into, since the declaration can not see the type!  In the language of the LRM, the level of the object cannot be deeper than that of the access type.  Let's see an example.

package K is
 
    type Int_Ptr is access all Integer;
    Global : Int_Ptr;
  
    procedure P is
       y : alaised Integer := 42;
       y_ptr : Int_Ptr := y'Access; -- accessibility check failure
    begin
       null;
    end P;
 
end K;

Applying the check to y'Access (which is, remember, the only place the check is applied), we see that the type of access value being created (Int_Ptr) is declared at a higher level than the object being accessed (y).  This means objects (or subprograms having parameters) of that type can exist outside the procedure P (such as Global), and that the resulting pointer might, at some point, somewhere, be copied into it.  Consequently, the creation of y_ptr is prohibited outright, even though in this particular case no dangling references are created.  Remember, accessibility checks prevent creation of pointers that might dangle, and not necessarily ones that do.

Now let's see a version that works.

package K is

    type Int_Ptr is access all Integer;
    Global : Int_Ptr;

    procedure P is
       y : alaised Integer := 42;
       type Local_Int_Ptr is access all Integer;  -- new, local type
       y_ptr : Local_Int_Ptr := y'Access; -- pass
    begin
       null;
    end P;

   Bad_Global : Local_Int_Ptr; -- error, no scope
   procedure Bad_P (arg : Local_Int_Ptr); -- error, no scope.

end K;

This is the same example, except instead of declaring y_ptr as the higher-scoped Int_Ptr, we've created a new local type, Local_Int_Ptr.  This time, when we apply the check, we find that the type (Local_Int_Ptr) is declared at the same level as the object (y).  The compiler knows there can be no Local_Int_Ptr's declared up in K somewhere, since those declarations would be invalid.  Trying to copy y_ptr into Global would fail, because they are, after all, two different types.  Similarly, trying to pass y_ptr as a Int_Ptr parameter (or casting it to an Int_Ptr) would fail because of incompatible types.  It's worth noting that these are regular old type check failures that have nothing to do with access values.

In fact, the only places we can ever have objects or subprogram parameters of Local_Int_Ptr is inside P (or within subprograms within P, or within subprograms within subprograms, etc).  Try as we might, the existing type system and scoping rules will prevent y_ptr from ever being copied out of P, which can therefore never dangle.  We can copy it around inside of P (and to any subprograms within P) as much as we like, and maintain certainty that it will never 'escape' out into K (or other subprograms directly within K).

So we can start to see the "assume-the-worst" philosophy that Ada takes towards access values.  If it's possible that an access value could ever be copied out to a longer-lived object, then you are forbidden from ever creating that access value.  The only way to work with access values to locally declared objects is to type them in such a way that ensures they can never be copied out of their local scope, no matter what.

And in many cases, this is all that's required: redeclare a new type (not a subtype) at the same level at which the objects are declared, which ensures that these access values can never escape.  Of course, things are rarely this simple; in the upcoming posts, we will see the various problems that occur with this mechanism, and the various ways in which we can work around them.