Skip to content

LISP - Structures

Structures are user-defined, composite data types that group related data into a single object. They are the simplest and often most performant way to create record-like data in Common Lisp, ideal for when the full power of the Common Lisp Object System (CLOS) is not required.

The defstruct macro is used to define a new structure type. When you define a structure, Lisp automatically generates a set of useful functions for creating and interacting with instances of that structure.

Let’s define a book structure to track library books:

(defstruct book
title
author
subject
(book-id -1) ; Providing a default value
(available-p t))
  • Constructor (make-book): A function to create new instances of the structure.
  • Type Predicate (book-p): A function that returns T if its argument is a book structure, and NIL otherwise.
  • Accessors (book-title, book-author, etc.): Functions to retrieve the value of a specific slot from a structure instance.
  • Setters: The accessors are setf-able, allowing you to modify slot values.
  • Copier (copy-book): A function to create a (shallow) copy of a structure instance.

Working with Structures: A Complete Example

Section titled “Working with Structures: A Complete Example”

This example demonstrates the entire lifecycle: creating, accessing, modifying, and checking the type of a structure instance. We use let to keep our variables local.

;; Define the book structure (should be at the top level of your file)
(defstruct book title author subject (book-id -1) (available-p t))
(defun run-book-demo ()
(let ((book1 (make-book :title "The Pragmatic Programmer"
:author "Andy Hunt & Dave Thomas"
:subject "Software Engineering"
:book-id 101)))
;; 1. Print the created structure
(format t "Created Book:~% ~s~%~%" book1)
;; 2. Access slot values
(format t "Title: ~s~%" (book-title book1))
(format t "Is available? ~a~%~%" (book-available-p book1))
;; 3. Modify a slot value using SETF
(format t "Checking out the book...~%")
(setf (book-available-p book1) nil)
(format t "Is available now? ~a~%~%" (book-available-p book1))
;; 4. Use the type predicate
(format t "Is book1 a 'book' type? ~a~%" (book-p book1))
(format t "Is the string \"hello\" a 'book' type? ~a~%" (book-p "hello"))))
;; Run the demonstration function
(run-book-demo)
Created Book:
#S(BOOK :TITLE "The Pragmatic Programmer" :AUTHOR "Andy Hunt & Dave Thomas" :SUBJECT "Software Engineering" :BOOK-ID 101 :AVAILABLE-P T)
Title: "The Pragmatic Programmer"
Is available? T
Checking out the book...
Is available now? NIL
Is book1 a 'book' type? T
Is the string "hello" a 'book' type? NIL

defstruct provides options to customize its behavior, making it highly flexible.

  • :conc-name: Controls the prefix for accessor names. (:conc-name book-) is the default. Setting it to nil creates accessors with no prefix (e.g., title instead of book-title).
  • :constructor: Customizes the constructor name, e.g., (:constructor create-book). If nil, no constructor is created.
  • :include: Allows a structure to inherit slots from another. This is a simple form of inheritance.
  • :type: Specifies the underlying representation. (:type vector) creates a structure represented by a vector, which can be faster. (:type list) uses a list.
;; Base structure
(defstruct vehicle (num-wheels 4))
;; Car inherits from vehicle
(defstruct (car (:include vehicle) (:conc-name car-))
make
model)
(let ((my-car (make-car :num-wheels 4 :make "Honda" :model "Civic")))
(format t "My car: ~a, Make: ~a, Wheels: ~a"
my-car
(car-make my-car) ; Accessor from car
(vehicle-num-wheels my-car) ; Accessor from included vehicle
))

When should you use defstruct versus defclass?

  • Use defstruct for: Simple data containers, performance-critical code where you need packed data, and when you don’t need features like multiple inheritance or method specialization.
  • Use defclass for: Complex object models, systems requiring multiple inheritance, dynamic behavior with generic functions (multimethods), and introspection via the Metaobject Protocol (MOP).