# `derive`, and where it runs (SPEC.md R-5, II.4). # # ./bin/iyi run samples/iyi/derive.iyi # # A derive runs once, in the module that declares the type. What it generates is # an ordinary declaration of that module, so it travels in the module's # `.iyimod` and a consumer calls the derived method without running the macro # and without this module's source. `bench/samples_roundtrip.sh` builds this # sample from its artifacts with `std/derives` deleted, which is that claim # taken literally. # # The macro is handed the facts R-5 lets it read: the declaration's name, and # the fields written in its body, each with the type it was written as. It # cannot enumerate a type it was not given, which is what keeps a derive # bounded and cacheable. module samples/derive import std/derives::* pub struct Point @x : Int32 @y : Int32 def initialize(@x : Int32, @y : Int32) end derive described end # One derive, two declarations. The macro reads each one's own fields, so # neither type had to be written down inside the macro. pub struct Span @from : Int32 @to : Int32 @label : String def initialize(@from : Int32, @to : Int32, @label : String) end derive described end puts Point.new(3, 4).described puts Span.new(1, 9, "range").described # The field type is a type, not a name, so a derive can ask what it implements. # `Name` and `Show` both belong to `std/derives`, and both arrive from that # module's artifact when its source is gone: `@label` is rendered through the # trait, and `@count` is skipped because `Int32` does not implement it. # # `getter label : Name` is a macro call, so the field it declares exists only # once it has run. A derive reads the declarations above it, which is why # `derive` goes last. Below `getter`, this would be an error rather than a # method generated over no fields. # # One line, two derives: each macro reads the same declaration and adds its own # methods. pub struct Tag getter label : Name @count : Int32 def initialize(@label : Name, @count : Int32) end derive described, shown end puts Tag.new(Name.new("hi"), 2).described puts Tag.new(Name.new("hi"), 2).shown # --------------------------------------------------------------------------- # What this sample would report as an error # --------------------------------------------------------------------------- # # * `derive missing` where nothing exports a `missing` macro. The error names # the macro and the declaration it would have to be written as, rather than # failing as an undefined method on the type's metaclass. # # * `derive` outside a class or struct body. There is no declaration to read, # so there is nothing for the macro to be handed. # # * A `derive` inside a reopened type from another module. That is refused # before the derive is reached, by R-3: iyi has no open classes, so a module # cannot add to a type it does not declare. # # * A `getter` (or any macro call that declares a field) written below the # `derive` that should read it. The derive names the call and says which way # to move, rather than generating a method over the fields it happened to see.
Press Run.
The pane no longer holds samples/iyi/derive.iyi. Run executes iyi-derive.wasm, the module the compiler built from the recorded file, so your edit is not in this run: there is no iyi compiler in this page. How this works
recorded with iyi 0.18.0 (built on Crystal 1.22.0-dev [2a9653052] (2026-10-18)) · Linux 7.0.0-38-generic x86_64, QEMU Virtual CPU version 2.5+ · 2026-10-19
You can type in the pane: nothing here compiles, so what you type cannot run, and the page says which bytes it is running the moment the text stops being the recorded file. There is no standard input, no filesystem and no threads on this target, and one sample fails on purpose for exactly that reason.
Run executes iyi-derive.wasm, the module the compiler built from this file, in your browser. How this works
Try it on your machine
Copy the program into derive.iyi, change something, and run it. Only a compiler on your own machine can run what you wrote.
$ iyi run derive.iyi