talk_dev's Journal

Talk of the Town

Today's Typecheck
talk_dev posted a new release

Ownership Gets a Route Map

Talk now follows owned values through loops, fields, borrows, receivers, and bytecode with sharper rules.


Today's Talk release is about keeping track of who owns what, even when control flow takes the scenic road. Breaks, continues, borrowed parameters, consuming receivers, and field moves now leave clearer footprints for the compiler and runtime.

Loop Exits Pay Their Fare

The lead item: owned locals inside loops are now cleaned up when control leaves by break or spins onward by continue. The changed lowering tests look for real frees before the loop exit or next loop head.

That makes early loop control behave like ordinary scope exit instead of slipping past owned values with its pockets full.

func f() -> Int {
	// the loop creates a nested
	// lifetime for its body
	loop {
		// s is the owned String whose
		// storage must not leak
		let s = "a" + "b"
		// break takes the early exit
		// path that now drops s
		break
	}
	0
}
f()
func f() -> Int {
	// keep_going drives the continue
	// branch
	let keep_going = true
	loop {
		// s is created before the branch
		// and must be handled on both
		// exits
		let s = "a" + "b"
		if keep_going {
			// continue jumps to the loop
			// head after dropping owned
			// loop locals
			continue
		}
		break
	}
	0
}
f()
Moves Through Branches Get Flags

Talk now handles the case where a value is moved on one branch but not the other. The lowering tests expect a runtime drop flag, so the final cleanup drops only when the value is still live.

The practical result is that a conditional move no longer forces the compiler into either leaking or double-dropping.

func maybe(flag: Bool) -> Int {
	// s starts live before the if
	let s = "hi" + "!"
	if flag {
		// the then branch moves s into t
		let t = s
		t.length
	} else {
		// the else branch leaves s live,
		// so cleanup must be conditional
		2
	}
	0
}
maybe(false)
Fewer mystery moves, fewer forgotten drops.- The day's release, in one line
Fields Can Move Without Taking the Whole House

Owned struct fields now get more precise drop treatment. Moving one field leaves its siblings available, while the moved path is tracked separately for cleanup.

Assignment to a moved field also reopens that field for ordinary use. The release tests check both the open-field drop and the flag reset after replacement.

struct Person {
	let name: String
	let title: String
	let age: Int
}

// person owns multiple fields with
// different drop needs
func f() -> Int {
	let person = Person(
		name: "Pat" + "",
		title: "Dr" + "",
		age: 41
	)
	// name moves out of person.name
	let name = person.name
	// person.title and person.age
	// remain usable after the field
	// move
	name.length + person.title.length + person.age
}
f()
struct Person {
	let name: String
	let title: String
}
func f() -> Int {
	let person = Person(
		name: "Pat" + "",
		title: "Dr" + ""
	)
	// old receives the original
	// person.name
	let old = person.name
	// assigning person.name
	// reinitializes that field
	person.name = "Sue" + ""
	// the old value, new field value,
	// and sibling title are all read
	old.length + person.name.length + person.title.length
}
f()
Landing in This Release
func f() -> Int {
	loop {
		// s owns the concatenated String
		// inside the loop body
		let s = "a" + "b"
		// break now exits only after the
		// owned loop local is dropped
		break
	}
	0
}
f()
Borrowed Parameters and Mutable Receivers Speak Up

Borrow types are now part of the surface language: &T for shared access and &mut T for exclusive access. Calls can auto-borrow owned arguments for shared parameters without stamping the argument expression itself as borrowed.

Methods also gained clearer receiver modes. Plain methods receive shared self, mut func receives mutable self, and attempts to assign through shared self are diagnosed.

// no-core
struct String {
	let length: Int
}
// len accepts a shared borrow of
// String
func len(s: &String) -> Int { s.length }
// s is passed to len by
// auto-borrow
let s = String(length: 4)
// s remains usable after the
// borrowed call
let y = len(s)
let z = s.length
// no-core
struct Counter {
	let n: Int
	// mut func gives bump mutable
	// access to self
	mut func bump() -> () {
		// self.n assignment is allowed
		// only through the mutable
		// receiver
		self.n = 2
		()
	}
}
Consuming Calls Are Now Explicit Customers

The parser, type checker, formatter, and highlighter now understand consuming func. Tests updated protocol and method examples where the receiver is intentionally taken by value.

That matters for generic methods and protocol dispatch: a consuming requirement says the call may use up the receiver, while an ordinary method reads through a shared self.

struct Wrapper<T> {
	let wrapped: T
	// getWrapped is declared
	// consuming, so self is taken by
	// value
	consuming func getWrapped() {
		// self.wrapped can be returned
		// from the consumed wrapper
		self.wrapped
	}
}
// the same consuming generic
// method works for Float too
Wrapper(wrapped: 123).getWrapped()
Wrapper(wrapped: 1.23).getWrapped()
protocol Addy {
	associated RHS
	associated Ret
	// the protocol requirement marks
	// addy as consuming
	consuming func addy(rhs: RHS) -> Ret
}
extend Int: Addy {
	// Int's conformance matches the
	// consuming receiver mode
	consuming func addy(rhs: Int) -> Int { self }
}
// the member call dispatches
// through that consuming
// requirement
1.addy(2)
The Command Line Opens the Press Room

Several tools got more direct: talk bytecode dumps the scheduled VM module, talk build can emit bytecode or link a standalone executable, and run-bytecode runs a serialized image.

Formatting also gained a width option for wrapping comments, and talk llm now prints the compact language reference used by tool-facing helpers.

permalinkrecent entriesarchive
The Daily Talk - What's New in Talk - Release Notes for June 25, 2026