Early preview: syntax, standard library APIs, and tooling may change.

Safety and Runtime ModelMemory by Example

Memory by Example

Blorp gives ordinary data value semantics, while ARC and COW keep copies efficient. Channels and scoped resources have different ownership rules.

Value Semantics

Assignment creates another name for the same value, not a mutable reference to be changed through either name.

original: List[Int] = [1, 2]

snapshot: List[Int] = original

ARC/COW

ARC and copy-on-write let containers share storage until an update would be observable.

updated: List[Int] = snapshot.append(3)

print(original.to_string()) -- prints: [1, 2]
print(snapshot.to_string()) -- prints: [1, 2]
print(updated.to_string()) -- prints: [1, 2, 3]

Aliases

append through one name returns a new list value; the earlier alias keeps seeing the original list.

func main(args: List[String]) -> Void:
	a: List[Int] = [1]

	b: List[Int] = a

	c: List[Int] = b.append(2)

Channel Handles

Copying a channel copies a handle to the same endpoint, not its queued data. Sending or sealing through either handle affects that endpoint. This is explicit communication, unlike updating an ordinary copied list.

import:
	channel: RecvAttempt(RecvValue, RecvSealed), try_recv_attempt, wait_send


func copied_channel_shares_endpoint() -> Bool:
	original: Channel[Int] = channel(1)
	copy = original
	sent = wait_send(copy, 42).is_ok()
	seal(copy)
	-- Sealing either handle seals the same endpoint; buffered data still drains.
	received = match try_recv_attempt(original):
		RecvValue(42): True
		_: False
	sealed = match try_recv_attempt(original):
		RecvSealed: True
		_: False
	sent and received and sealed


func main(args: List[String]) -> Int:
	shared = copied_channel_shares_endpoint()
	print(shared.to_string()) -- stdout: True
	if shared:
		0
	else:
		1

Closure Capture

A closure cannot capture a var binding. Bind an immutable snapshot first; later rebinding the original does not change the captured value. Scoped resource handles cannot be captured at all.

pure func make_adder(base: Int) -> pure (Int) -> Int:
	pure func(x): x + base


func snapshot_value() -> Int:
	var count: Int = 7
	snapshot: Int = count
	read_snapshot = pure func() -> Int: snapshot
	count = 9
	read_snapshot()


func main(args: List[String]) -> Void:
	print(snapshot_value().to_string()) -- stdout: 7 (not 9)

Example

value-semantics.brp
pure func make_label(prefix: String) -> pure (Int) -> String:
	pure func(n: Int): prefix + n.to_string()


func main(args: List[String]) -> Void:
	original: List[Int] = [1, 2, 3]
	snapshot: List[Int] = original
	updated: List[Int] = snapshot.append(4)

	label = make_label("count: ")
	print(original.length()) -- prints: 3
	print(updated.length()) -- prints: 4
	print(label(updated.length())) -- prints: count: 4

Try it

terminal
blorp run value-semantics.brp