Skip to content

Add start_with(value) combinator — needs a semantics decision first #802

Description

@0-jake-0

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

  • Semantics ruling recorded in this issue before implementation
  • Op impl in crates/wingfoil/src/ops.rs with #[op(build = start_with, fluent)]
  • Fluent method declared on StreamOps in src/fluent.rs
  • Exercised in a nitro! block in crates/wingfoil/tests/op_completeness.rs, or consciously classified into one of that file's fluent-only lists
  • Test asserting exact values and tick times under RunMode::HistoricalFrom(NanoTime::ZERO) — in particular what time the initial value lands at
  • Rustdoc stating the chosen semantics explicitly and naming the alternative

Getting started

Run /new-op start_with (.claude/commands/new-op.md). Background: docs/adding-an-op.md. Branch from main, PR base main.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-designAPI design and ergonomicscoreCore Rust library functionalityenhancementNew feature or requestpriority: lowLow prioritysize: smallSmall effort (< 1 day)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions