Skip to content

Commit bad5d1d

Browse files
committed
new interface doc
1 parent 5fbc171 commit bad5d1d

File tree

2 files changed

+744
-0
lines changed

2 files changed

+744
-0
lines changed

changes/proposals/pact.md

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
# Pact
2+
3+
**Pact** - Package interface specification for Theater.
4+
5+
## Status
6+
7+
Exploratory design document. Capturing ideas from initial exploration.
8+
9+
## Motivation
10+
11+
Theater has different goals and constraints than wasmtime, so it is time to re-evaluate the design decisions that we have inherited from WIT. Wasmtime made many decisions in the name of safety, with the goal of running untrusted code together on the same machine. Fundamentally, their atom is the Component. Theater's atom is the actor, which could be composed of multiple packages.
12+
13+
Pact is Theater's answer to WIT - a type system for describing package interfaces, designed for first-class manipulation by packages themselves.
14+
15+
## Core Primitives
16+
17+
### 1. Types
18+
19+
Primitive types:
20+
```
21+
bool, u8, u16, u32, u64, s8, s16, s32, s64, f32, f64, char, string
22+
```
23+
24+
### 2. Type Constructors
25+
26+
Type constructors are functions from types to types:
27+
```
28+
list: Type -> Type
29+
option: Type -> Type
30+
result: (Type, Type) -> Type
31+
```
32+
33+
### 3. Records
34+
35+
Named product types:
36+
```
37+
record point {
38+
x: f32,
39+
y: f32,
40+
}
41+
```
42+
43+
### 4. Variants
44+
45+
Tagged unions:
46+
```
47+
variant shape {
48+
circle(f32),
49+
rectangle(f32, f32),
50+
point,
51+
}
52+
```
53+
54+
### 5. Functions
55+
56+
First-class functions with explicit signatures:
57+
```
58+
func(s32, s32) -> s32
59+
```
60+
61+
Functions can be:
62+
- Passed as values
63+
- Returned from other functions
64+
- Stored in records
65+
66+
### 6. Interfaces
67+
68+
Interfaces are first-class values describing the full contract of a package - what it imports and what it exports.
69+
70+
```
71+
interface calculator {
72+
imports { logger, types.big-number }
73+
exports {
74+
add: func(big-number, big-number) -> big-number;
75+
sub: func(big-number, big-number) -> big-number;
76+
}
77+
}
78+
```
79+
80+
### 7. Metadata
81+
82+
Interfaces can carry typed metadata using `@` annotations:
83+
84+
```
85+
interface calculator {
86+
@version: string = "1.2.3"
87+
@author: string = "colin"
88+
@retry-count: u32 = 3
89+
@config: CalculatorConfig = { timeout: 30, debug: false }
90+
91+
imports { ... }
92+
exports { ... }
93+
}
94+
```
95+
96+
- Built-in metadata (e.g., `@version`) - Pack understands these
97+
- User-defined metadata - any `@name: Type = value`
98+
- Metadata flows with the interface when passed around
99+
- Packages can inspect metadata: `calculator.@version`
100+
101+
As first-class values, interfaces can be:
102+
- Passed to functions
103+
- Returned from functions
104+
- Stored in records
105+
- Manipulated programmatically
106+
107+
This moves interface operations into package space - instead of special tooling with hardcoded operations, packages can write whatever interface manipulations they need.
108+
109+
## Syntax
110+
111+
- Comments: `//`
112+
- Terminators: semicolons (`;`)
113+
- Blocks: braces (`{ }`)
114+
- Type annotations: colon (`:`)
115+
- Metadata: `@name: Type = value;`
116+
117+
## File Structure
118+
119+
Pact files live in a `pact/` directory:
120+
121+
```
122+
pact/
123+
calculator.pact
124+
logger.pact
125+
types.pact
126+
```
127+
128+
No special `package` or `world` declarations. Each file defines interfaces. Reference other files with dot notation:
129+
130+
```
131+
// In calculator.pact
132+
interface calculator {
133+
imports {
134+
logger.log, // function from logger.pact
135+
types.BigNum // type from types.pact
136+
}
137+
exports {
138+
add: func(types.BigNum, types.BigNum) -> types.BigNum
139+
}
140+
}
141+
```
142+
143+
Namespacing via nested interfaces:
144+
145+
```
146+
interface my-org {
147+
interface calculator { ... }
148+
interface logger { ... }
149+
}
150+
151+
// Access: my-org.calculator.add
152+
```
153+
154+
Versioning via nesting or metadata:
155+
156+
```
157+
interface calculator {
158+
@version: string = "2.0.0"
159+
...
160+
}
161+
162+
// Or nested versions
163+
interface calculator {
164+
interface v1 { ... }
165+
interface v2 { ... }
166+
}
167+
```
168+
169+
## Type Operations
170+
171+
### On Types
172+
173+
Standard type constructors:
174+
```
175+
list<T> // List of T
176+
option<T> // Optional T
177+
result<T, E> // Success T or error E
178+
tuple<T, U, ...> // Product type
179+
```
180+
181+
### On Interfaces
182+
183+
Interfaces are data. Write functions that operate on them:
184+
185+
```
186+
// Compose two interfaces
187+
fn compose(a: Interface, b: Interface) -> Interface {
188+
// Merge imports, merge exports, check for conflicts
189+
}
190+
191+
// Check compatibility
192+
fn satisfies(provider: Interface, consumer: Interface) -> bool {
193+
// Does provider export what consumer imports?
194+
}
195+
196+
// Subset exports
197+
fn only(i: Interface, funcs: list<string>) -> Interface {
198+
// Return interface with only specified exports
199+
}
200+
201+
// Transform all function signatures
202+
fn wrap_results(i: Interface) -> Interface {
203+
// Wrap each export's return type in result<T, error>
204+
}
205+
```
206+
207+
No blessed operations - packages define whatever manipulations they need. The type system provides the primitives; you build the operations.
208+
209+
## What This Enables
210+
211+
### Programmatic Package Composition
212+
213+
Instead of static manifest files wiring packages together, write code that composes interfaces:
214+
215+
```
216+
fn build_system() -> Interface {
217+
let calc = load_interface("calculator.wasm");
218+
let logger = load_interface("logger.wasm");
219+
220+
// Check compatibility
221+
if !satisfies(logger, calc.imports) {
222+
error("logger doesn't satisfy calculator's imports");
223+
}
224+
225+
// Compose into a system
226+
compose(calc, logger)
227+
}
228+
```
229+
230+
### Custom Tooling
231+
232+
Build whatever interface tools you need:
233+
- Compatibility checkers
234+
- Binding generators
235+
- Adapter synthesizers
236+
- Documentation extractors
237+
238+
These are just packages that operate on interfaces - no special tooling required.
239+
240+
### Runtime Capabilities
241+
242+
Interfaces can also be used at runtime as typed capabilities:
243+
244+
```
245+
fn setup() {
246+
let calc: Calculator = bind(calc_actor_id);
247+
worker.give_calculator(calc); // Pass capability
248+
}
249+
```
250+
251+
The same first-class interface serves both compile-time manipulation and runtime capability passing.
252+
253+
## Relationship to Existing Systems
254+
255+
### Pack Compiler
256+
257+
Pack currently has hardcoded interface operations. With first-class interfaces:
258+
- Pack becomes simpler - it provides primitives, not operations
259+
- Interface manipulation moves to packages
260+
- Users can extend/customize without modifying Pack
261+
262+
### Handler Matching
263+
264+
Handlers can be written as packages that operate on interfaces:
265+
- Inspect an interface's imports
266+
- Claim interfaces they can satisfy
267+
- No special handler registration - just interface matching
268+
269+
### RPC
270+
271+
RPC is just one pattern built on first-class interfaces:
272+
- `bind(actor_id)` returns a capability (interface bound to an actor)
273+
- Pass capabilities between actors
274+
- No special RPC mechanism - packages implement whatever patterns they need
275+
276+
## Generics
277+
278+
Type parameters are declared in the interface body with `type`:
279+
280+
```
281+
interface storage {
282+
type T: Serializable; // Type param with constraint
283+
284+
exports {
285+
get: func() -> T;
286+
set: func(T) -> ();
287+
}
288+
}
289+
```
290+
291+
Constraints use interface names - no separate trait system. `T: Serializable` means T must satisfy the `Serializable` interface.
292+
293+
Instantiation mirrors the body style:
294+
295+
```
296+
storage { T = User }
297+
```
298+
299+
Multiple type parameters:
300+
301+
```
302+
interface pair {
303+
type A;
304+
type B;
305+
}
306+
307+
pair { A = string, B = u32 }
308+
```
309+
310+
## Next Steps
311+
312+
1. [ ] Implement Pact parser in Pack
313+
2. [ ] Make interfaces introspectable at runtime (first-class)
314+
3. [ ] Define built-in metadata (`@version`, others?)

0 commit comments

Comments
 (0)