talk_dev's Journal

Talk of the Town

Today's Typecheck
talk_dev posted a new release

Talk and C, Sitting in a Binary Tree

TalkTalk opens a C-facing compiler API, adds a Swift package on top, gives protocols real arguments, and lets more leading-dot variants find their type from context.


Today's Talk issue is about making the language easier to host and easier to write. The full TalkTalk compiler now has a C front door, Swift gets a package that rides the same bridge, protocols can be parameterized by the conformance you mean, leading-dot variants need less ceremony, and LSP rename covers more of the surface developers actually touch.

Talk-C Opens the Compiler Room

talk-c is the new embedding story for the full TalkTalk compiler. It is not just a parser shim or a syntax highlighter hook: C callers can drive the same compiler pipeline used by the command line, from source text through diagnostics and compiled output.

The boundary stays deliberately plain. C code deals in compiler handles, option structs, source buffers, result objects, and explicit destruction, while TalkTalk keeps ownership of the compiler internals. That makes the API useful to build systems, editors, test harnesses, and host applications without asking them to shell out to talk.

The Compiler From C
#include <talk-c/talk.h>

TalkCompiler *c =
    talk_compiler_create();

TalkOptions opts =
    talk_options_default();
opts.module_name = "Demo";

TalkResult res =
    talk_compile_source(
        c,
        "Demo.tlk",
        source,
        opts
    );

if (!res.ok) {
    talk_diagnostics_write(
        res.diagnostics,
        stderr
    );
}

talk_result_destroy(res);
talk_compiler_destroy(c);
Swift Gets the Same Front Door

The new talk-swift Swift package builds on that C layer instead of inventing a second compiler interface. Swift tools can import a package, compile Talk source, inspect diagnostics, and keep using the same compiler behavior that C embedders and the CLI see.

That matters for editor integrations and app-hosted Talk. The Swift side can feel native while still sharing the boring correctness properties of the C ABI: stable handles at the boundary, explicit result lifetimes under the hood, and one compiler implementation behind every host language.

import TalkSwift

let compiler = TalkCompiler()
let result = try compiler.compile(
    source: source,
    filename: "Demo.tlk"
)

for diagnostic in result.diagnostics {
    print(diagnostic.message)
}
TalkTalk in your pocket.
Protocol Arguments Give Conformances Names

Protocols can now take arguments directly, rather than forcing every variation through associated types. The protocol arguments are part of the conformance identity, so one concrete type can conform to the same protocol more than once as long as the arguments are different.

That unlocks ordinary modeling patterns that associated types made awkward. A value can encode itself as JSON and as a row format, a collection can expose more than one index discipline, and a bridge type can satisfy the same protocol for several foreign representations without wrapper types whose only job is to make the conformance table unique.

struct JSON {
	let text: String
}

struct Row {
	let text: String
}

protocol Encode<Wire> {
	func encode() -> Wire
}

struct User {
	let name: String
}

extend User: Encode<JSON> {
	func encode() -> JSON {
		JSON(text: self.name)
	}
}

extend User: Encode<Row> {
	func encode() -> Row {
		Row(text: self.name)
	}
}

Associated types say what a conformance does.
Protocol arguments say what a conformance is.- Yoda (Empire Strikes Back)

Leading Dots Read the Room

Leading-dot enum spelling keeps spreading from explicit call sites into inferred contexts. When the expected type is already known, Talk can now infer the enum behind a bare variant in more places, including return positions, annotated lets, arrays, and nested expressions.

The result is the same value with less repeated type noise. Programmers still get a real type check - an ambiguous dot stays ambiguous - but ordinary code can use .home, .user(id), or .notFound where the surrounding expression has already named Route.

Inferred Variants
enum Route {
	case home
	case user(Int)
	case notFound
}

func fallback(id: Int) -> Route {
	if id > 0 { .user(id) } else {
		.notFound
	}
}

let routes: Array<Route> = [
	.home,
	.user(42),
	.notFound
]

func push(route: Route) -> () { () }

push(.home)
Rename Follows More of the Language

The LSP rename work continues past the easy local-variable case. The server now has more syntax-aware paths for declarations and references that show up through protocol arguments, extension conformances, enum variants, and the leading-dot shorthand that hides the enum name at the use site.

That is especially important now that more meaning lives in surface syntax instead of wrapper types and spelling conventions. Rename can update more real programs, and when the server cannot prove a rename is safe, it is expected to refuse the edit rather than guess across generated or ambiguous references.

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