Part of #459. Not a first issue — the code is small but there is a design question to settle before writing it, and the answer touches wingfoil's tick semantics.
What: start_with(value) gives a stream an initial value before its source produces anything.
Why: downstream ops that need a value present at graph start currently have to be wired around (merge with a constant, or a Default bound). This is the ergonomic form.
The decision to make first
Two defensible readings, and they behave differently downstream:
(a) Emit at start — a real tick. The initial value propagates like any other, so downstream ops fire once before the source has produced anything. Rx's startWith semantics. Shape: a start hook that schedules at start_time, then passes the source through. Reference: Ticker / Const (crates/wingfoil/src/ops.rs, the source ops with start hooks).
(b) Seed the value slot silently — Tick::Silent. The value is readable by anything sampling this stream, but nothing downstream is woken by it. This is what delay uses Silent for, and it is the reading that avoids a spurious cycle at graph start.
Recommendation: (a), because it matches every other library's start_with and the principle of least surprise for people arriving from Rx — with the rustdoc pointing at Tick::Silent and join_passive for anyone who wanted (b). But this needs a maintainer ruling before implementation; comment on this issue before starting.
Worth checking during the decision: whether (a) interacts correctly with RunMode::HistoricalFrom — the initial tick must land at start_time deterministically, not at whatever the first source tick happens to be.
Shape (assuming (a))
|
|
Cfg |
T — the initial value |
State |
bool — whether the initial value has been emitted |
In<'a> |
(&'a T,) |
Out |
T |
ACTIVATION |
Activation::SCHEDULES (it schedules its own initial tick) |
| Attribute |
#[op(build = start_with, fluent)] — lifecycle-hook shape, hooks are attached automatically |
Reference ops: Const and Ticker for the start-hook-that-schedules pattern; Delay for how Tick::Silent behaves if (b) wins.
Acceptance
Getting started
Run /new-op start_with (.claude/commands/new-op.md). Background: docs/adding-an-op.md. Branch from main, PR base main.
Part of #459. Not a first issue — the code is small but there is a design question to settle before writing it, and the answer touches wingfoil's tick semantics.
What:
start_with(value)gives a stream an initial value before its source produces anything.Why: downstream ops that need a value present at graph start currently have to be wired around (
mergewith aconstant, or aDefaultbound). This is the ergonomic form.The decision to make first
Two defensible readings, and they behave differently downstream:
(a) Emit at start — a real tick. The initial value propagates like any other, so downstream ops fire once before the source has produced anything. Rx's
startWithsemantics. Shape: astarthook that schedules atstart_time, then passes the source through. Reference:Ticker/Const(crates/wingfoil/src/ops.rs, the source ops withstarthooks).(b) Seed the value slot silently —
Tick::Silent. The value is readable by anything sampling this stream, but nothing downstream is woken by it. This is whatdelayusesSilentfor, and it is the reading that avoids a spurious cycle at graph start.Recommendation: (a), because it matches every other library's
start_withand the principle of least surprise for people arriving from Rx — with the rustdoc pointing atTick::Silentandjoin_passivefor anyone who wanted (b). But this needs a maintainer ruling before implementation; comment on this issue before starting.Worth checking during the decision: whether (a) interacts correctly with
RunMode::HistoricalFrom— the initial tick must land atstart_timedeterministically, not at whatever the first source tick happens to be.Shape (assuming (a))
CfgT— the initial valueStatebool— whether the initial value has been emittedIn<'a>(&'a T,)OutTACTIVATIONActivation::SCHEDULES(it schedules its own initial tick)#[op(build = start_with, fluent)]— lifecycle-hook shape, hooks are attached automaticallyReference ops:
ConstandTickerfor thestart-hook-that-schedules pattern;Delayfor howTick::Silentbehaves if (b) wins.Acceptance
Opimpl incrates/wingfoil/src/ops.rswith#[op(build = start_with, fluent)]StreamOpsinsrc/fluent.rsnitro!block incrates/wingfoil/tests/op_completeness.rs, or consciously classified into one of that file's fluent-only listsRunMode::HistoricalFrom(NanoTime::ZERO)— in particular what time the initial value lands atGetting started
Run
/new-op start_with(.claude/commands/new-op.md). Background:docs/adding-an-op.md. Branch frommain, PR basemain.