data-structurescommon-lispsbclclozure-cl

Why do crosslinked defstructs cause stack overflows?


While playing around with graphs, I got a curious error I didn't quite understand. The code below reproduces the problem.

;; Define struct to store a node with links to other nodes.
(defstruct node properties links)

;; Make two nodes
(setf a (make-node :properties '(:name a))
      b (make-node :properties '(:name b)))

;; Create link from b to a. This works fine...
(push b (node-links a))

;; ... but this crosslink makes lisp chase its own tail for a while and then crash with a stack overflow.
(push a (node-links b))

I got the same result with SBCL and Clozure. Setting *print-length* to a manageable value did not work.

So my questions is: Why doesn't this code create the same kind of infinite printing loop as a circular list (i.e., no stack overflow and stoppable with Ctrl-C). Any input is appreciated.

Thanks, Paulo


Solution

  • *print-length* controls the number of elements in a list. You're looking for *print-level*. This works fine for me.

    (let ((*print-level* 3))
      (format t "~W~%" a))
    ;; Output: #S(NODE :PROPERTIES (:NAME A)
    ;;  :LINKS (#S(NODE :PROPERTIES # :LINKS #)))
    

    Alternatively, you could use *print-circle* to detect cycles and print them in an even nicer way.

    (let ((*print-circle* t))
      (format t "~W~%" a))
    ;; Output: #1=#S(NODE :PROPERTIES (:NAME A)
    ;;  :LINKS (#S(NODE :PROPERTIES (:NAME B) :LINKS (#1#))))
    

    Here, it actually detects the cycle and prints #1#, a reference to the #1= to show that it's the same object.