F*: The General-Purpose Language That Turns Code Into Mathematical Proofs

Imagine a compiler that catches not just syntax errors and type mismatches, but outright logical bugs — buffer overflows, divisions by zero, null dereferences — before your code ever runs. That's the promise of F, a proof-oriented programming language that has been quietly powering some of the world's most security-critical software. And despite its academic roots, F is not a toy. It's a general-purpose language that lets you write real, production-ready code while proving it correct at compile time.

Developed by Microsoft Research and a growing community of researchers, F is a hybrid of functional programming and formal verification. It's the same philosophy that gives us proof assistants like Coq and Isabelle, but with a twist: F automates the painful proof process by using an SMT solver (Z3) to automatically discharge verification conditions. The result is a language that's both powerful enough for serious systems programming and practical enough to use in the real world — as evidenced by its use in verified cryptographic libraries like HACL* and the Everest project.

What Exactly Is F*?

F* (pronounced "F star") is a functional programming language that borrows heavily from OCaml and F#, but adds refinement types and dependent types. In plain English: you can attach logical properties to your types, and the compiler insists on proving them. For example, instead of saying "this function takes an integer," you can say "this function takes a positive integer." If any call site fails to guarantee that property, the compiler rejects the program.

This makes F a proof-oriented language. You're not just writing a program; you're also writing a proof of its correctness, albeit in a mostly automated way. The intuition is that every function with a signature that includes logical constraints is a theorem, and the implementation is the proof. When F accepts your code, it means the theorem is proven.

But don't be fooled by the academic terminology. F compiles to executable code in multiple languages — currently OCaml, F#, C, and WebAssembly (WASM). That means you can write a verified module in F, extract it to C, and drop it into a C project, or extract to WebAssembly and run it in a browser. It's a general-purpose language in the truest sense: it's not just a verification tool, but a source language for real software.

Refinement Types: The Secret Sauce

Refinement types are at the heart of F. A refinement type takes an existing type and adds a logical predicate that restricts it. For example, the type (x:int{x > 0}) describes integers greater than zero. In F, this is written as:

type positive = (x:int{x > 0})
let add_positive (a:positive) (b:positive) : positive = a + b

When F* type-checks this function, it verifies that the expression a + b is indeed positive given that a and b are positive. If you tried to return a - b, the compiler would reject it, because the proof doesn't hold.

This idea extends far beyond simple arithmetic. You can define arbitrary preconditions and postconditions. Here's a classic safe division example:

let safe_division (x:int) (y:int{y <> 0}) : int = x / y

The type of y requires that it's not zero. Any caller must provide a proof — or more often, a sufficiently strong refinement — that the divisor is non-zero. The compiler generates a proof obligation and hands it to Z3, which decides automatically whether it's satisfiable. If Z3 says yes, your code compiles. If not, you get a clear error message.

This automation is what sets F* apart from older proof assistants. You rarely have to write explicit proof scripts. You simply write your types and let the SMT solver do the heavy lifting. It's not magic, though. Sometimes you need to help the solver by adding intermediate assertions or structuring your code with what's called "proof lemmas" — but for many real-world problems, the automation is impressive.

Why Proof-Oriented Matters Beyond Academia

It's easy to dismiss formal verification as academic navel-gazing. But consider the stakes: more critical software vulnerabilities are still caused by common coding errors — buffer overflows, integer overflows, race conditions — than by exotic attacks. The infamous Heartbleed bug of 2014 was a simple missing bounds check in the OpenSSL implementation of TLS. A language like F* could have caught it at compile time.

The most visible success story is HACL (pronounced "hackle-star"), a cryptographic library written in F and verified against a portable C specification. Since its introduction, HACL has been integrated into real-world products: it's used by Firefox for WebAssembly-based crypto, by WireGuard for the Noise protocol, and by the NSS library (Network Security Services) as a source for verified C code. HACL's secret is that it's extracted to C from verified F* code, meaning the C code inherits the proof of correctness.

Then there's the Everest project, an ambitious effort to build a verified version of the HTTPS stack. Using F*, the Everest team produced miTLS, a verified TLS implementation, along with other verified components. EverCrypt is a component of Everest that provides a verified cryptographic provider with a state-machine-style API that's designed to be safe to use even by programmers unfamiliar with formal methods.

These projects prove that F* isn't an ivory-tower experiment. It's being used to protect real data in real systems.

How F* Verifies State and Effects

One of the reasons F is truly general-purpose is its effect system. You can write programs that manipulate mutable state, throw exceptions, or perform IO, and still reason about them rigorously. F uses a monadic effect system similar to what you'd find in Haskell, but with a twist: effects are part of the type, and the compiler verifies that your stateful operations obey the specified pre- and postconditions.

For example, consider a function that appends a string to a global buffer. You can write a specification that says: "If the buffer has size less than its capacity before the call, then after the call, the buffer contains the old content plus the new string." F* will generate proof obligations that check this property at every call site. If there's any path where the buffer might be full, the compiler rejects it.

This makes F* exceptionally well-suited for low-level systems programming, where resource bounds and error handling are critical. The extraction to C ensures that the verified code is exactly what runs in production — no mismatches between a formal model and the actual implementation.

The Learning Curve and Ecosystem

So, is it worth learning? That depends on your tolerance for mathematical notation and rigorous thinking. F* is not a beginner-friendly language. It assumes you're comfortable with functional programming, some linear algebra of logic, and ideally have seen type theory before. The learning curve is steep, but the payoff is a deep understanding of what it means to "prove" a program correct.

Fortunately, the ecosystem has matured. There's a well-written online textbook, "Programming and Proving in F", that walks you from basic syntax to advanced verification techniques. The F distribution includes a VS Code plugin, an interactive emacs mode, and command-line tools that integrate with SMT solvers. The community is active on GitHub and by 2026, documentation and examples have expanded significantly.

If you're already working in a system where safety is paramount — think embedded systems, blockchain, cryptographic protocols, or safety-critical aerospace software — F is a natural fit. There are also opportunities to use F in conjunction with other tools. For instance, some teams use F* to verify critical modules and extract them to C, while the rest of the codebase remains in a conventional language.

F* vs. Other Formal Methods

F* occupies an interesting niche in the verification landscape. Here's a quick comparison:

Tool Approach Best For
F* SMT-driven refinement types Efficient verification of executable code
Coq Interactive proof assistant Highly expressive proofs, however manual
Dafny Imperative, SMT-driven Sequential programs with loops and classes
Isabelle/HOL Interactive proof assistant Large developments in math and CS
Why3 Program verification with pluggable provers Functional and imperative programs

Compared to Coq and Isabelle, F is much more automated, but less expressive for arbitrary mathematical reasoning. Compared to Dafny, F is more functional and type-theoretic, but also more complex. The choice depends on your needs.

One of F's biggest advantages is its focus on extraction. Verified code isn't just a proof; it becomes an executable artifact. This is why F has been adopted for cryptographic code, where you want both reliability and performance.

The Future: Verification Meets AI-Assisted Coding

As of August 2026, the software industry is in the middle of an AI coding revolution. Tools like Copilot, Cursor, and other AI pair programmers have made it easier than ever to generate code quickly. But AI-generated code is often plausible yet subtly wrong — exactly the kind of code that benefits from formal verification.

This is where F suddenly becomes exciting. The combination of large language models and proof-oriented languages could lead to a new paradigm: AI writes code, but the compiler proves it correct. While this isn't happening yet in a mainstream way, researchers are exploring ways to generate F proofs with LLM assistance. The holy grail is to have AI generate both a function and its proof of correctness, with F* serving as the final checkpoint.

Microsoft Research, the original home of F, is also pushing the language forward. The project's official site lists a host of published papers and tools, and ongoing work includes better support for modularity, concurrency, and richer type inference. In the long run, F could become as natural a choice for security-critical code as Rust is for memory safety — except F* goes further, proving not just memory safety, but semantic correctness.

Limitations and When Not to Use F*

Despite its power, F* isn't the right tool for every job. The learning curve is real, and the proof burden can be heavy for large codebases. Even with SMT automation, some proofs require deep mathematical insight that only a human expert can provide. For most web and mobile development, simpler languages are more practical.

F also has a smaller ecosystem and fewer libraries than mainstream languages. You won't find thousands of npm packages in F. The community is active but niche, so you'll likely need to implement many utilities from scratch. However, for the specific niche of verified, high-assurance systems, nothing else offers F*'s combination of practicality and rigor.

The Bottom Line

F is a dense, challenging, but deeply rewarding language. It turns software development into a mathematical discipline, and it does so without sacrificing the ability to write practical code. If you're a language geek, a security engineer, or a systems programmer who's tired of late-night debugging sessions caused by null pointers and integer overflows, give F a shot.

You might not adopt it for your next hackathon project, but you'll certainly appreciate the mindset it promotes: every function is a theorem, every compilation is a proof. And in a world increasingly reliant on software, having such proofs is not a luxury — it's a necessity.

Sources:
- F* official website
- Microsoft Research: F* Project
- F* Overview paper (2016)
- HACL* / Project Everest
- Programming and Proving in F*

← All posts

Comments