Skip to content

Structs ​

When using cl-defstruct, the :et slot property can be used to indicate the type of a struct's slots. Slots without an :et property are given the Any type.

emacs-lisp
(cl-defstruct my-person
  (name "" :et String)
  (age 0 :et Integer))

The type (Struct my-person) refers to instances of the my-person struct. The shorthand *my-person is equivalent, however the longer form may still need to be used for structs whose names contain characters which break et's parsing.

From a struct definition, the type-checker will generate definitions for the struct's constructor, predicate, copier, and accessors.

emacs-lisp
(make-my-person :name "John" :age 35)
;; >> *my-person

(my-person-name (:type *my-person))
;; >> String

(copy-my-person (:type *my-person))
;; >> *my-person

(let ((x (:type *my-person|Other)))
  (my-person-p x))
;; >> t{x : *my-person} | nil{x : Other}

For more information on the predicate return type, see Narrowing.

Generic structs ​

To make a struct type generic, add an :et-generics property to the first slot.

emacs-lisp
(cl-defstruct my-pair
  (first nil :et-generics [T] :et T)
  (second nil :et T))

The type (Struct my-pair Integer), or equivalently *my-pair<Integer>, represents a my-pair struct with T=Integer.
Generic constraints use the same syntax as aliases and polymorphic functions:

emacs-lisp
(cl-defstruct my-numeric-pair
  (first 0 :et-generics [(T <: Number)] :et T)
  (second 0 :et T))

Struct type arguments are invariant, since struct slots can be mutated. Thus, *box<Integer> is not a subtype of *box<Number>.