Structs
A struct groups named fields into one value.
Declaring a struct
struct Name {
field: Type,
pub field: Type,
pub field: Type = default,
}
- Fields are separated by commas or newlines; a trailing comma is fine.
pubmakes a field readable from other files, and visible to@for. See Struct fields.= defaultgives a value used when a construction leaves the field out.pub structlets other files import the struct itself.- Facets such as
invariantgo between the name and the{.
Constructing a struct
There are three ways to build a struct value:
struct Point {
pub x: int,
pub y: int = 5,
}
macro emit_point(p: Point) {
@emit p
}
emit_point Point(1, 2) # positional
emit_point Point(x = 3) # named; y uses its default
emit_point Point { x: 4, y: 6 } # braces
Point { x: 1, y: 2 }
Point { x: 3, y: 5 }
Point { x: 4, y: 6 }
Every field without a default must be given a value:
struct Point {
pub x: int,
pub y: int,
}
macro emit_point(p: Point) {
@emit p
}
emit_point Point(1)
`Point` expects 2 argument(s), but 1 were supplied
A generic struct is constructed with braces:
Pair<int> { a: 1, b: 2 }.
Reading fields
Use .field:
struct Point {
pub x: int,
pub y: int,
}
macro show(value: int) {
@emit value
}
const p = Point(3, 4)
show p.x * p.x + p.y * p.y
25
Values are never modified. To “change” a field, build a new struct.
Structs as machine code
When an evaluator like bitter packs a struct, it concatenates its fields,
first field in the most significant bits. That’s how an instruction format is
described. Here’s RISC-V’s R-type format from std.riscv.impl:
pub struct RType {
funct7: Funct7, # bits<7>
rs2: Reg, # bits<5>
rs1: Reg, # bits<5>
funct3: Funct3, # bits<3>
rd: Reg, # bits<5>
opcode: Opcode, # bits<7>
}
A 32-bit instruction is just a struct whose fields add up to 32 bits. See
Packing bytes with bitter.
Generating fields
A struct’s fields can be generated with @for and
@if, using the struct’s generic parameters. This is how
std.array declares an array of any length:
pub struct Array<T, const N: int>
| invariant N >= 0
{
@for i in 0..N {
pub __el`i`: T,
}
pub skip len: int = N,
}
macro show_all<const N: int>(values: Array<int, N>) {
@for v in values {
@emit v
}
}
show_all Array<int, 3> { __el0: 7, __el1: 8, __el2: 9 }
7 8 9
A construction can generate its field values the same way:
from std.array import Array
macro squares<const N: int>(_len: Array<int, N>) -> Array<int, N> {
@return Array<int, N> {
@for i in 0..N {
__el`i`: i * i,
}
}
}
macro show_all<const N: int>(values: Array<int, N>) {
@for v in values {
@emit v
}
}
show_all squares(Array<int, 4> { __el0: 0, __el1: 0, __el2: 0, __el3: 0 })
0 1 4 9
__el`i` builds the field names __el0, __el1, … See
Spliced names.