Structs are custom data types that group related values together. They’re fundamental to organizing data in Conjure and can contain fields of any type.
Defining Structs
Structs are declared using the struct keyword followed by a name and a block containing field definitions. Fields have a name and type, separated by whitespace.
struct Point2D {
x f32
y f32
}For simple structs, you can define fields on a single line separated by commas.
struct Point2D { x f32, y f32 }Creating Struct Instances
Create struct instances using construction syntax with field assignments.
var origin = Point2D{ x = 0.0, y = 0.0 }When the type is known from context (like a variable declaration with an explicit type), you can omit the struct name.
var origin Point2D = { x = 1.0, y = 0.0 }Zero Initialization
Structs can be zero-initialized using empty braces or by simply declaring a variable with the struct type.
var defaultPoint = Point2D{}
var anotherPoint Point2DBoth create a Point2D with x and y set to 0.0.
Accessing Fields
Access struct fields using dot notation.
var p = Point2D{ x = 5.0, y = 10.0 }
io.println("X: #{p.x}")
io.println("Y: #{p.y}")
p.x = 15.0 // modify fieldNested Structs
Structs can contain other structs as fields.
struct Point3D {
x f32
y f32
z f32
}
struct Line3D {
start Point3D
end Point3D
}
var line = Line3D{
start = Point3D{ x = 0.0, y = 0.0, z = 0.0 },
end = Point3D{ x = 1.0, y = 1.0, z = 1.0 },
}Anonymous Nested Structs
Structs can contain anonymous nested structs for organizing related data.
struct Node {
kind u8
data struct {
value i32
binary struct {
left i32
right i32
}
}
}Anonymous Unions in Structs
Structs can contain anonymous C-style unions for memory-efficient variant data.
struct Node {
kind u8
data union {
value i32
binary struct {
left i32
right i32
}
}
}Bitfields
Use the @bits attribute to create bitfields for compact data representation. This is useful for packing multiple small values into a single word.
struct PoolRef {
@bits(4) poolId u8
@bits(28) index u32
}Generic Structs
Structs support template parameters for generic programming. Template parameters are specified in square brackets after the struct name.
struct Pair[T1, T2] {
first T1
second T2
}
var intStringPair = Pair[i32, string]{ first = 42, second = "hello" }
var floatBoolPair = Pair[f32, bool]{ first = 3.14, second = true }Struct Attributes
@packed Proposed
The @packed attribute removes padding between fields, creating a tightly packed struct.
@packed struct PackedData {
a u8
b u32
c u8
}@align Proposed
The @align attribute specifies custom alignment for a struct.
@align(16) struct AlignedData {
values [4]f32
}Passing Structs to Functions
Structs are passed by value, meaning the function receives a copy. For large structs, use pointers to avoid expensive copies.
// Passed by value (copy)
func movePoint(p Point2D, dx f32, dy f32) Point2D {
return Point2D{ x = p.x + dx, y = p.y + dy }
}
// Passed by pointer (no copy)
func movePointInPlace(p *Point2D, dx f32, dy f32) {
p.x = p.x + dx
p.y = p.y + dy
}Returning Structs
Functions can return structs directly.
func createPoint(x f32, y f32) Point2D {
return Point2D{ x = x, y = y }
}
var p = createPoint(10.0, 20.0)Struct Methods
Methods can be defined inside struct bodies. Fields must be declared before methods.
Instance Methods
Instance methods use self (value receiver) or *self (pointer receiver) as the first parameter.
struct Point2D {
x f32
y f32
func length(self) f32 {
return math.sqrt(self.x * self.x + self.y * self.y)
}
func scale(*self, factor f32) {
self.x *= factor
self.y *= factor
}
}
var point = Point2D{ x = 3.0, y = 4.0 }
var len = point.length() // value receiver: passes by value
point.scale(2.0) // pointer receiver: passes by referenceMethods with self receive a copy of the struct (read-only). Methods with *self receive a pointer and can modify the struct. Methods do not have implicit access to fields - use self.field to access them.
Static Methods
Static methods are defined without a self or *self parameter. They are called on the type itself rather than on instances, making them useful for factory functions, utility methods, or namespace organization.
struct Point2D {
x f32
y f32
// Static factory method
func origin() Point2D {
return Point2D{ x = 0.0, y = 0.0 }
}
// Static factory method with parameters
func fromAngle(angle f32, distance f32) Point2D {
return Point2D{
x = math.cos(angle) * distance,
y = math.sin(angle) * distance,
}
}
// Static utility method
func distance(a Point2D, b Point2D) f32 {
var dx = b.x - a.x
var dy = b.y - a.y
return math.sqrt(dx * dx + dy * dy)
}
}
var origin = Point2D.origin()
var point = Point2D.fromAngle(0.5, 10.0)
var dist = Point2D.distance(origin, point)Static methods are namespaced under the struct type, helping organize related functionality without polluting the global namespace.
Structs with Arrays
Structs can contain fixed-size arrays.
struct Matrix3x3 {
data [9]f32
}
struct Vertex {
position [3]f32
normal [3]f32
texcoord [2]f32
}Memory Layout Proposed
Structs are laid out in memory with fields in declaration order. The compiler may add padding between fields for alignment unless @packed is used.
struct Example {
a u8 // 1 byte
// 3 bytes padding (for alignment)
b i32 // 4 bytes
c u8 // 1 byte
// 3 bytes padding
}
// Total size: 12 bytes
@packed struct PackedExample {
a u8 // 1 byte
b i32 // 4 bytes
c u8 // 1 byte
}
// Total size: 6 bytes