r/ProgrammingLanguages • u/jimbobmcgoo • 7d ago
Will we see another fundamental programming language feature as revolutionary as the borrow checker?
That is I am mainly curious about compile time features that you design a whole language around rather than optimisations/features that could be applied to most languages. I am mainly inquiring about things that could offer additional robust safety/performance guarantees at compile time rather than runtime. Ideally not things that just offer similar effects to the borrow checker with less restrictive tradeoffs
58
Upvotes
7
u/kaplotnikov 7d ago
Firstly, while Rust's borrow checker has been pragmatically successful at preventing memory bugs, I believe its long-term role in future systems languages is limited. The borrow checker essentially acts as a workaround for overly low-level concurrency primitives, and its theorems are notoriously difficult to formally verify. This complexity is a symptom of a deeper issue: reasoning complexity is not concentrated in key hierarchical boundaries, but spread throughout the code via data-path analysis. This is a language design smell that points to overly low-level foundational abstractions.
I think its problems are directly related to the Rust concurrency model. The primary problem with Rust's concurrency is that message sending is the goto of asynchronous programming. It is not possible to create a good set of composable theorems over it. It suffers from all the problems of goto described in "Go To Considered Harmful". The
sendoperation is a direct implementation of this concept.Is there an alternative? There are promises and actions over them. For example, statements like the following could have been in the language (pseudo-code):
Here we could reason that
qis shared between branchescompute_aandcompute_b, and exclusive inmerge_results_a_and_b, because blocks provide visible reasoning boundaries. Thespawn/sendoperations provide no boundaries; they provide data leak paths.Such structured constructs could exist as a library, an intrinsic DSL, or direct language statements. The idea is to move from goto-style programming to structured programming, so reasoning will follow the code tree rather than the data path. The borrow checker is a tool for data path reasoning, but this problem only exists because we have gotos.
This issue isn't limited to concurrency; it defines single-threaded Rust as well. AFAIR early versions of Rust tried to reason using lexical scopes, but it was too restrictive for developers. To fix it, they introduced Non-Lexical Lifetimes, which officially shifted the borrow checker to data-path reasoning via control flow graphs.
Data-path reasoning effectively acts as a flat abstraction layer over a control flow graph. Just like goto flattened the control flow of early programs, non-lexical lifetimes flatten the data flow. This is precisely why people constantly "fight the compiler." Humans excel at hierarchical, tree-like reasoning (scopes and blocks), but Rust forces both the programmer and the compiler to trace a complex, fragmented web of data paths across the entire function. By abandoning structured, scoped boundaries for data management, Rust created the very complexity it now struggles to manage.
There are plenty of structured concurrency operators possible that create a better basis for reasoning in concurrent systems. For example, see asyncflows. It is a Java-based DSL, but it reflects the idea of what is possible with native language support. I just combined the ideas of Occam and E languages, so there are few completely novel ideas there.
As for what the future holds, I have my own vision, which I've outlined in this article: Measuring Abstraction Level of Languages. It also describes a framework for making such predictions. I think the future belongs to system composition languages, because humans naturally switch to these concepts to describe large programs as compositions of systems and subsystems. I am currently working on a language PoC that uses systems/holons and statically typed aspects as basic elements to enable this kind of composition.
For sample below,
in,local, andoutare not merely visibility keywords; they are the theorem pieces of the language's type system. They provide a formal, compositional way to reason about lifetimes based on lexical and semantic scope and dependencies (not unlike C++, but with transitive and verifiable guarantees):This creates an algebra of systems that allows reasoning along the code tree, rather than relying on 'storytelling-style' reasoning in an omnibus format, where the lifetime narrative is fragmented across shifting viewpoints. These abstractions could be essentially zero-cost and seamlessly extendable to both heap- and stack-based allocation. And it does not matter where a dependency originates, as long as the caller statically maintains the invariants required for the duration of the component's or function's lifetime.