5.1.32. Operator Overloading
This tutorial covers operator overloading in daslang — defining custom behavior for built-in operators when applied to your own types.
daslang operators are regular functions named def operator <op> and follow
standard overload‑resolution rules. They can be declared as free functions or
as struct methods.
5.1.32.1. Arithmetic operators
Overload +, -, *, /, % to give your types arithmetic
behaviour. Each operator is a binary function that takes two values and returns
a result:
struct Vec2 {
x : float
y : float
}
def operator +(a, b : Vec2) : Vec2 {
return Vec2(x = a.x + b.x, y = a.y + b.y)
}
def operator -(a, b : Vec2) : Vec2 {
return Vec2(x = a.x - b.x, y = a.y - b.y)
}
def operator *(a : Vec2; s : float) : Vec2 {
return Vec2(x = a.x * s, y = a.y * s)
}
def operator *(s : float; a : Vec2) : Vec2 {
return a * s
}
def operator /(a : Vec2; s : float) : Vec2 {
return Vec2(x = a.x / s, y = a.y / s)
}
Usage:
let a = Vec2(x = 1.0, y = 2.0)
let b = Vec2(x = 3.0, y = 4.0)
print("{(a + b).x}, {(a + b).y}\n") // 4, 6
print("{(a * 3.0).x}\n") // 3
Note that operator * is defined twice — once for Vec2 * float and once
for float * Vec2 — because daslang does not automatically commute
arguments.
5.1.32.2. Comparison operators
Overload ==, !=, <, >, <=, >= for custom comparisons.
Returning bool is required:
def operator ==(a, b : Vec2) : bool {
return a.x == b.x && a.y == b.y
}
def operator !=(a, b : Vec2) : bool {
return !(a == b)
}
// Order by squared magnitude (avoids sqrt)
def operator <(a, b : Vec2) : bool {
return (a.x * a.x + a.y * a.y) < (b.x * b.x + b.y * b.y)
}
Usage:
let a = Vec2(x = 1.0, y = 2.0)
let c = Vec2(x = 3.0, y = 4.0)
print("a < c: {a < c}\n") // true
Note
daslang does not auto-generate != from ==, or > from <.
Each comparison operator must be defined explicitly if needed.
5.1.32.3. Unary operators
Overload unary - (negate), ~ (bitwise complement), ! (logical not),
and prefix/postfix ++/--:
def operator -(a : Vec2) : Vec2 {
return Vec2(x = -a.x, y = -a.y)
}
Usage:
let a = Vec2(x = 1.0, y = 2.0)
let neg = -a
print("-a = ({neg.x}, {neg.y})\n") // -a = (-1, -2)
For increment/decrement, ++operator is prefix (modify-then-use) while
operator ++ is postfix (use-then-modify).
5.1.32.4. Compound assignment operators
Overload +=, -=, *=, /=, %=, &=, |=, ^=,
<<=, >>= and others for in-place modification. The first argument must
be var ... & (mutable reference):
def operator +=(var a : Vec2&; b : Vec2) {
a.x += b.x
a.y += b.y
}
def operator -=(var a : Vec2&; b : Vec2) {
a.x -= b.x
a.y -= b.y
}
def operator *=(var a : Vec2&; s : float) {
a.x *= s
a.y *= s
}
Usage:
var a = Vec2(x = 1.0, y = 2.0)
a += Vec2(x = 0.5, y = 0.5)
// a is now (1.5, 2.5)
5.1.32.5. Index operators
Index operators control how [] behaves on your types:
Operator |
Purpose |
|---|---|
|
Read access — |
|
Write access — |
|
Compound index — |
|
Compound index — |
|
Move into index — |
|
Clone into index — |
Example as free functions:
struct Matrix2x2 {
data : float[4]
}
def operator [](m : Matrix2x2; i : int) : float {
return m.data[i]
}
def operator []=(var m : Matrix2x2&; i : int; v : float) {
m.data[i] = v
}
def operator []+=(var m : Matrix2x2&; i : int; v : float) {
m.data[i] += v
}
Usage:
var m : Matrix2x2
m.data[0] = 1.0
m[0] = 10.0 // calls operator []=
m[0] += 5.0 // calls operator []+=
print("{m[0]}\n") // 15
The safe index operator ?[] returns a default value when the index is out of
range, following the same pattern.
5.1.32.6. Dot operators / property accessors
Dot operators let you create computed properties that look like regular field access.
operator . name defines a getter, operator . name := defines a setter:
struct Particle {
pos_x : float
pos_y : float
}
// getter — computed "speed" property
def operator . speed(p : Particle) : float {
return sqrt(p.pos_x * p.pos_x + p.pos_y * p.pos_y)
}
// setter — scales direction to target speed
def operator . speed := (var p : Particle&; value : float) {
let mag = sqrt(p.pos_x * p.pos_x + p.pos_y * p.pos_y)
if (mag > 0.0) {
let scale = value / mag
p.pos_x *= scale
p.pos_y *= scale
}
}
Usage:
var p = Particle(pos_x = 3.0, pos_y = 4.0)
print("{p.speed}\n") // 5
p.speed := 10.0
// p is now (6, 8)
The generic operator . takes a string field name and intercepts all field
accesses. This is powerful but should be used sparingly:
def operator .(t : MyType; name : string) : string {
return "accessing: {name}"
}
The ". ." (dot-space-dot) syntax bypasses any overloaded dot operator to
access real struct fields:
print("{p . . pos_x}\n") // accesses the actual field
5.1.32.7. Clone and finalize
operator := overloads clone behaviour (the := operator) and
operator delete (or defining a finalize function) overloads deletion:
struct Resource {
name : string
refcount : int
}
def operator :=(var dst : Resource&; src : Resource) {
dst.name = src.name
dst.refcount = src.refcount + 1
}
Usage:
var orig = Resource(name = "texture", refcount = 1)
var copy : Resource
copy := orig
print("{copy.refcount}\n") // 2 — clone incremented the count
See also
Move, copy, and clone for more details.
5.1.32.8. Struct method operators
Operators can be defined as struct methods instead of free functions. This is useful for encapsulation — the operator lives with the type definition:
struct Stack {
items : array<int>
def const operator [](index : int) : int {
return items[index]
}
def operator []=(index : int; value : int) {
items[index] = value
}
}
The const qualifier on the getter means it does not modify the struct.
Write operators omit const because they mutate state.
Usage:
var s : Stack
s.items |> push(10)
s.items |> push(20)
print("{s[0]}\n") // 10
s[1] = 99
print("{s[1]}\n") // 99
5.1.32.9. Generic operators
Operators (and operator-like functions) can be generic using auto types.
This lets one definition cover multiple types, as long as they have the required
fields:
struct Vec3 {
x : float
y : float
z : float
}
def dot_2d(a, b : auto) : float {
return a.x * b.x + a.y * b.y
}
Both Vec2 and Vec3 have x and y fields, so dot_2d works
with either:
let v2a = Vec2(x = 1.0, y = 0.0)
let v2b = Vec2(x = 0.0, y = 1.0)
print("{dot_2d(v2a, v2b)}\n") // 0
let v3a = Vec3(x = 3.0, y = 4.0, z = 0.0)
let v3b = Vec3(x = 1.0, y = 0.0, z = 99.0)
print("{dot_2d(v3a, v3b)}\n") // 3
For more sophisticated constraints, use contracts.
5.1.32.10. Complete operator reference
The full list of overloadable operators in daslang:
Category |
Operators |
|---|---|
Arithmetic |
|
Comparison |
|
Bitwise |
|
Logical |
|
Unary |
|
Compound assignment |
|
Index |
|
Safe index |
|
Dot |
|
Type |
|
Null coalesce |
|
Interval |
|
See also
Full source: tutorials/language/32_operator_overloading.das
Next tutorial: Algorithm
Regular expressions tutorial (previous tutorial).
Functions — function declaration and operator overloading reference.
Generic programming — generic operators with auto types.