(rontolisp) docs
← Special Forms

defclass

(defclass name (superclass*) ((slot slot-option...) ...) class-option...)

Defines a class and returns the name symbol. This is a static CLOS subset: every superclass must be defined by an earlier defclass, and instances are first-class objects created with make-instance (like defstruct instances they are not lists — consp is nil — and print shows them as #<NAME :SLOT value ...>). Slot options:

  • :initarg keyword — the constructor keyword for the slot (defaults to the slot-name keyword)
  • :initform expr — the default value, evaluated at construction time when the slot is not supplied. Omitting it leaves the slot UNBOUND, as in CL: slot-boundp is nil and a read signals unbound-slot
  • :reader fn — defines fn as a reader function
  • :accessor fn — like :reader, and additionally a setf-able place

A subclass inherits every slot of its superclasses and its instances match the superclasses' defmethod class specializers. Multiple inheritance is supported: the first superclass's slots come first and each later superclass appends the slots whose name is not present yet, a diamond keeps ONE copy of the shared slot, and both method dispatch and re-declared slot options follow the class precedence list (CLHS 4.3.5 -- the class, then its superclasses left to right, each before its own superclasses; conflicting local orders are an error). A subclass may re-declare an inherited slot: the storage stays the one inherited slot, while the subclass's :initform/:initarg override the inherited ones and its readers/accessors are added to them. Reader/accessor functions are ordinary defuns, so they are first-class. The class options (:documentation "...") (accepted and ignored) and (:default-initargs :initarg value ...) (defaults applied by make-instance for initargs not supplied) are supported. Beyond :reader/:accessor, the slot options :type (recorded, no checking), :writer (a symbol names a two-argument new-value-first generic, (setf name) the setf function of name) and :allocation are supported: :allocation :class stores the slot's value in ONE cell shared by the declaring class, its instances and its subclasses — every access path (accessors, slot-value, a make-instance initarg) reads and writes the shared cell, the :initform is evaluated once at class-definition time, and a subclass re-declaring the slot with :allocation :class gets a cell of its own (CLHS 7.5.3). Without a :metaclass (below), other class options and other slot options are errors. On the compilation path defclass is only supported as a top-level form; find-class and class-of answer the class metaobject, and of the runtime class operations only change-class exists (the target may be computed). Evaluating a defclass for an existing name updates the definition — a :metaclass class re-runs the class-definition protocol through reinitialize-instance on the SAME metaobject (see below) — but existing instances are not updated (update-instance-for-redefined-class does not exist), and on the compilation path the statically compiled parts (slot layout, constructors, accessors) always follow the LAST definition.

Metaclasses (the static MOP subset): the class option (:metaclass M) names a class inheriting standard-class, defined earlier by defclass. The class definition then runs the class-definition protocol at definition time: the metaclass is instantiated — its shared-initialize methods see every other unknown class option as an initarg whose value is the option's tail list, and a :before method's write to a slot with a declared :initarg is overwritten by the supplied initarg afterwards, CL's fill order (a slot without a declared :initarg, like table-name below, keeps the :before's write) — each slot's non-standard options (:col-type, ...) are handed to closer-mop:direct-slot-definition-class as initargs and its answer is instantiated as that slot's direct-slot-definition metaobject, effective slots are computed through closer-mop:compute-effective-slot-definition (the default method picks and instantiates closer-mop:effective-slot-definition-class inside the dynamic extent of a user override's call-next-method), and closer-mop:finalize-inheritance runs eagerly (CL finalizes lazily; inputs are static, so only the timing of definition errors differs). find-class and class-of answer the metaclass instance from then on, while instances of the class itself stay ordinary objects. The definition routes through closer-mop:ensure-class-using-class — its user :around methods dispatch on the EXISTING class metaobject, so they fire when the same name is defined again (the first definition passes nil), and the redefinition takes the reinitialize-instance path on the same metaobject. Initialization runs through the ordinary generic chain, so user initialize-instance/reinitialize-instance :around methods on the metaclass (or on a slot-definition class) may rewrite the initargs — :direct-superclasses and :direct-slots included — before (apply #'call-next-method ...), as mito's table classes do. Slot-definition metaobjects also carry closer-mop:slot-definition-initfunction (a zero-argument function evaluating the :initform; nil when the slot has none). The protocol is static: a defclass in a non-top-level position, or protocol calls on classes unknown at definition time, signal an error.