One resolver
OneShot.make() returns an aliased value for waiters and a unique resolver for its producer. Resolving consumes that capability, so checked Ship cannot publish twice.
let publishpublish: fn(resolver: OneShot.Resolver(Int) @ unique, value: Int) -> Unit = fn(resolverresolver: OneShot.Resolver(Int): OneShot.Resolver(Int) @ unique, valuevalue: Int) {
OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Int) @ unique, value: Int @ global) -> Unit(resolverresolver: OneShot.Resolver(Int), valuevalue: Int);
};
One Resolution
let (~valuevalue: OneShot(Int), ~resolverresolver: OneShot.Resolver(Int)) = OneShot.makemake: pure fn!('a)() -> (value: OneShot(Int), resolver: OneShot.Resolver(Int) @ unique)();
publishpublish: fn(resolver: OneShot.Resolver(Int) @ unique, value: Int) -> Unit(resolverresolver: OneShot.Resolver(Int), 4141: Int);
✓valuevalue: OneShot(Int): OneShot(Int);
expect ✓await valuevalue: OneShot(Int) == 4141: Int;Waiting without blocking
Waiting parks only the current fiber. Another child can make progress and complete the value on the same event loop.
Waiting Fiber
let (~value: startedstarted: OneShot(Unit), ~resolver: startstart: OneShot.Resolver(Unit)) = OneShot.makemake: pure fn!('a)() -> (value: OneShot(Unit), resolver: OneShot.Resolver(Unit) @ unique)();
let (~value: releaserelease: OneShot(Int), ~resolver: release_withrelease_with: OneShot.Resolver(Int)) = OneShot.makemake: pure fn!('a)() -> (value: OneShot(Int), resolver: OneShot.Resolver(Int) @ unique)();
let childchild: Async.Task(Int) = spawn {
OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Unit) @ unique, value: Unit @ global) -> Unit(startstart: OneShot.Resolver(Unit), ());
await releaserelease: OneShot(Int) + 11: Int
};
await startedstarted: OneShot(Unit);
OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Int) @ unique, value: Int @ global) -> Unit(release_withrelease_with: OneShot.Resolver(Int), 4141: Int);
expect ✓await childchild: Async.Task(Int) == 4242: Int;A callback waits the same way. twice never waits on its own; the fiber that calls it parks wherever its callback does.
let twicetwice: fn(step: fn() -> Int) -> Int = fn(stepstep: fn() -> Int) { stepstep: fn() -> Int() + stepstep: fn() -> Int() };
Waiting Inside A Callback
let (~value: startedstarted: OneShot(Unit), ~resolver: startstart: OneShot.Resolver(Unit)) = OneShot.makemake: pure fn!('a)() -> (value: OneShot(Unit), resolver: OneShot.Resolver(Unit) @ unique)();
let (~value: releaserelease: OneShot(Int), ~resolver: release_withrelease_with: OneShot.Resolver(Int)) = OneShot.makemake: pure fn!('a)() -> (value: OneShot(Int), resolver: OneShot.Resolver(Int) @ unique)();
let childchild: Async.Task(Int) = spawn {
OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Unit) @ unique, value: Unit @ global) -> Unit(startstart: OneShot.Resolver(Unit), ());
twicetwice: fn(step: fn() -> Int) -> Int(() => await releaserelease: OneShot(Int))
};
await startedstarted: OneShot(Unit);
OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Int) @ unique, value: Int @ global) -> Unit(release_withrelease_with: OneShot.Resolver(Int), 2020: Int);
expect ✓await childchild: Async.Task(Int) == 4040: Int;The resolver remains unique across control-flow paths. Every path may consume it once; no path can consume it twice.
let resolve_branchresolve_branch: fn(flag: Bool) -> Int = fn(flagflag: Bool) {
let (~valuevalue: OneShot(Int), ~resolverresolver: OneShot.Resolver(Int)) = OneShot.makemake: pure fn!('a)() -> (value: OneShot(Int), resolver: OneShot.Resolver(Int) @ unique)();
if flagflag: Bool {
OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Int) @ unique, value: Int @ global) -> Unit(resolverresolver: OneShot.Resolver(Int), 11: Int);
} else {
OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Int) @ unique, value: Int @ global) -> Unit(resolverresolver: OneShot.Resolver(Int), 22: Int);
}
await valuevalue: OneShot(Int)
};
let resolve_matchresolve_match: fn(flag: Bool) -> Int = fn(flagflag: Bool) {
let (~valuevalue: OneShot(Int), ~resolverresolver: OneShot.Resolver(Int)) = OneShot.makemake: pure fn!('a)() -> (value: OneShot(Int), resolver: OneShot.Resolver(Int) @ unique)();
match flagflag: Bool {
| True -> OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Int) @ unique, value: Int @ global) -> Unit(resolverresolver: OneShot.Resolver(Int), 33: Int)
| False -> OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Int) @ unique, value: Int @ global) -> Unit(resolverresolver: OneShot.Resolver(Int), 44: Int)
}
await valuevalue: OneShot(Int)
};
let resolve_if_letresolve_if_let: fn(candidate: Option(Int)) -> Int = fn(candidatecandidate: Option(Int)) {
let (~valuevalue: OneShot(Int), ~resolverresolver: OneShot.Resolver(Int)) = OneShot.makemake: pure fn!('a)() -> (value: OneShot(Int), resolver: OneShot.Resolver(Int) @ unique)();
if let Some(itemitem: Int) = candidatecandidate: Option(Int) {
OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Int) @ unique, value: Int @ global) -> Unit(resolverresolver: OneShot.Resolver(Int), itemitem: Int);
} else {
OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Int) @ unique, value: Int @ global) -> Unit(resolverresolver: OneShot.Resolver(Int), 55: Int);
}
await valuevalue: OneShot(Int)
};
Resolution Paths
expect ✓(resolve_branchresolve_branch: fn(flag: Bool) -> Int(TrueTrue: Bool), resolve_branchresolve_branch: fn(flag: Bool) -> Int(FalseFalse: Bool)) == (11: Int, 22: Int);
expect ✓(resolve_matchresolve_match: fn(flag: Bool) -> Int(TrueTrue: Bool), resolve_matchresolve_match: fn(flag: Bool) -> Int(FalseFalse: Bool)) == (33: Int, 44: Int);
expect ✓(resolve_if_letresolve_if_let: fn(candidate: Option(Int)) -> Int(Some(66: Int)), resolve_if_letresolve_if_let: fn(candidate: Option(Int)) -> Int(None)) == (66: Int, 55: Int);Completion and join order
Completion order and join order are independent. A later child may finish first; joining the earlier child still returns its own value.
type OrderProbe = {
mut events: Vector(String);
}
let recordrecord: fn(probe: Book.Concurrency.Coordination.OrderProbe @ global, event: String @ global) -> Unit = fn(probeprobe: Book.Concurrency.Coordination.OrderProbe, eventevent: String) {
probe.events := Vector.pushpush: pure fn!('a)(items: Vector(String), item: String) -> Vector(String)(probeprobe: Book.Concurrency.Coordination.OrderProbe.eventsevents: Vector(String), eventevent: String);
};
Completion Before Join
let probeprobe: Book.Concurrency.Coordination.OrderProbe: OrderProbe @ global = OrderProbe { events = []; };
let (~value: release_firstrelease_first: OneShot(Unit), ~resolver: first_withfirst_with: OneShot.Resolver(Unit)) = OneShot.makemake: pure fn!('a)() -> (value: OneShot(Unit), resolver: OneShot.Resolver(Unit) @ unique)();
let (~value: release_secondrelease_second: OneShot(Unit), ~resolver: second_withsecond_with: OneShot.Resolver(Unit)) = OneShot.makemake: pure fn!('a)() -> (value: OneShot(Unit), resolver: OneShot.Resolver(Unit) @ unique)();
let (~value: second_finishedsecond_finished: OneShot(Unit), ~resolver: finish_secondfinish_second: OneShot.Resolver(Unit)) = OneShot.makemake: pure fn!('a)() -> (value: OneShot(Unit), resolver: OneShot.Resolver(Unit) @ unique)();
let firstfirst: Async.Task(Int) = spawn {
await release_firstrelease_first: OneShot(Unit);
recordrecord: fn(probe: Book.Concurrency.Coordination.OrderProbe @ global, event: String @ global) -> Unit(probeprobe: Book.Concurrency.Coordination.OrderProbe, "first""first": String);
11: Int
};
let secondsecond: Async.Task(Int) = spawn {
await release_secondrelease_second: OneShot(Unit);
recordrecord: fn(probe: Book.Concurrency.Coordination.OrderProbe @ global, event: String @ global) -> Unit(probeprobe: Book.Concurrency.Coordination.OrderProbe, "second""second": String);
OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Unit) @ unique, value: Unit @ global) -> Unit(finish_secondfinish_second: OneShot.Resolver(Unit), ());
22: Int
};
OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Unit) @ unique, value: Unit @ global) -> Unit(second_withsecond_with: OneShot.Resolver(Unit), ());
await second_finishedsecond_finished: OneShot(Unit);
OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Unit) @ unique, value: Unit @ global) -> Unit(first_withfirst_with: OneShot.Resolver(Unit), ());
expect ✓await firstfirst: Async.Task(Int) == 11: Int;
expect ✓await secondsecond: Async.Task(Int) == 22: Int;
expect ✓probeprobe: Book.Concurrency.Coordination.OrderProbe.eventsevents: Vector(String) == ["second""second": String, "first""first": String];Joining a later-spawned child does not implicitly join or cancel its older sibling.
Join Later Child First
let (~value: release_firstrelease_first: OneShot(Unit), ~resolver: first_withfirst_with: OneShot.Resolver(Unit)) = OneShot.makemake: pure fn!('a)() -> (value: OneShot(Unit), resolver: OneShot.Resolver(Unit) @ unique)();
let firstfirst: Async.Task(Int) = spawn {
await release_firstrelease_first: OneShot(Unit);
11: Int
};
let secondsecond: Async.Task(Int) = spawn {
22: Int
};
expect ✓await secondsecond: Async.Task(Int) == 22: Int;
OneShot.resolveresolve: pure fn!('a)(resolver: OneShot.Resolver(Unit) @ unique, value: Unit @ global) -> Unit(first_withfirst_with: OneShot.Resolver(Unit), ());
expect ✓await firstfirst: Async.Task(Int) == 11: Int;OneShot stores successful coordination values only. Producer errors use the owning fiber's structural error channel instead of becoming a second future-shaped failure container.