Cleanup Issue DEFSTRUCT-REDEFINITION

Status
Passed (as amended) Jan 89 X3J13
Category
CLARIFICATION
References
DEFSTRUCT (CLtL pp 305-320)
Related issues
DEFSTRUCT-ACCESS-FUNCTIONS-INLINE

Problem Description

The case of a structure type being redefined is not discussed in CLtL. Is it legal to redefine a DEFSTRUCT? What happens to DEFSTRUCTS that :INCLUDE the one defined. What things might be "wired in" in compiled code that refered to the previous DEFSTRUCT?

Proposal

The results of redefining a DEFSTRUCT structure are undefined.

Rationale

DEFSTRUCT is intended as "the most efficient" structure class. DEFCLASS allows much more flexible structures to be defined. Thus, implementations should be free to "wire in" much of the behavior of a DEFSTRUCT into compiled code.

The issue of redefinition should be addressed since there are always consequences that affect use of the structures.

Current Practice

None of KCL, Lucid, & Symbolics detect a redefinition.

Envos Medley goes to some effort to detect if a new structure is "compatible" with the old -- e.g., slots might change names, initial values, but, since the space allocated in an instance is determined by the :TYPE, an incompatible set of :TYPE forms would cause old instances to be marked "obsolete". (The TYPE-OF an old instance changes to **OBSOLETE**, for example.)

Cost to Implementors

This proposal attempts to be consistent with current practice.

Cost to Users

It is doubtful that any current programs actually define structures more than once. Thus, constraints on DEFSTRUCT redefinition primarily affect the debugging environment.

Cost of Non-Adoption

Confusion.

Benefits

Clarity.

Aethetics:

Something that is not well-defined and leads to erratic behavior should be explicitly considered an error.

Discussion

Common implementation techniques may cause the following behavior if a DEFSTRUCT is redefined:

If the new DEFSTRUCT is identical to the old DEFSTRUCT except for the initialization forms for slots, previous structure objects probably can continue to be accessed with previously compiled slot accessors. DEFSTRUCT constructor, test functions are proclaimed INLINE, and if these have changed, previously compiled occurrences of them may behave unpredictably.

If any change is made to the definiton of the slots (either in number, name, or :TYPE), attempting to execute a slot accessor of the old definition may behave unpredictably: if a slot name of the old definition also names a slot of the new definition, any "compiled" code might use the old definition instead.

DEFSTRUCT constructor, test functions may also be proclaimed INLINE, and may behave unpredictably if previously compiled. In particular, a compiled occurance of a constructor might have the previously slot initial values "wired in".

If the new DEFSTRUCT differs from the old in any aspect other than the initialization forms for slots, the results of attempting to access any old instance might result in unspecified behavior. For example, if the size of the structure became considerably shorter, an old accessor might "access off the end" of an instance of a new object; it might signal an error or have other unpredictable results.

Masinter supports this proposal. If users want more flexibility than DEFSTRUCT allows, they should use DEFCLASS.

Some felt strongly that just saying it's an error to redefine a structure but not requiring the error to be signalled will cause users to be confused by the differing seemingly erratic behavior and code.

Programming environments are allowed, encouraged, etc. to allow such redefinition, perhaps with warning messages. It is beyond the scope of the language standard to define those interactions, except to note that they are not portable.

Here's an example where reexecuting an EQUAL DEFSTRUCT might result in different behavior:

(defvar *token-counter* 0) (defstruct token (cookie '("unique-string")) (counter (incf *token-counter*)))

(defvar *first-token* (make-token))

(eql (token-cookie *first-token*) (token-cookie (make-token))) => true

(defstruct token (cookie '("unique-string")) (counter (incf *token-counter*)))

(eql (token-cookie *first-token*) (token-cookie (make-token))) => false

I.e., even though the second DEFSTRUCT is EQUAL to the first, the structures are not EQL.

This is related to the compiler issue QUOTE-MAY-COPY, but is not the same issue, since that proposal isn't proposing that QUOTE might copy its value *every time* it is executed.

Edit History