4.12 Experimental Rule Patterns

ctx.actions.map_directory(), rule(parent = ...), subrule(), and materializer_rule() all look like tempting answers to the same complaint: ordinary rule authoring can feel too static. You declare attrs, analyze configured targets, register actions, and live inside the graph Bazel can understand ahead of time. Then a generated directory appears, a baseline rule needs local specialization, a helper wants private tools, or a dependency edge should not be resolved until it is really needed.

child_rule = rule(
    implementation = _child_impl,
    parent = base_rule,
)

This section is deliberately last. You should first have the ordinary contract from 4.2 Custom Rules, Providers & Actions, testing discipline from 4.5 Rule Testing & Documentation, and public-surface caution from 4.11 Ruleset Packaging & Publishing. Experimental APIs are not permission to abandon that model. They are controlled bends in Bazel's static architecture, each with a narrow boundary, an experimental status note, version sensitivity, and a design cost.

The Design Review Questions

For generated directories, ask whether the action graph needs scoped dynamism inside declared tree-artifact boundaries. 4.12.1 Dynamic Actions with ctx.actions.map_directory() starts from the familiar action model in 4.2.2 Actions, then shows the exception: Bazel can call a top-level template callback after input directories exist, letting the rule register follow-up actions from directory listings. The boundary is "declared directories in, declared output directories out," not "execution can invent the graph". Filenames can drive scoped follow-up actions, but file contents and arbitrary labels remain outside the callback's power.

For rule-family reuse, ask whether the relationship is public substitutability or internal composition. 4.12.2 Rule Extension API: parent + ctx.super() covers IS-A composition: a child rule extends a parent rule, calls ctx.super(), and preserves or augments the parent's provider contract. 4.12.3 Subrules covers HAS-A composition: a rule composes smaller reusable helpers with private attrs, private tools, and a smaller context. Many reuse problems still belong to a symbolic macro, an ordinary Starlark helper, or a better provider boundary.

For deferred dependencies, ask whether eager analysis is the real problem or only a convenient explanation. 4.12.4 Dormant Dependency Materialization moves the dependency-resolution boundary: materializer rules and dormant dependencies model possible edges that should not all be eagerly analyzed. The Level 4 lesson stays rule-author focused: define the resolver target, provider contract, attribute hooks, and tests before imagining scale wins. The broader operational payoff belongs later in 6.6.9 Bottleneck-Driven Optimization.

Experimental Means Burden Of Proof

The common mistake is to treat "experimental" as a small warning label on an otherwise normal API. Here it is part of the concept. These APIs are version-sensitive, and several exist because Bazel is exploring how to express patterns that ordinary macros, rules, providers, toolchains, aspects, repository rules, and module extensions do not model cleanly enough.

That does not make them bad APIs. It changes the adoption bar. A production ruleset should be able to say why stable mechanisms are insufficient, which Bazel versions are supported, what public contract downstream users see, and which tests from 4.5.1 Analysis-Phase Testing or 4.5.2 Ruleset Integration Tests would catch a behavior change. If the answer is only "this avoids copying code" or "this might be faster," step back to symbolic macros, ordinary Starlark helpers, providers, or ordinary attrs first.

How To Read This Last

Do not read this section as the normal next step after 4.2 Custom Rules, Providers & Actions. Read 4.12.1 Dynamic Actions with ctx.actions.map_directory() when tree artifacts or generated directories are the real pressure. Read 4.12.2 Rule Extension API: parent + ctx.super() and 4.12.3 Subrules when a rule family has a concrete reuse problem and you can name whether the relationship is public specialization or private helper composition. Save 4.12.4 Dormant Dependency Materialization for dormant-dependency designs, not ordinary optional deps, configuration choices, or vague analysis-performance hopes.

For a first pass through Level 4, it is enough to learn the vocabulary and the adoption bar. Return here when a stable mechanism would misrepresent the graph shape, and bring the testing and publishing discipline from 4.5 Rule Testing & Documentation and 4.11 Ruleset Packaging & Publishing with you.

think

Compare: A proposed experimental API can either express a graph shape that stable APIs cannot model honestly or merely remove boilerplate from a workable stable design. What baseline and evidence should decide whether to adopt it?

Reveal

First describe the stable implementation and identify its concrete limitation. The experimental API has a case only when that baseline cannot represent the required tree-artifact action pattern, rule-family relationship, encapsulated helper boundary, or deferred dependency edge without distorting the graph.

Then require a supported Bazel-version range, a public compatibility boundary, and tests that would detect semantic changes. If the benefit is only convenience, code deduplication, or an unmeasured performance hope, keep the stable mechanism.

key takeaway

Experimental rule patterns are not shortcuts around Bazel's architecture. They are narrow tools for controlled movement at specific boundaries: tree-artifact action templates, rule inheritance, encapsulated implementation helpers, and deferred dependency edges. Learn them as design vocabulary, then choose them only when the ordinary Level 4 contract cannot express the shape honestly.