Shared Code

AI authored to showcase ironpad capabilities.

Introduction

Every cell in a notebook can call into a common shared module. There are two ways to put code there, and this notebook uses both kinds of visibility deliberately:

  • Shared cells (the amber ones below) sit inline with the narrative. They never run on their own; their contents are appended to shared.rs and every cell can call them as shared::.... Use them when the shared code is part of the story.
  • The notebook-level shared source (the collapsed appendix at the bottom of view mode, or the pencil panel in the editor) holds plumbing that readers don't need to see.

Below, two amber shared cells define format_greeting and fibonacci, and the ordinary cells after them consume both. Along the way: what shared cells do at compile time, the assembly order, and one sharp edge around feature detection.

Why shared cells exist

The notebook-level shared source came first, and it has one failure mode: everything you share disappears into a single collapsed blob at the bottom of the page. Fine for plumbing; terrible for teaching. A notebook that says "call shared::simulate_step" while hiding simulate_step in an appendix pulls the reader away from the argument right when the code matters most.

Shared cells fix that. When a helper is load-bearing for the narrative, promote it to a shared cell and it sits inline, amber, right where the story needs it. format_greeting below is 3 lines; fibonacci is 15. Hiding either would cost more comprehension than it saves in scroll height.

format_greeting (shared)⬡ shared
fibonacci (shared)⬡ shared

What a shared cell actually is

Three rules pin down the semantics, and all three fall out of one fact: a shared cell is source text, not an execution unit.

  1. Shared cells never execute. No run button, no output; the source is appended to shared.rs and compiled into every other cell.
  2. They hold an empty piping slot. Cells consume upstream outputs positionally (cell0, cell1, ...), and every cell, shared or markdown included, occupies one index in that numbering. A shared cell's slot is simply empty. The flip side: inserting any cell above a producer shifts the indices its consumers reference, so add narrative cells below the piping chains they discuss.
  3. Editing one stales every code cell. The shared source is part of each cell's blake3 cache key, so one changed character invalidates every compiled blob in the notebook. Correct, but it makes a keystroke in shared code cost a full recompile pass.

For a mental model: shared cells are header files that happen to live in the essay. Now the consumers.

Greeting
Fibonacci

Assembly order

The notebook builds one shared.rs, and the recipe is deterministic: the notebook-level shared source first, then every shared cell top to bottom in notebook order, joined by blank lines. This notebook has no notebook-level shared source, so the generated file is exactly the two amber cells:

// shared.rs, as generated for this notebook
pub fn format_greeting(name: &str) -> String { /* amber cell 1 */ }

pub fn fibonacci(n: u64) -> u64 { /* amber cell 2 */ }
  • One namespace inside, one prefix outside. Shared cells and the notebook-level shared source share a single module and call each other with no prefix; ordinary cells reach in through shared::.
  • Order is cosmetic to rustc. Item order in a Rust file is irrelevant, so moving a shared cell changes only the readability of the generated shared.rs.

A shared_cargo_toml rides along too (here, a small [profile.release] tweak); its dependencies merge into every cell's build the same way.

The next cell leans on shared::fibonacci harder: consecutive Fibonacci ratios converge on the golden ratio, 1.618, within 10 terms.

Golden ratio convergence

The feature-detection caveat

This is the sharp edge. ironpad picks heavyweight build features by substring scanning the text it compiles: the cell's own source plus the entire shared source. std::simd or core::simd anywhere in that text opts the cell into SIMD codegen; rayon in the merged dependencies triggers an atomics build with a full std rebuild; the autodiff intrinsics pull in the Enzyme toolchain.

Because shared text is appended to every cell's compile input, one trigger substring in a shared cell opts every cell in the notebook in. The scan is textual, so a comment counts:

// A shared cell containing only this comment:
//   "We could vectorize this with std::simd later."
// ...now builds EVERY cell with -C target-feature=+simd128.

That one is mostly harmless (simd128 is cheap and universal in modern browsers); the same slip with rayon in shared_cargo_toml forces a far slower -Zbuild-std atomics rebuild on every cell. The rule: keep heavyweight triggers out of shared code; if one cell needs SIMD, mention SIMD in that cell. Markdown is never scanned, which is why this cell gets to say all of this freely.

Global shared source or shared cell?

The mechanics are identical (same module, same cache behavior, same feature-detection blast radius), so the choice is purely editorial:

  • Shared cell when the code is part of the argument. If a reader who skips it will misunderstand the cells that follow, it belongs inline and amber.
  • Notebook-level shared source when the code is plumbing: serde structs, constants, the 60 lines of setup nobody needs to read. The collapsed appendix is the right visibility for code whose only job is to exist.

Either way, one keystroke in shared code recompiles the whole notebook, so shared code is the right home for code that has stopped changing. Draft in a normal cell; promote once it settles.

One last cell pulls both helpers together.

Combined