Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

Software Contracts, Part 7: Contracts as annotations - language features used to express contracts

Software Contracts, Part 7: Contracts as annotations - language features used to express contracts

  • Comments 14

My last post on contracts introduced the idea that a languages type system can be used as a mechanism to expose the contract of a function.  It turns out that language designers started recognising this fact and they started adding annotations to types that allow the contract for functions to be expressed.

As is to be expected, originally these annotations were relatively limited in scope - for instance, the C language added a concept of "type qualifier" when it was standardized (K&R had no such concept).  They only added a couple of simple qualifiers - const and volatile.  When a parameter to a function was declared to be "const", it meant that the function's contract included a guarantee not to modify the parameter [1]. 

Of course, sometimes it was rather hard to decypher the difference between "const char *" and "char *const" (the first is a pointer to a constant character, the second is a constant pointer to a character), but the intent was there.  The C language specification treated the type qualifier mostly as a hint - it didn't do a particularly good job of enforcing them.

 

For C++, the language designers went further than in C. They tightened up the semantics for "const" adding stricter type checks.  And they added new parameter type, a "reference" parameter.  When a function took a reference to a variable as a parameter, it essentially says "the contract for this function includes the ability to modify the contents of this variable".

 

 

Next:  Contract Annotations enforced outside the compiler.

[1] Yeah, I know about CreateProcess.

Edit: Ok, I screwed up the definitions of const char * and char * const.

  • I think that "const char *" means a pointer to a constant character, and "char *const" means a constant pointer to a character, just the opposite of what you said.

  • "Of course, sometimes it was rather hard to decypher the difference between "const char *" and "char *const" (the first is a constant pointer to a character, the second is a pointer to a constant character), but the intent was there."

    I believe you've gotten those backwards.  A "const char *" is equivalent to a "char const *", which is a pointer to constant characters -- ie. you can change which set of characters the pointer points to but you can't change the characters themselves.  Whereas a "char * const" is a constant pointer to non-constant characters, meaning that you can change the characters as much as you like but you can't make the pointer point at a different set of them.

  • const char* is a pointer to a constant character.  char* const is a constant pointer to a character.  I think your description got it backwards.  I guess that kind of proves your point, though...

  • > When a parameter to a function was declared to be "const",

    > it meant that the function's contract included a guarantee

    That was more or less the intent but not the actual meaning.  See below.

    > The C language specification treated the type qualifier mostly

    > as a hint - it didn't do a particularly good job of enforcing

    > them.

    The C language specification essentially doesn't enforce anything.  In some cases it requires that at least one diagnostic be emitted during translation, but it doesn't require that diagnostic to be distinguishable from one that could be emitted while properly translating a perfectly valid program.  It never requires translation to abort -- an implementation is always allowed to finish translating and executing (in some way or other) a program even if it has issued a warning.

    What the C language specification essentially does do is entirely avoid defining any meaning for programs that violate various conditions of the standard.  This is the infamous "undefined behaviour".  A perfect example is modification of an object which was defined as const.  Such modification can be attempted by casting a const pointer to non-const and assigning to the resulting lvalue, for which the C standard says that the C standard says nothing at all about the effect of the entire program.  Undefined behaviour.  No guarantees.

    If you want enforcement, C isn't the place to get it.

  • > Of course, sometimes it was rather hard to decypher the difference

    > between "const char *" and "char *const" (the first is a constant

    > pointer to a character, the second is a pointer to a constant

    > character), but the intent was there.

    It's actually the other way around, const char * is a pointer to a constant character and char * const is a constant pointer to a character.  

    I find it easier just to write them both in the same manner to avoid that kind of confusion, so char const * and char * const, then you can just read it from right to left.

  • I'm sure you meant that const char* means pointer to a constant character. The idea is that the const keyword is written before the thing that is constant...

  • One of the most-useful annotations is just starting to come into the mainstream languages: the "notnull" property (of a pointer). C++ "References" are implicitly "not null", i.e. a reference is guaranteed to refer to "something" (except by casting? my memory is hazy), i.e. it is a non-nullable pointer.

    The introduction of annotations should be guided by bug frequency statistics: In malloc/free-languages, more than 50% of bugs tend to be allocation-related; in managed-memory languages (gc/refcount), most bugs tend to be nullpointer dereferences. Of course, the hardest-to-find bugs are parallelism/synchronization bugs, so attributes like "is-reentrant" or "is-multithreaded" would be quite nice.

  • Software contracts seem like an important issue for programming in the large, so why isn't it more common? Pascal had the ability to restrict integer ranges with a declaration like 0..400, but I haven't seen that in other languages. Java tried to address the declaration of thrown exceptions although programmers generally hate it.

    Has C# made any progress in this area?

  • Eiffel took a stronger approach to contracts.  I think the fact that many programmers find extensive, explicit contract specification to be cumbersome, as well as managers finding it to be expensive due to the additional time spent defining contracts, might partially explain why some of those design principles aren't more mainstream.

    Perhaps that's why the languages that mandate particularly strong, explicit contracts aren't quite as mainstream as those that allow devs to do an end-run around the system?

    I'm also interested in the apparent phenomenon of a preference for implicit contracts in languages in academia.  I can see the attraction of apparent elegance within code, to be sure.  I'm not sure I can see that being effective in a real world industrial situation when dealing with code written in 5 different parts of the world by 30 different people with not-a-shred of documentation to be found.

  • Modern fortran includes function-level qualifier "pure" meaning the function has no side effects.  As a result, the compiler can do them in any order (or in parallel).

    Pretty cool!

    Link: http://www.ncsa.uiuc.edu/UserInfo/Resources/Hardware/IBMp690/IBM/usr/share/man/info/en_US/xlf/html/lr82.HTM#HDRPURE

  • Ok, it's taken 7 other posts, but we've finally gotten close to where I wanted to be when I started this

  • Thursday, January 25, 2007 9:31 AM by Dave

    > Pascal had the ability to restrict integer ranges with a

    > declaration like 0..400

    Exactly.  Enforcement was essentially optional -- compilers were required to provide a mode in which enforcement yielded diagnostics (which were allowed to be warnings, i.e. execution was allowed to continue).  This mode did not have to be the default (i.e. execution could continue without warnings, proceeding to the same kind of debugging issues as in C).

    Thursday, January 25, 2007 9:55 AM by Greg

    > I think the fact that many programmers find extensive,

    > explicit contract specification to be cumbersome, as well as

    > managers finding it to be expensive

    Exactly.  The frequency of bugs in developing contract specifications is at least as high as in later phases of development.  The problem is that there's no budget for fixing bugs early.  You can obtain a budget for fixing (or not fixing) bugs late by waiting for customers to find the bugs.  And since it's 50 times as expensive to fix bugs late, you can get 50 times the budget, and provide job security to the managers.

  • > I think the fact that many programmers find extensive,

    > explicit contract specification to be cumbersome, as well as

    > managers finding it to be expensive

    This is a circular argument. As long as mainstream languages and development tools don't support embedded contract terms, it will be cumbersome and expensive. Trying to use Javascript as a strongly typed language using comments would be cumbersome and expensive, not to mention futile.

  • > This is a circular argument. As long as mainstream languages and

    > development tools don't support embedded contract terms, it will

    > be cumbersome and expensive.

    Do you really think so?  

    I'd propose that it isn't the act of dictating/maintaining a known contract that's really cumbersome.  It's determining what that contract is in the first place.  I've found it to be an activity that's pretty front-heavy, and I don't have to be my manager to know that it's hard to get a budget and schedule that's properly weighted for design when all that money's being dumped into the COPQ portion of the budget to fix problems in the field.  

    (Because the last set of products wasn't really designed either.)

Page 1 of 1 (14 items)