Rust Is Great. We Chose Zig Anyway.

 

Why Tickoni rejected Rust's ownership model, refused to settle for C, and built its financial infrastructure in Zig.

The programming language debate is usually about which language has the best features. We had a slightly different problem: we want to ship an actual system.

Every few years, systems programming discovers a new religion.

C is too dangerous. C++ is too complicated. Rust will save us from memory bugs. The borrow checker is the future. Rewrite everything. Preferably yesterday.

And look, some of those arguments are legitimate.

Rust is an extraordinary piece of engineering. Its ownership model eliminates entire categories of bugs. Its type system is powerful. Its tooling is excellent.

But there's a question that rarely gets enough attention in these debates:

What happens when your application doesn't actually own most of the memory it's working with?



Not theoretically. Not in some carefully constructed benchmark. In a real application talking to decades of existing native software.

That was the question we faced building Tickoni.

Tickoni stack brings together Firedancer, Apache Arrow, GSL, TA-Lib, QuantLib, and Qt. High-performance systems infrastructure, columnar analytics, numerical computation, quantitative finance, and a native desktop interface.

A substantial part of that ecosystem is written in C or C++.

We weren't about to rewrite QuantLib because someone on the internet thinks every new project should be 100% Rust. We weren't going to rebuild Arrow. And we certainly weren't going to throw away mature numerical libraries just to achieve some kind of language purity.

We wanted to use the best existing infrastructure, integrate it with as little unnecessary machinery as possible, and build the rest ourselves.

That made the choice surprisingly straightforward.

Rust was the obvious candidate. C was the obvious fallback. Zig turned out to be the better answer.

Here's why.

1. Your Dependency Graph Doesn't Care About Your Favorite Language

Let's start with something painfully uncontroversial that somehow becomes controversial in programming language discussions.

Good infrastructure already exists.

Apache Arrow solves difficult problems around columnar data representation and interoperability.

GSL provides decades of numerical algorithms.

TA-Lib implements established technical indicators.

QuantLib supplies sophisticated financial models that would be expensive, risky, and frankly unnecessary to reproduce.

Firedancer brings serious systems-level engineering, including high-performance native infrastructure and shared-memory primitives.

Qt gives us the foundation for a cross-platform professional desktop terminal.

These are not small convenience packages that can be replaced over a weekend.

They represent enormous amounts of accumulated engineering work.

Rewriting any of them would mean taking responsibility for their functionality, correctness, edge cases, numerical behavior, testing, and maintenance.

And for what? So we could proudly say everything was written in the same language?

No thanks.

We believe in using good infrastructure, regardless of which language its authors chose.

That immediately changes the problem.

We need to move data between components, call native functions, manage foreign resources, and preserve memory layouts. Sometimes the memory belongs to us. Often it doesn't.

At this point, language interoperability stops being a secondary feature. It becomes one of the primary architectural requirements.

And that's where Rust gets interesting.

2. Rust's Biggest Advantage Gets Complicated at the FFI Boundary

Let's give Rust the credit it deserves.

The borrow checker is not a gimmick.

For an application built predominantly in Rust, where ownership and lifetimes are expressed within its type system, Rust provides genuinely powerful guarantees.

You cannot casually free an object while safe Rust references still exist. You cannot create arbitrary mutable aliases. The compiler catches many mistakes before the program ever runs.

That's excellent.

Now imagine the memory wasn't allocated by Rust.

Suppose a C++ library allocates an Arrow buffer. A native numerical routine operates on that buffer. Another component retains a reference to it. Eventually, a foreign release callback is responsible for cleaning it up.

Who owns the memory?

How long does the allocation live?

Can the underlying library modify it while Rust holds a reference?

Will the memory still exist when a callback executes?

Rust's compiler cannot infer the answers from the implementation of an external C or C++ library.

You have to establish those contracts yourself.

Welcome to unsafe.

The part nobody likes putting on the slide

Rust's Foreign Function Interface is capable and well designed. It can absolutely integrate native libraries.

But operations involving foreign functions and raw pointers frequently require unsafe because Rust cannot verify the behavior of foreign code.

And unsafe means something very specific.

It means the programmer is responsible for upholding invariants that the compiler cannot check.

You have to verify lifetimes. You have to establish aliasing rules. You have to ensure pointers are valid, buffers remain allocated, and foreign code respects the assumptions your Rust references rely on.

You then typically wrap those operations in safe Rust abstractions.

This works. In fact, it's one of Rust's strengths.

But there's a catch.

That safe wrapper is only as sound as the unsafe assumptions underneath it.

If the foreign library frees a buffer earlier than expected, the borrow checker cannot save you. If a callback modifies memory that Rust considers immutable, the type system cannot retroactively negotiate with the C code.

You still have to understand the underlying memory model.

You still have to debug the native boundary.

You still have to trust, validate, and maintain the contracts.

And with a system like Tickoni, those boundaries aren't occasional exceptions.

They're a central part of the architecture.

Does this defeat the purpose of Rust?

Not entirely. That would be an easy argument to make, but an inaccurate one.

Safe Rust remains safe when built on sound abstractions. Well-designed wrappers can isolate unsafe operations and provide strong guarantees to everything above them.

If we were writing a mostly Rust application with a few native dependencies, we'd consider that an excellent tradeoff.

But Tickoni has a different shape.

We are deliberately building around existing native systems. Resource ownership frequently originates outside our application language. Shared-memory protocols, native callbacks, and external allocation strategies are fundamental design concerns.

Using Rust would mean continuously translating those realities into Rust-compatible abstractions.

We would be maintaining two things: the actual native memory contracts and the Rust model built around them.

That's additional complexity.

Maybe worthwhile in some architectures.

For ours, it was complexity we didn't want to introduce.

We didn't reject Rust because unsafe exists. We rejected it because much of our integration work would still depend on manually verified memory contracts—the very thing Rust is supposed to help us avoid.

3. Zig Doesn't Try to Own Memory It Doesn't Own

This is where Zig starts looking very attractive.

Zig approaches systems programming from a different direction.

It doesn't require every resource to fit into a compiler-enforced ownership model.

Instead, it gives developers explicit control over memory allocation, pointers, data layout, and resource lifetime.

The rules are visible. The responsibility is visible.

A C-compatible pointer is a pointer. A native function is a native function. An external allocation remains governed by the external allocation contract.

There is no additional ownership framework that foreign memory must be squeezed into.

And when your system spends a lot of time interacting with C libraries, that's a pretty significant advantage.

C interoperability without the ceremony

Zig provides straightforward interoperability with C-compatible data structures and function interfaces.

For Tickoni, this matters everywhere.

TA-Lib expects arrays of numeric data and caller-provided output buffers.

GSL exposes numerical operations through conventional C interfaces.

Apache Arrow defines a stable C Data Interface for sharing columnar data between runtimes.

Firedancer's native interfaces and memory-oriented design fit naturally into this style of systems programming.

Zig lets us work with these interfaces directly.

Importantly, this doesn't magically make them safe.

A bad pointer is still a bad pointer. An invalid lifetime is still an invalid lifetime.

The difference is that we aren't adding a second system of ownership rules around the native contract.

We can establish the contract explicitly, validate it at the boundary, and build higher-level abstractions where they're useful.

That's a better fit for our engineering model.

And yes, C++ is still C++

Before someone points it out: QuantLib and Qt are C++ libraries.

Zig doesn't somehow turn C++ into C.

C++ object models, exceptions, templates, and binary interfaces introduce additional interoperability requirements. Depending on the component, we need C-compatible wrappers or other carefully defined native interfaces.

Rust would face comparable challenges.

The goal is not to pretend those boundaries don't exist.

The goal is to keep them narrow, explicit, and predictable.

We don't want the programming language to dictate how our native architecture must be reorganized. We want the language to help us implement the architecture we actually need.

That's Zig's appeal.

4. Fine. Then Why Not Just Write C?

This is the reasonable objection.

If we want direct access to C libraries, explicit memory management, minimal abstraction, and predictable execution, why not use C?

After all, C already does all those things.

And unlike Zig, C has decades of ecosystem stability behind it.

It's a fair question.

Our answer is simple:

We want C's control. We don't want C's development experience.

C is extraordinary at what it was designed to do.

It also makes far too many common programming mistakes unnecessarily easy.

Consider the usual problems.

A function returns an integer. Does zero mean success? Does negative one mean failure? Is the actual error stored somewhere else?

A pointer is returned. Can it be null? Who owns it? Is there an associated length? Which function releases it?

You allocate three resources. The fourth allocation fails. Now every error path needs to clean up exactly the right combination of resources.

Of course, competent C programmers can solve all of this.

That's not the point.

The point is that C repeatedly forces programmers to implement conventions that a more modern language can help enforce structurally.

Error handling that doesn't require archaeology

Zig has error unions.

Functions can explicitly represent failure in their types. Errors can propagate with try or be handled using catch.

This isn't revolutionary computer science.

It's just a better way to express something every serious application already needs.

In Tickoni, where native computations can fail for everything from invalid parameters to numerical convergence problems, we need errors to remain meaningful as they cross subsystem boundaries.

Structured error handling makes that easier.

Resource cleanup without the circus

Zig has defer.

Acquire a resource, register the cleanup, and let the scope handle it.

There's also errdefer, which runs cleanup when control leaves a scope through error propagation.

Compare that with the typical C function containing multiple allocations, several early failure conditions, and a collection of cleanup labels.

Yes, goto cleanup works.

No, it isn't a compelling reason to keep writing new infrastructure in C.

Arrays that remember how long they are

Zig slices carry both a pointer and a length.

That sounds almost embarrassingly basic until you consider how many C bugs emerge because a pointer and its associated length disagree.

Zig also provides optionals, compile-time evaluation, and runtime safety checks in applicable build modes.

None of these features makes low-level code automatically correct.

But they remove friction from writing code that is correct.

And over a substantial codebase, that matters.

Compile-time specialization without preprocessor acrobatics

Zig's comptime provides another practical advantage.

We can construct generic functionality, specialize implementations, and evaluate expressions at compile time without turning the codebase into a forest of preprocessor macros.

For financial computing, where functions often vary by numeric type, instrument representation, or compile-time configuration, this is useful.

We get specialization without necessarily paying for runtime abstraction.

So yes, C would work.

But Zig lets us write C-style systems software with better tools for expressing the things C programmers have been managing manually for decades.

That's not a small improvement.

5. Zig 0.17 Just Made the Memory-Safety Argument More Interesting

Now comes the predictable response.

"But Zig isn't memory safe."

Correct.

Zig doesn't provide Rust's borrow checker. It doesn't statically eliminate use-after-free bugs. It doesn't guarantee that a pointer remains valid just because the code compiles.

And if someone claims otherwise, they're selling you a fantasy.

But there's an important difference between saying memory errors are possible and saying you have no effective way to detect them.

Zig has been building a different set of tools around explicit memory management.

And Zig 0.17, released in October 2026, made a notable improvement.

Meet SafeAllocator

Zig 0.17 introduced std.heap.SafeAllocator, replacing the older DebugAllocator approach and deprecating that previous implementation.

SafeAllocator provides stronger diagnostics for allocation misuse.

It records metadata and stack traces associated with allocations. At allocator deinitialization, it can report allocations that were never freed.

It also detects allocation mismatches and handles double frees and certain allocation-operation races through panics or segmentation faults.

Under the documented backing-allocator conditions, avoiding memory reuse also helps expose many writes after free.

This is extremely useful.

Imagine a long-running market-data process that allocates temporary buffers during numerical processing.

Somewhere inside an error path, one allocation isn't released.

The system keeps running. Memory usage slowly climbs.

Traditionally, finding that leak could become a fairly unpleasant debugging exercise.

With tracked allocation metadata and stack traces, we have a much better starting point.

Instead of merely knowing that memory went missing, we can investigate where the outstanding allocation originated.

That's a meaningful improvement in development-time observability.

But let's not confuse debugging with proof

SafeAllocator is not a borrow checker.

It does not automatically track allocations performed inside an unrelated native library.

It cannot make arbitrary foreign pointers safe.

It does not prove the absence of memory corruption in production.

And C developers are perfectly capable of using AddressSanitizer, LeakSanitizer, and other excellent diagnostics.

The argument isn't that Zig invented memory debugging.

The argument is that Zig combines first-class explicit allocator selection with increasingly powerful mechanisms for detecting allocator misuse.

We can use diagnostic allocation strategies during development and testing, then choose allocators appropriate to the production workload.

For latency-sensitive components, that might mean specialized allocation pools, arenas, or shared-memory management.

For debugging, it can mean much more aggressive tracking.

Same language. Different allocation policies.

That is exactly the kind of flexibility we want.

Rust tries to prevent many memory errors through compile-time constraints.

Zig leaves more of those contracts explicit and provides tools to help discover when they are violated.

Those aren't equivalent guarantees.

But for a platform already dependent on foreign memory ownership, the second model is a serious engineering option.

6. Zero-Copy Isn't a Buzzword. It's an Ownership Problem.

Everyone likes talking about zero-copy.

It sounds fast. It looks good in architecture diagrams. Put it next to "real-time" and you've got yourself a fantastic investor presentation.

Actually implementing it is more interesting.

Consider Apache Arrow.

Its C Data Interface allows different runtimes to exchange columnar arrays without necessarily copying their underlying data buffers.

Great.

But somebody still owns those buffers.

Arrow solves this with an explicit release contract. The producer provides a callback, and the consumer invokes it when the data is no longer needed.

The producer can manage the allocation however it wants.

The consumer doesn't need to know the allocator's internals.

This is a beautifully practical design.

And it illustrates the point we've been making.

The real safety requirement is not that every language has the same ownership system.

It's that the ownership contract is correct, explicit, and respected.

Zig works naturally with this design.

Now make it shared memory

Tickoni's tile-based architecture also involves communication between independent processes using shared-memory workspaces.

Here, things become even more interesting.

An ordinary pointer from one process cannot simply be assumed valid in another process. Shared regions can be mapped at different virtual addresses.

You need well-defined layouts, handles or offsets, synchronization, and lifetime protocols.

You need to decide who is allowed to mutate a shared object and when.

You need to think about what happens if a process exits unexpectedly.

A borrow checker cannot inspect another process and verify that all of those contracts remain intact.

Neither can Zig.

This is systems architecture, not a language feature.

The difference is that Zig lets us express the required low-level structures directly, without asking the code to conform to an additional ownership model that cannot cover the complete cross-process relationship anyway.

For Tickoni, that's a feature, not a deficiency.

Memory ownership doesn't become correct because a language has a good type system. It becomes correct because the complete system has a sound ownership protocol.

A good language helps implement that protocol.

It cannot replace it.

7. We Didn't Pick Zig Because of a Benchmark

Let's kill another popular argument.

"Which is faster: Rust, Zig, or C?"

Wrong question.

At least for this decision.

All three languages can produce extremely fast native code.

Rust can implement zero-copy systems. C can have extremely predictable allocation behavior. Zig can generate efficient machine code.

If somebody claims one is universally faster, ask for the workload, the compiler configuration, and the benchmark methodology.

Then watch the conversation get significantly less exciting.

For Tickoni, the expensive mistakes are often architectural.

Unnecessary memory copies.

Excessive allocations.

Serialization between components that could exchange native representations.

Poor cache locality.

Synchronization overhead.

Unnecessary crossings between incompatible data models.

These problems can overwhelm relatively small differences in generated instructions.

A beautifully optimized numerical routine doesn't help much if we're spending an unreasonable amount of time preparing and moving its input.

That's why we care about explicit memory layouts, native interoperability, and predictable resource management.

Zig lets us optimize those decisions without introducing a mandatory garbage collector or hidden allocation model.

It doesn't guarantee performance.

It gives us the control to engineer it.

And frankly, that's more valuable than winning another synthetic benchmark on a developer's laptop.

8. The Toolchain Matters. So Does the Version Number.

There's another reason Zig fits our requirements: its integrated tooling.

Tickoni targets multiple platforms. That means dealing with different operating systems, native libraries, linking requirements, and compiler environments.

Anyone who has spent serious time maintaining cross-platform C++ builds knows how quickly that becomes entertaining.

And by entertaining, we mean absolutely not entertaining.

Zig's build system and cross-compilation capabilities offer a compelling way to coordinate native builds.

We can work with Zig, C, and C++ components through one programmable build environment, while retaining target-specific configurations where necessary.

That doesn't erase Qt's packaging requirements or magically unify incompatible C++ ABIs.

But it reduces the amount of disconnected tooling we have to manage.

There's a catch, though.

Zig is still pre-1.0.

And that means breaking changes.

Zig 0.17, for example, removed the old @cImport language builtin as C translation moved into a separate integration workflow. The release also brought substantial build-system changes.

For a project with significant native dependencies, this is not something you ignore.

Compiler upgrades can involve real migration work.

Rust's ecosystem and language stability are stronger today. C's compatibility story is stronger still.

We accept that tradeoff.

Because the long-term benefits of Zig's language model and native integration are more important to our architecture than pretending toolchain migration has zero cost.

But if you're adopting Zig, go in with your eyes open.

A younger language means you occasionally get to help discover exactly how young it is.

9. So Is Zig Better Than Rust?

No.

At least, not as a universal statement.

This is the part programming language tribalism tends to struggle with.

There is no single best systems programming language for every workload.

If we were building an entirely new network service with predominantly internal data structures and minimal native dependencies, Rust would be extremely attractive.

The ownership system could govern most of the application. We would get meaningful compiler-enforced guarantees across a large proportion of the codebase.

That would be a strong architectural argument for Rust.

If we were maintaining a small C library with decades of existing consumers, staying with C might be the sensible choice.

But Tickoni is neither of those systems.

We are building a high-performance financial platform around existing C and C++ infrastructure, explicit memory ownership, native numerical routines, and shared-memory communication.

Rust can handle this architecture. It would simply require us to maintain additional abstractions and validate the same foreign contracts underneath them.

C can handle it too. We'd just be giving up language features that make writing and maintaining new systems code substantially more manageable.

Zig sits in the right place for us.

Not because it combines every advantage of Rust and C. It doesn't.

Not because its allocator diagnostics make memory bugs impossible. They don't.

Not because it automatically generates faster code. It doesn't.

But because it offers a particularly useful combination of native interoperability, explicit resource management, modern language constructs, and developer tooling.

That combination matches the problems we're actually solving.

10. The Real Question Isn't Which Language Is Safest

Here's the uncomfortable truth about systems programming.

A language can make some bugs impossible and still be a poor fit for the architecture you're building.

A language can require manual memory management and still be the rational engineering choice.

The type system is not your architecture.

The compiler is not your ownership protocol.

And choosing a language with strong guarantees doesn't automatically transfer those guarantees to the C library on the other side of an FFI call.

We considered Rust seriously. Its strengths are real.

But integrating deeply with C and C++ systems meant that many of Tickoni's most important correctness requirements would still depend on explicitly maintained foreign ownership contracts.

At that point, we needed to ask whether Rust's additional abstraction machinery justified its cost for our particular platform.

We decided it didn't.

Then we looked at C.

C would give us the interoperability we wanted, but it would also bring along decades-old limitations in error handling, resource cleanup, and everyday expressiveness.

Zig gave us a more attractive answer.

Direct native interfaces. Explicit memory management. Better error handling. Better cleanup mechanisms. Compile-time specialization. Integrated build tooling. And, with Zig 0.17, stronger memory diagnostics through SafeAllocator.

Of course, we still have to write correct code.

We still have to test our native interfaces, verify resource lifetimes, audit shared-memory protocols, and diagnose concurrency problems.

No language gets us out of doing real engineering.

And that's fine.

We didn't choose Zig to avoid responsibility for memory. We chose it because we wanted direct control over memory without dragging C's limitations into the rest of the codebase.

Rust is an excellent language.

C is an extraordinary foundation.

But Tickoni isn't a language popularity contest.

We're building a financial computing platform, and the architecture gets the final vote.

For Tickoni, that vote went to Zig.

Comments

Popular posts from this blog

Decoding the PlayStation 3 Hack: Unraveling the ECDSA Random Generator Flaw

Patricia Tries

Building the Financial Bot with OpenClaw — POC