Cella Documentation 0.1.0-dev Home Source

Operators

Records and enums can declare their own operators. An operator member is a function written with op and named by its symbol, and a + b on that type calls it. The same form declares indexers (v[i]), the dereference operator (*p) and the operators that let for walk a type.

Declaring an operator

A binary operator is a static member that takes both operands as parameters:

Vector: rec{	x: f64	y: f64 	+: op(a: Vector, b: Vector) -> Vector => Vector(a.x + b.x, a.y + b.y)	-: op(a: Vector, b: Vector) -> Vector => Vector(a.x - b.x, a.y - b.y)} main: fun{	val a = Vector(1.0, 2.0)	val b = Vector(3.0, 5.0)	val sum = a + b	val difference = b - a	printf("%.1f %.1f\n", sum.x, sum.y)	printf("%.1f %.1f\n", difference.x, difference.y)}
Output
4.0 7.0
2.0 3.0

The member's name is the operator, followed by a colon and op. The body is a block or => and an expression, as for any function. Operators keep the precedence and associativity of the built-in ones (see Operators and precedence).

Binary operators

These binary operators can be declared:

KindOperators
Arithmetic+ - * / %
Wrapping+% -% *%
Bitwise& | ^
Shifts and rotations<< >> <<< >>>
Comparisons== != < <= > >=

A binary operator takes exactly two parameters, and at least one of them must have the declaring type. The other can be any type, and either one can come first, so a type can declare both v * 2.0 and 2.0 * v:

Vector: rec{	x: f64	y: f64 	*: op(v: Vector, scale: f64) -> Vector => Vector(v.x * scale, v.y * scale)	*: op(scale: f64, v: Vector) -> Vector => Vector(v.x * scale, v.y * scale)	==: op(a: Vector, b: Vector) -> bool => a.x == b.x && a.y == b.y} main: fun{	val v = Vector(1.0, 2.0)	val right = v * 3.0	val left = 3.0 * v	val literal = v * 3	printf("%.1f %.1f\n", right.x, right.y)	printf("%d %d\n", i32(right == left), i32(left == literal))}
Output
3.0 6.0
1 1

The literal 3 in v * 3 becomes an f64, the type the parameter asks for, as a literal does for any call.

The rules on the parameters:

Point: rec{	x: i32 	+: op(a: i32, b: i32) -> i32 => a + b'+' operators need a 'Point' parameter	/: op(self, other: Point) -> Point => otherCannot take 'self' in '/' operators	*: op(mut a: Point, b: Point) -> Point => bCannot take 'mut' parameters in '*' operators	!=: op(a: Point) -> bool => true'!=' operators need two parameters} main: fun{}

Declared comparisons chain like the built-in ones, so a < b < c means a < b && b < c, with b evaluated once:

Version: rec{	major: i32	minor: i32 	<: op(a: Version, b: Version) -> bool => a.major < b.major || a.major == b.major && a.minor < b.minor} main: fun{	val low = Version(1, 2)	val mid = Version(1, 10)	val high = Version(2, 0)	printf("%d %d\n", i32(low < mid < high), i32(mid < low < high))}
Output
1 0

Unary operators

-, + and ~ take one operand, which the operator receives as self:

Flags: rec{	bits: u8 	~: op(self) -> Flags => Flags(~self.bits)	-: op(self) -> Flags => Flags(0)	-: op(a: Flags, b: Flags) -> Flags => Flags(a.bits & ~b.bits)} main: fun{	val f = Flags(0b1010)	printf("%u %u %u\n", (~f).bits, (-f).bits, (f - Flags(0b0010)).bits)}
Output
245 0 8

- and + can be declared both ways: with self as unary operators and with two parameters as binary ones. ~ is only unary. The receiver can be self or imm self, but not mut self: a unary operator gives a new value and leaves its operand as it was.

Some operators can't be declared at all: !, &&, ||, = and ..

Point: rec{	x: i32 	~: op(a: Point, b: Point) -> Point => aCannot declare '~' operators without 'self'	-: op(mut self) -> Point => selfCannot take 'mut self' in '-' operators	!: op(self) -> Point => selfCannot declare '!' operators} main: fun{}

Compound assignment

A compound assignment such as += changes its left side in place. It takes mut self and one parameter, and returns nothing:

Score: rec{	points: i32	bonus: i32 	+=: op(mut self, points: i32)	{		self.points += points		if points >= 10			self.bonus += 1	}} main: fun{	var score = Score(0, 0)	score += 5	score += 12	printf("%d %d\n", score.points, score.bonus)}
Output
17 1

Every compound form of the binary operators can be declared: +=, -=, *=, /=, %=, +%=, -%=, *%=, &=, |=, ^=, <<=, >>=, <<<= and >>>=. Since the operator borrows its left side mutably, that side must be something that can change, such as a var:

Score: rec{	points: i32 	+=: op(mut self, points: i32) => self.points += points	-=: op(self, points: i32) => retCannot take 'self' in '-=' operators	*=: op(mut self) => ret'*=' operators need one parameter	/=: op(mut self, divisor: i32) -> Score => selfCannot return values from '/=' operators} main: fun{	val score = Score(0)	score += 5Cannot mutably borrow values}

Nothing is derived

Each operator stands alone. Declaring + doesn't give +=, == doesn't give !=, and < doesn't give >, <= or >=. Each one a type supports has to be declared:

Point: rec{	x: i32 	+: op(a: Point, b: Point) -> Point => Point(a.x + b.x)	==: op(a: Point, b: Point) -> bool => a.x == b.x	<: op(a: Point, b: Point) -> bool => a.x < b.x} main: fun{	var a = Point(1)	a += Point(2)No operation defined for '+=' between 'Point' and 'Point'	val different = a != Point(3)No operation defined for '!=' between 'Point' and 'Point'	val greater = a > Point(0)No operation defined for '>' between 'Point' and 'Point'}

A record has no operators of its own, not even ==. An enum whose cases have no payloads compares with == and != already, and declaring either is an error ("Operator '==' between 'Color' and 'Color' is built in"), as is declaring any operator that a built-in type already has for the same operands. An enum with payloads has neither until it declares them. Enums declare operators the same way records do:

Shape: enum{	rect(width: i32, height: i32)	square(side: i32) 	==: op(a: Shape, b: Shape) -> bool => area(a) == area(b)	!=: op(a: Shape, b: Shape) -> bool => area(a) != area(b)} area: fun(shape: Shape) -> i32 => match shape{	rect(val w, val h) => w * h	square(val side) => side * side} main: fun{	val a = Shape.rect(2, 8)	val b = Shape.square(6)	val c = Shape.square(4)	printf("%d %d\n", i32(a == c), i32(a != b))}
Output
1 1

Operands and parameter modes

An operand without a mode is taken by value, as for any parameter (see Parameter modes): a copy, or the value itself for a type that can't be copied. Such a type usually takes its operands with imm to borrow them. Operands carry no marks, so a + b borrows or moves each operand as the operator's declaration says:

Text: own rec{	id: i32 	+: op(imm a: Text, imm b: Text) -> Text => Text(a.id * 10 + b.id)	-: op(a: Text, imm b: Text) -> Text => Text(a.id - b.id) 	drop: op	{		printf("drop %d\n", self.id)	}} main: fun{	val a = Text(1)	val b = Text(2)	val c = a + b	val d = c - b	printf("result %d\n", d.id)}
Output
drop 12
result 10
drop 10
drop 2
drop 1

c - b hands c to the operator, which drops it when it returns, and c can't be used afterwards. A compound assignment's parameter takes modes the same way, except mut.

Calling an operator through its type

Type.op(...) calls an operator like a static function. The operands are the arguments, receiver first, and a compound assignment's left side is passed with mut:

Point: rec{	x: i32 	-: op(self) -> Point => Point(-self.x)	-: op(a: Point, b: Point) -> Point => Point(a.x - b.x) 	+=: op(mut self, other: Point)	{		self.x += other.x	}} main: fun{	var p = Point(3)	val negated = Point.-(p)	val difference = Point.-(p, Point(1))	Point.+=(mut p, Point(10))	printf("%d %d %d\n", negated.x, difference.x, p.x)}
Output
-3 2 13

Built-in types work the same way, so i32.+(1, 2) adds two i32s and f64.*(1.5, 2.0) multiplies two f64s:

main: fun{	val small: i8 = 100	var total = 7	i32.+=(mut total, 3)	printf("%d %d %d %.1f\n", i32.+(1, 2), i32.+(small, small), total, f64.*(1.5, 2.0))}
Output
3 200 10 3.0

i32.+(small, small) converts both i8s to i32 first, so the sum doesn't overflow. An operator can't be called through a value:

Point: rec{	x: i32 	-: op(self) -> Point => Point(-self.x)} main: fun{	val p = Point(1)	val negated = p.-()Cannot call '-' through a value}

Ambiguous operators

Both operand types can declare an operator for the same pair. Using it is then an error, because neither declaration wins:

Matrix: rec{	scale: i32 	*: op(m: Matrix, v: Vector) -> Vector => Vector(v.x * m.scale)} Vector: rec{	x: i32 	*: op(m: Matrix, v: Vector) -> Vector => Vector(v.x * m.scale + 1)} main: fun{	val product = Matrix(3) * Vector(5)Operation '*' between 'Matrix' and 'Vector' is ambiguous}

Calling the operator through one of the types picks its declaration:

main: fun{	val a = Matrix.*(Matrix(3), Vector(5))	val b = Vector.*(Matrix(3), Vector(5))	printf("%d %d\n", a.x, b.x)}
Output
15 16

Two declarations in the same type with the same parameter types conflict, and both are reported:

Point: rec{	x: i32 	<: op(a: Point, b: Point) -> bool => a.x < b.x'Point.<' is declared more than once with the same signature	<: op(a: Point, b: Point) -> bool => a.x > b.x'Point.<' is declared more than once with the same signature} main: fun{}

Visibility

An operator is visible wherever its type is. pvt limits it to the type's own members, like any other member (see Methods and properties):

Point: rec{	x: i32 	*: pvt op(a: Point, b: Point) -> Point => Point(a.x * b.x) 	square: fun(self) -> Point => self * self} main: fun{	val p = Point(3)	val squared = p.square()	val product = p * p'*' is private to its type}

Generic operators

An operator can have type parameters, written after its symbol. They must be inferable from the operands:

Box[T]: rec{	value: T} Counter: rec{	n: i32 	+[T: copy]: op(a: Counter, b: Box[T]) -> Counter => Counter(a.n + 1)} main: fun{	val c = Counter(0) + Box(2.5) + Box('x')	printf("%d\n", c.n)}
Output
2

A type parameter that only appears in the result can't be inferred, since an operator use has no place to write type arguments:

Counter: rec{	n: i32 	+[T: copy]: op(a: Counter, b: Counter) -> T => undef[T]Cannot infer 'T' from the operands of '+'} main: fun{}

Generics are covered in Generics.

Indexers

An indexer makes value[i] work on a type. It's an operator named [], and it usually comes as a pair: one with imm self that lends the element for reading, and one with mut self that lends it for writing.

Scores: rec{	values: array[i32, 3] 	[]: op(imm self, i: usize) -> imm[i32] => imm self.values[i]	[]: op(mut self, i: usize) -> mut[i32] => mut self.values[i]} addTen: fun(mut value: i32){	value += 10} main: fun{	var scores = Scores([0, 0, 0])	scores[0] = 5	scores[1] = scores[0] * 2	scores[2] += 7	addTen(mut scores[2])	printf("%d %d %d\n", scores[0], scores[1], scores[2])}
Output
5 10 17

The read-only indexer returns imm[T] and the writable one mut[T], borrows of the element (see Borrows). scores[i] then works like an array element: it can be read, assigned, changed with +=, passed as mut scores[i], and used with . to reach the element's fields and methods.

Which indexer a use picks

The compiler picks the indexer from how the element is used. Reading the element, borrowing it with imm or calling a method on it that doesn't change it uses the imm self indexer. Assigning to it, changing it with a compound assignment, passing it as mut, assigning to one of its fields or calling a mut self method on it uses the mut self indexer:

Logged: rec{	values: array[i32, 2] 	[]: op(imm self, i: usize) -> imm[i32]	{		printf("read ")		ret imm self.values[i]	} 	[]: op(mut self, i: usize) -> mut[i32]	{		printf("write ")		ret mut self.values[i]	}} main: fun{	var logged = Logged([1, 2])	val a = logged[0]	logged[0] = 3	logged[1] += 4	val b = imm logged[1]	printf("\n%d %d\n", a, i32(b))}
Output
read write write read 
1 6

A type with only a writable indexer uses it for reads too, as long as the value is writable. A type with only a read-only one can't have its elements changed:

Readings: rec{	values: array[i32, 2] 	[]: op(imm self, i: usize) -> imm[i32] => imm self.values[i]} Slots: rec{	values: array[i32, 2] 	[]: op(mut self, i: usize) -> mut[i32] => mut self.values[i]} main: fun{	var readings = Readings([1, 2])	readings[0] = 5'Readings' has no writable '[]'	val slots = Slots([1, 2])	val first = slots[0]'Slots' has no read-only '[]'}

When the writable indexer is pvt, the elements are read-only outside the type, while the type's own members can still write them:

Scores: rec{	values: array[i32, 3] 	[]: op(imm self, i: usize) -> imm[i32] => imm self.values[i]	[]: pvt op(mut self, i: usize) -> mut[i32] => mut self.values[i] 	reset: fun(mut self, i: usize)	{		self[i] = 0	}} main: fun{	var scores = Scores([4, 5, 6])	scores.reset(1)	scores[0] = 9'[]' is read-only outside its type}

Indexers that return values

The read-only indexer can return the element by value instead of lending it. This is how a type gives out elements that aren't stored anywhere, such as single bits:

Bits: rec{	word: u64 	[]: op(self, i: usize) -> bool => ((self.word >> u64(i)) & 1) != 0} main: fun{	val bits = Bits(0b1010)	printf("%d %d %d %d\n", i32(bits[0]), i32(bits[1]), i32(bits[2]), i32(bits[3]))}
Output
0 1 0 1

A value that an indexer returns isn't stored in the type, so it can't be assigned, borrowed or changed:

Bits: rec{	word: u64 	[]: op(self, i: usize) -> bool => ((self.word >> u64(i)) & 1) != 0} flip: fun(mut value: bool){	value = !value} main: fun{	var bits = Bits(5)	bits[0] = trueCannot assign to unstored values	flip(mut bits[1])Cannot mutably borrow unstored values}

A by-value indexer can be paired with a writable one, which handles every write while the by-value one handles reads.

Several parameters

An indexer takes any number of parameters of any type, so m[row, column] and table[color] work:

Matrix: rec{	cells: array[f64, 4] 	[]: op(imm self, row: usize, column: usize) -> imm[f64] => imm self.cells[row * 2 + column]	[]: op(mut self, row: usize, column: usize) -> mut[f64] => mut self.cells[row * 2 + column]} Color: enum{	red	green	blue} Table: rec{	values: array[i32, 3] 	[]: op(self, color: Color) -> i32 => self.values[usize(color)]	[]: op(mut self, color: Color) -> mut[i32] => mut self.values[usize(color)]} main: fun{	var m = Matrix([1.0, 2.0, 3.0, 4.0])	m[1, 0] = 7.5	m[0, 1] *= 3.0	printf("%.1f %.1f %.1f %.1f\n", m[0, 0], m[0, 1], m[1, 0], m[1, 1]) 	var table = Table([0, 0, 0])	table[green] = 4	table[Color.blue] += 2	printf("%d %d %d\n", table[red], table[green], table[blue])}
Output
1.0 6.0 7.5 4.0
0 4 2

A bare case name like green works as an index because the parameter expects a Color.

Indexer rules

An indexer needs a receiver and at least one parameter, and none of its parameters can be mut. A type has at most one read-only and one writable indexer, and the two must take the same parameters and give the same element type. The read-only one takes self or imm self and returns imm[T] or T; the writable one takes mut self and returns mut[T]:

Grid: rec{	cells: array[i32, 4] 	[]: op(self, i: usize) -> imm[i32] => imm self.cells[i]'[]' with 'self' is declared more than once in 'Grid'	[]: op(self, row: usize, column: usize) -> imm[i32] => imm self.cells[row * 2 + column]'[]' with 'self' is declared more than once in 'Grid'} Mixed: rec{	cells: array[i32, 4] 	[]: op(imm self, i: usize) -> imm[i32] => imm self.cells[i]Cannot declare '[]' operators with different parameters	[]: op(mut self, i: isize) -> mut[i32] => mut self.cells[0]Cannot declare '[]' operators with different parameters} Wrong: rec{	cells: array[i32, 4] 	[]: op(mut self, i: usize) -> i32 => self.cells[i]Cannot return 'i32' from '[]' with 'mut self'	[]: op(i: usize) -> i32 => 0Cannot declare '[]' operators without 'self'} main: fun{}

Indexing with the wrong number of arguments says how many the type takes:

Grid: rec{	cells: array[i32, 4] 	[]: op(imm self, row: usize, column: usize) -> imm[i32] => imm self.cells[row * 2 + column]} main: fun{	val grid = Grid([1, 2, 3, 4])	val cell = grid[1]Indexing 'Grid' takes 2 arguments}

The dereference operator

A type that holds a pointer to a value can make * reach that value. The operator is * with a receiver and no parameters. Like indexers, it comes as a read-only form with imm self returning imm[T] and a writable form with mut self returning mut[T]:

Enemy: rec{	health: i32 	hit: fun(mut self, damage: i32) => self.health -= damage} EnemyBox: own rec{	pointer: req ptr[Enemy] 	new: op(enemy: Enemy)	{		self.pointer = ptr[Enemy](malloc(sizeOf(Enemy)))		*self.pointer = enemy	} 	*: op(imm self) -> imm[Enemy] => imm *self.pointer	*: op(mut self) -> mut[Enemy] => mut *self.pointer 	drop: op	{		free(self.pointer)	}} main: fun{	var boxed = EnemyBox(Enemy(30))	printf("%d\n", (*boxed).health)	*boxed = Enemy(50)	(*boxed).hit(5)	printf("%d\n", (*boxed).health)}
Output
30
45

The use picks the form the same way as for indexers: reads take the imm self one, and writes, mut borrows and mut self calls take the mut self one. A read-only * can also return its target by value, as in *: op(imm self) -> i32.

. looks through *

. on a value whose type declares * reaches the target's members, so boxed.health means (*boxed).health:

Enemy: rec{	health: i32 	hit: fun(mut self, damage: i32) => self.health -= damage 	describe: fun(self) => printf("enemy %d\n", self.health)} EnemyBox: own rec{	pointer: req ptr[Enemy] 	new: op(enemy: Enemy)	{		self.pointer = ptr[Enemy](malloc(sizeOf(Enemy)))		*self.pointer = enemy	} 	*: op(imm self) -> imm[Enemy] => imm *self.pointer	*: op(mut self) -> mut[Enemy] => mut *self.pointer 	describe: fun(imm self) => printf("box\n") 	drop: op	{		free(self.pointer)	}} main: fun{	var boxed = EnemyBox(Enemy(30))	boxed.hit(5)	boxed.health += 1	printf("%d\n", boxed.health)	boxed.describe()	(*boxed).describe()}
Output
26
box
enemy 26

The type's own members come first: boxed.describe() calls EnemyBox.describe, and (*boxed).describe() reaches the one on Enemy. . looks through one level only. If the target also declares *, its members are reached with (*outer).field, not outer.field.

Dereference rules

* takes a receiver and no other parameters, and a self receiver needs a type that copies. A type has at most one form of each kind, and both must give the same target type:

Holder: rec{	pointer: req ptr[i32]	count: i64 	*: op(self, index: i32) -> imm[i32] => imm *self.pointerCannot take parameters in '*' operators with 'self'} Mixed: rec{	pointer: req ptr[i32]	count: i64 	*: op(imm self) -> imm[i32] => imm *self.pointerCannot declare '*' operators with different target types	*: op(mut self) -> mut[i64] => mut self.countCannot declare '*' operators with different target types} Swapped: rec{	pointer: req ptr[i32] 	*: op(self) -> mut[i32] => mut *self.pointerCannot return 'mut[i32]' from '*' with 'self'} main: fun{}

Custom for loops

for walks integer ranges and arrays on its own (see Control flow and Arrays). Any other type takes part through two operators: for: op(mut self) -> bool and *. A type with both is a cursor. Before each pass, the loop calls for; when it returns true, the loop reads the element with * and runs the body, and when it returns false, the loop ends.

Countdown: rec{	left: i32 	for: op(mut self) -> bool	{		self.left -= 1		ret self.left >= 0	} 	*: op(imm self) -> imm[i32] => imm self.left} main: fun{	for x in Countdown(3)		printf("%d ", x) 	printf("\n")}
Output
2 1 0 

for takes mut self and nothing else, and returns bool. break, cont and ret work as in any loop.

Stepping a cursor in place

A cursor in a variable is copied into the loop, like an argument, so the variable doesn't change. for x in mut c steps the variable itself, so a loop that stops early leaves it where it stopped, and a second loop carries on from there:

main: fun{	var c = Countdown(6)	for x in c		printf("%d ", x) 	printf("| left %d\n", c.left) 	for x in mut c	{		if x == 3			break	} 	printf("left %d\n", c.left) 	for x in mut c		printf("%d ", x) 	printf("| left %d\n", c.left)}
Output
5 4 3 2 1 0 | left 6
left 3
2 1 0 | left -1

Without mut, for x in c takes the cursor by value instead: it copies a cursor that copies and moves one that doesn't.

Collections

A collection hands out a cursor through in. in: op(imm self) -> Cursor makes the cursor for loops that read; a second in: op(mut self) -> Cursor makes one for loops that change the elements. A cursor that refers back to its collection holds a borrow of it, so it's a ref rec (see Borrows):

Bag: rec{	items: array[i32, 4]	count: usize 	in: op(imm self) -> BagCursor => BagCursor(imm self, 0, 0)	in: op(mut self) -> BagEditor => BagEditor(mut self, 0, 0)} BagCursor: ref rec{	bag: imm[Bag]	current: usize	next: usize 	for: op(mut self) -> bool	{		self.current = self.next		self.next += 1		ret self.current < self.bag.count	} 	*: op(self) -> imm[i32] => imm self.bag.items[self.current]} BagEditor: ref rec{	bag: mut[Bag]	current: usize	next: usize 	for: op(mut self) -> bool	{		self.current = self.next		self.next += 1		ret self.current < self.bag.count	} 	*: op(self) -> imm[i32] => imm self.bag.items[self.current]	*: op(mut self) -> mut[i32] => mut self.bag.items[self.current]} main: fun{	var bag = Bag([1, 2, 3, 4], 3)	for mut x in bag		x += 10 	for x in bag		printf("%d ", x) 	printf("\n")}
Output
11 12 13 

The binding picks the in: for x in bag and for imm x in bag use the imm self one, and for mut x in bag uses the mut self one. for x in bag copies each element, which needs a copyable element type, while for imm x in bag borrows it. Writing to a mut binding goes through the cursor's writable *.

When a loop ends

The cursor lives until the loop ends, however it ends, and is dropped then. A cursor with a destructor finds out that the loop is over, which a collection can use to check, for example, that it wasn't changed during the loop:

Countdown: own rec{	left: i32 	for: op(mut self) -> bool	{		self.left -= 1		ret self.left >= 0	} 	*: op(imm self) -> imm[i32] => imm self.left 	drop: op	{		printf("[done %d]", self.left)	}} find: fun(target: i32) -> i32{	for x in Countdown(10)	{		if x == target			ret x * 2	} 	ret -1} main: fun{	for x in Countdown(5)	{		if x == 3			cont 		if x == 1			break 		printf("%d ", x)	} 	printf("\n")	val found = find(4)	printf(" %d\n", found)}
Output
4 2 [done 1]
[done 4] 8

Loop operator rules

in takes a receiver, imm self, mut self or, for a type that copies, self, and no other parameters, and returns a cursor rather than a borrow. for takes mut self only and returns bool. A type declares each form once:

Steps: rec{	left: i32 	for: op(self) -> bool => trueCannot take 'self' in 'for' operators	for: op(mut self, n: i32) -> bool => trueCannot take parameters in 'for' operators	for: op(mut self) -> i32 => 1Cannot return 'i32' from 'for' operators} Sources: rec{	x: i32 	in: op(self) -> Steps => Steps(0)	in: op(self, extra: i32) -> Steps => Steps(extra)Cannot take parameters in 'in' operators	in: op(imm self) -> imm[i32] => imm self.xCannot return 'imm[i32]' from 'in' operators} main: fun{}

The ? operator

Postfix ? unwraps a value or leaves the function early. A type declares it as ?: op(self) -> T with an expression body, or as ?: op(self) with no result. When a function writes x?, the body runs in that function: its value becomes the value of x?, and a ret in the body returns from the function that wrote x?.

The usual use is a result type that holds either a value or an error. ? gives the value, or returns the error to the caller:

Result[T, E]: enum{	ok(value: T)	err(error: E) 	?: op(self) -> T => match self	{		ok(val value) => value		err(val error) => ret Result.err(error)	}} parse: fun(n: i32) -> Result[i32, i32]{	if n < 0		ret Result.err(n) 	ret Result.ok(n * 2)} sumTwo: fun(a: i32, b: i32) -> Result[i32, i32]{	val total = parse(a)? + parse(b)?	ret Result.ok(total)} show: fun(result: Result[i32, i32]){	match result	{		ok(val value) => printf("ok %d\n", value)		err(val error) => printf("error %d\n", error)	}} main: fun{	show(sumTwo(1, 2))	show(sumTwo(-1, 2))	show(sumTwo(1, -5))}
Output
ok 6
error -1
error -5

In sumTwo(-1, 2), parse(-1)? runs the err arm, which returns Result.err(-1) from sumTwo; parse(2) never runs.

The function that uses ? must be able to return what the body returns. Using it in a function with another result type, with no result, or that never returns is an error, and so is ? on a type that doesn't declare it:

parse: fun(n: i32) -> Result[i32, i32] => Result.ok(n) plain: fun -> i32 => parse(1)?Cannot use '?' on 'Result[i32, i32]' in 'plain''plain' returns 'i32' nothing: fun{	val x = parse(1)?Cannot use '?' on 'Result[i32, i32]' in 'nothing''nothing' doesn't return a value} number: fun -> i32{	val x = 5	ret x?Cannot use '?' on 'i32'} main: fun{}

A ? operator with no result suits operations that either succeed with nothing to give back or fail. It can have a block body, and x? is then a statement:

Status[E]: enum{	done	failed(error: E) 	?: op(self)	{		if self is failed(val error)			ret failed(error)	}} flush: fun(fail: bool) -> Status[i32]{	if fail		ret failed(5) 	ret done} save: fun(first: bool, second: bool) -> Status[i32]{	flush(first)?	flush(second)?	ret done} show: fun(status: Status[i32]){	match status	{		done => printf("done\n")		failed(val error) => printf("failed %d\n", error)	}} main: fun{	show(save(false, false))	show(save(false, true))}
Output
done
failed 5

Since there's no result, using such a ? as a value is an error:

flush: fun -> Status[i32] => done save: fun -> Status[i32]{	val result = flush()?'flush()?' doesn't return a value	ret done} main: fun{}

A ? operator takes self and nothing else and can't be generic or required by a trait. One with a return type needs a => body:

Copied: enum{	one 	?: op(imm self) -> i32 => 1Cannot take 'imm self' in '?' operators} Block: enum{	one 	?: op(self) -> i32'?' operators must have an expression body	{		ret 1	}} Fallible: trait{	?: op(self) -> i32Cannot declare '?' operators in traits} main: fun{}

Operators in traits

A trait can require operators, indexers, * and in like any other member, and generic code can then use them on a type parameter. ? is the exception. See Traits.