Structures offer a way to store data in named slots. They support single inheritance.

Classes provided by the Common Lisp Object System (CLOS) are more flexible however structures may offer better performance (see for example the SBCL manual).

As usual, this is best read in the Common Lisp Cookbook.

Structures

Creation

defstruct

(defstruct person
   id name age)

At creation slots are optional and default to nil.

To set a default value:

(defstruct person
   id
   (name "john doe")
   age)

Also specify the type after the default value:

(defstruct person
  id
  (name "john doe" :type string)
  age)

We create an instance with the generated constructor make- + <structure-name>, so make-person:

(defparameter *me* (make-person))
*me*
#S(PERSON :ID NIL :NAME "john doe" :AGE NIL)

note that printed representations can be read back by the reader.

With a bad name type:

(defparameter *bad-name* (make-person :name 123))
Invalid initialization argument:
  :NAME
in call for class #<STRUCTURE-CLASS PERSON>.
   [Condition of type SB-PCL::INITARG-ERROR]

We can set the structure’s constructor so as to create the structure without using keyword arguments, which can be more convenient sometimes. We give it a name and the order of the arguments:

(defstruct (person (:constructor create-person (id name age)))
     id
     name
     age)

Our new constructor is create-person:

(create-person 1 "me" 7)
#S(PERSON :ID 1 :NAME "me" :AGE 7)

However, the default make-person does not work any more:

(make-person :name "me")
;; debugger:
obsolete structure error for a structure of type PERSON
[Condition of type SB-PCL::OBSOLETE-STRUCTURE]

Slot access

We access the slots with accessors created by <name-of-the-struct>- + slot-name:

(person-name *me*)
;; "john doe"

we then also have person-age and person-id.

Setting

Slots are setf-able:

(setf (person-name *me*) "Cookbook author")
(person-name *me*)
;; "Cookbook author"

Predicate

(person-p *me*)
T

Single inheritance

With the :include <struct> argument:

(defstruct (female (:include person))
     (gender "female" :type string))
(make-female :name "Lilie")
;; #S(FEMALE :ID NIL :NAME "Lilie" :AGE NIL :GENDER "female")

Limitations

After a change, instances are not updated.

If we try to add a slot (email below), we have the choice to lose all instances, or to continue using the new definition of person. But the effects of redefining a structure are undefined by the standard, so it is best to re-compile and re-run the changed code.

(defstruct person
       id
       (name "john doe" :type string)
       age
       email)

attempt to redefine the STRUCTURE-OBJECT class PERSON
incompatibly with the current definition
   [Condition of type SIMPLE-ERROR]

Restarts:
 0: [CONTINUE] Use the new definition of PERSON, invalidating already-loaded code and instances.
 1: [RECKLESSLY-CONTINUE] Use the new definition of PERSON as if it were compatible, allowing old accessors to use new instances and allowing new accessors to use old instances.
 2: [CLOBBER-IT] (deprecated synonym for RECKLESSLY-CONTINUE)
 3: [RETRY] Retry SLIME REPL evaluation request.
 4: [*ABORT] Return to SLIME's top level.
 5: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1002A0FFA3}>)

If we choose restart 0, to use the new definition, we lose access to *me*:

*me*
obsolete structure error for a structure of type PERSON
   [Condition of type SB-PCL::OBSOLETE-STRUCTURE]

There is also very little introspection. Portable Common Lisp does not define ways of finding out defined super/sub-structures nor what slots a structure has.

The Common Lisp Object System (which came after into the language) doesn’t have such limitations. See the CLOS section.