# Structs

Objects are fast and space-efficient, especially when they don't contain any `state` forms. If you have a small compound data structure (say, a geometric primitive, or the return value from a function), it's usually best to implement it as a class, rather than a table or an array.

``````(defclass Rect
(field x)
(field y)
(field w)
(field h)

(met init (@x @y @w @h))

(met area ()
(* @w @h))

(met op-clone ()
(Rect @x @y @w @h))

(met op-eq? (other)
(let [x y w h] other)
(and (== @x x) (== @y y) (== @w w) (== @h h))))
``````

That's a lot of code for something so simple! We provide the `defstruct` macro to get rid of the boilerplate:

``````(defstruct Rect
x y w h

(met area ()
(* @w @h)))

(prn Rect) ; prints #<class:Rect>
(let rect (Rect:new 10 10 20 20))
(prn rect) ; prints #<obj:Rect>
(prn [rect 'x]) ; prints 10
(prn (.area rect)) ; prints 400
``````

As demonstrated above, the `defstruct` macro defines a class with the given named fields. After the list of field names, `defstruct` also accepts zero or more `met`, `prop` and `const` clauses. Other class clauses, like `init`, `state`, `fsm`, `wrap` and `mixin`, are forbidden. Classmacros are also forbidden.

## Struct Initialization

When programming in Rust, you will have encountered tuple structs: structs which identify their fields by position, rather than by name.

``````
#![allow(unused_variables)]
fn main() {
struct Raster {
pixels: Vec<u32>,
width: u32,
height: u32,
format: ImageFormat
}

// vs.

struct Raster(Vec<u32>, u32, u32, ImageFormat);
}
``````

Tuple structs with more than one field are generally discouraged. They're hard to understand, hard to refactor, and they require more documentation. This is doubly true in a dynamically-typed language. For example, if you wanted to change the order of a tuple's fields in a Python codebase, the language would give you no help at all; it would be a completely manual task.

To encourage the use of named (rather than positional) struct fields, `defstruct` registers a global initialization macro which shares the struct's name. This macro behaves like a Rust struct initializer, or like the `tab` macro:

``````(defstruct Hit
hitbox
strength
element)

(let hitbox (Rect @x @y w h))

(let fire-punch (Hit
hitbox
(strength 35)
(element 'fire)))

(let ice-punch (Hit
(element 'ice)
..fire-punch))

; the Hit macro resembles a table constructor
(let fire-punch-table (tab
('hitbox hitbox)
('strength 35)
('element 'fire)))
``````

The original class is bound to the global `Name:new`. This enables you to easily opt in to using positional arguments, when you think they would be the better choice.

``````(defstruct Rgb r g b)

(let named (Rgb (r 32) (g 139) (b 32)))
(let positional (Rgb:new 32 139 32))

(prn (eq? named positional)) ; prints #t
``````

Finally, the global `Name?` is bound to a function which tests whether or not a value is a `Name` struct. This is more convenient and intuitive than writing `(is? val Name)` every time.

``````(let chartreuse (Rgb 0x7f 0xff 0x00))
(prn (Rgb? chartreuse)) ; prints #t
``````

The default behaviour of the `eq?` function, when comparing two objects, is to test them for identity using the `same?` function. This means that two objects can belong to the same type, and store the same values, but still compare unequal to one another.

You can override this default behaviour by defining a method named `op-eq?`.

As noted above, `op-eq?` is automatically implemented by the `defstruct` macro. It will compare each struct field in turn using `eq?`.

``````(defclass Spawner
(field level) ; a large, immutable table
(field to-spawn) ; a class
(field remaining) ; an integer counter

(met op-eq? (other)
(and
(same? @level [other 'level])
(same? @to-spawn [other 'to-spawn])
(== @remaining [other 'remaining]))))
``````

By default, the `clone` and `deep-clone` functions only duplicate a reference to an object; they don't copy the object's storage. You can provide `op-clone` and `op-deep-clone` methods to override this behaviour.