When the Type System Justifies Deleting Process

·7 min read

Yesterday I merged contributions from two different developers on Rapina, the Rust web framework I'm building. Today I deleted our develop branch.

This wasn't a reckless move. It was the result of watching rust-lang/rust ship 150,000+ commits with hundreds of contributors on a single main branch—and realizing our gitflow setup was solving a problem Rust had already solved at the type system level.

The Gitflow Tax

We run five repositories. Four to five developers. Gitflow everywhere: feature branches, develop branch, release branches, hotfix branches. The ceremony compounds across repos. A feature spanning two services means coordinating merges across two develop branches. Reviews lag because developers aren't sure which branch represents "current truth." Merge conflicts accumulate.

Gitflow was designed in 2010 for a different era. Vincent Driessen introduced it as a branching model for teams shipping versioned software with long-lived release cycles. It adds process-level isolation: develop is your integration branch, main is your stable release branch, and the space between them is a buffer where you catch problems before they hit production.

The assumption: you need process to prevent bad code from reaching production.

That assumption makes sense in languages where the type system doesn't help you. JavaScript, Python, Ruby—languages where tests are your primary safety net and runtime exceptions are a fact of life. In those ecosystems, branch isolation buys you time to catch issues before they compound.

But Rust changes the equation.

What the Compiler Already Guarantees

When I reviewed contributions to Rapina today, I wasn't looking for null pointer dereferences. I wasn't checking if someone forgot to handle an error case. I wasn't scanning for data races or use-after-free bugs.

The compiler had already ruled those out.

The code review was about design coherence:

  • Does this API fit the framework's architectural model?
  • Is this abstraction at the right level?
  • Does this change make the framework easier or harder to reason about?

These are questions the type system can't answer. But everything else—memory safety, concurrency safety, error handling discipline—is enforced before the code is even eligible for review.

This is the insight: Rust moves invariants from runtime and process into compile time. Gitflow is a process-level workaround for weak compile-time guarantees. When the compiler enforces invariants, the process can be radically simpler.

How rust-lang/rust Ships

I've been contributing to the Rust compiler. The repository has over 150,000 commits. Hundreds of active contributors. A CI pipeline that runs thousands of tests across platforms and configurations.

They use a single main branch (historically called master, now main). No develop branch. Feature work happens in PRs. Merges go straight to main after review and CI passes. Release branches are cut only when preparing a release—at the last responsible moment.

This isn't reckless cowboy coding. It's compiler-enforced discipline backed by comprehensive CI.

The branching strategy is simple because the language doesn't allow the complexity to hide. If your PR breaks something, the compiler or CI catches it before merge. There's no "integrate into develop and see what happens" phase. The feedback is immediate and deterministic.

The Decision

I started with Mediator, our core backend service. Deleted the develop branch. Updated CI to treat main as the single source of truth. Moved to trunk-based development: feature branches merge directly to main after review and CI.

The team asked the expected questions:

  • "What if something breaks in production?"
  • "How do we isolate work-in-progress features?"
  • "Isn't this risky?"

The answers:

  1. If something breaks in production, it's not because we skipped develop—it's because our tests or type modeling are insufficient. The develop branch was giving us false confidence. It wasn't catching the bugs that matter.
  2. Feature isolation happens at the architecture level, not the branch level. If a feature isn't ready, it's behind a feature flag or not exposed in the API. The type system ensures incomplete features don't compile into incoherent states.
  3. The risk profile doesn't change—it just becomes visible faster. Gitflow delays feedback. Trunk-based development surfaces integration issues immediately, when they're cheapest to fix.

When Process Is a Smell

This is a broader principle in technical leadership: when you find yourself adding process to compensate for language limitations, you're treating symptoms instead of causes.

Gitflow compensates for languages where merging code from multiple contributors is inherently risky. The branch model is risk mitigation.

Rust eliminates entire classes of risk at compile time. The mitigation becomes overhead.

This doesn't mean Rust projects never need complex branching strategies. Regulated industries, embedded systems with hardware-in-the-loop testing, teams with async deployment cycles—these contexts might still justify heavier process.

But for a web framework with solid CI and a type system that enforces invariants? Trunk-based development isn't just viable—it's the architecturally honest choice.

What This Reveals About Architectural Discipline

Rapina is a web framework that emphasizes architectural discipline. The design philosophy: the framework should make bad architecture hard to write.

Deleting the develop branch is an extension of that philosophy. If the branching strategy is complex, it's because the architecture allows states and transitions that shouldn't be possible. Fix the architecture. Simplify the process.

The same principle applies to the framework itself:

  • If users need extensive documentation to avoid memory bugs, the API is wrong.
  • If users need linters to catch concurrency issues, the abstraction is leaky.
  • If users need gitflow to prevent broken merges, the type modeling is weak.

Rust enables a different contract: the compiler is the first line of defense. Process is the last resort.

The AI Era Context

Why does this matter for technical leadership in the AI era?

Because AI-generated code is about to flood every codebase. LLMs can write syntactically correct code in any language. But they can't reason about invariants across a system. They can't model ownership semantics. They can't enforce architectural discipline.

In dynamically typed languages, AI-generated code looks fine until it explodes at runtime. The solution is more tests, more process, more review overhead.

In Rust, AI-generated code either compiles or doesn't. The type system is a forcing function. If the AI doesn't understand ownership, the code won't build. If the AI violates an invariant, the compiler rejects it.

This changes the role of the technical lead. In dynamically typed ecosystems, leadership means building process to catch what the language doesn't. In Rust, leadership means designing type systems that catch what process can't.

Deleting gitflow is a small decision. But it reflects a larger shift: the compiler becomes the enforcer, and the leader's job is to design the rules the compiler enforces.

What I'm Watching

Rapina received two contributions from different developers yesterday. Both were clean merges. The code reviews were about design, not bugs.

This is what an open source community forming around architectural discipline looks like. Contributors aren't fighting the type system—they're using it to write better code. The framework's type model is the shared language.

I'm watching to see if this scales. If the community grows, will trunk-based development remain viable? Or will there be a threshold where the coordination overhead justifies reintroducing branch isolation?

My hypothesis: if the type system is strong enough, the threshold is much higher than most teams assume.

We'll see.

Takeaways for Technical Leads

If you're leading a Rust project and still using gitflow, ask:

  1. What invariants does the develop branch protect that the compiler doesn't already enforce?
  2. What percentage of bugs caught in develop would have been caught by better type modeling or CI?
  3. Is the branching complexity solving an architectural problem or hiding it?

If the answers are "not many," "most of them," and "hiding it," consider trunk-based development.

The transition isn't costless. You need solid CI. You need a team that trusts the compiler. You need architectural discipline in how features are flagged and deployed.

But if you have those things—and if you're using Rust—the process can be radically simpler.

Because when the type system enforces the invariants, the branches become theater.

Comments

Participate in the discussion