Stable intro
Ah, programming — a mix of art and science, or more like a balance between them.
On one side it's a pure freedom of what to write and how. On the other side it's a very strict field where every step can lead to chaos. That's why language design is a hard thing: programmers desire as much freedom as possible, but every line of code must be written with responsibility in mind. It’s all about finding the right trade-offs.
Working with trade-offs requires caution to avoid getting stuck in a local minimum. For this reason, accepting changes into the language should be a slow and steady process, not a hype-driven process.
The balance between freedom and responsibility was the main motivation for creating a new programming language called Stable. Now, let’s talk about planned features of the language.
Main features of Stable:
- Minimal and consistent syntax
- What you see is what will compile
- Elegant standard library
- Official packages
- Extensive tooling
Additional features:
- Fast compilation times
- Runtime support
- Garbage collection
- Custom allocator support
- SIMD-friendly
- Low-friction with C
Let's talk about every feature in more details.
Minimal and consistent syntax
Let’s start with the opposite: who would want a bloated and inconsistent syntax? Probably no one. However, there may be groups who prefer @ for variable declaration, while others prefer the variable keyword. Stable is part of the C-family of programming languages, with no major new concepts in syntax.
Most of the syntax is already defined, but some edge cases are still under consideration. After v1, Stable will become fully stable and frozen, with no further changes to syntax or semantics.
package main
import "sys"
const prefix string = "Hello, "
func main() {
var audience = "Stable world!"
sys.Println(prefix ++ audience)
}
What you see is what will compile
No JIT, no macros, no implicit code generation, no hidden casts, no implicit returns, and so on.
In other words, the idea is simple: don’t hide the real workings behind syntactic sugar unless there’s a strong reason for it. A bit of verbosity is okay.
Elegant standard library
A language without a useful standard library is like an engine without wheels. You can’t go fast or far just by having power; something needs to move you forward. This is the role of the standard library in a programming language.
However, simply throwing a bunch of packages for every possible use case won’t work either. The standard library should not only provide common routines (who wants to implement DateTime over and over?) but also serve as glue for the code. For example, the HTTP client interface should be standardized and respected across the entire ecosystem.
Official packages
This might sound similar to a standard library, but it refers to a much broader set of packages.
For example, imagine there are 4 database clients for the XYZ database in an ecosystem, all performing the same task in a similar way. Is there any good reason to have 4 separate implementations instead of 1 official version?
Focusing efforts on a single, official implementation leads to a higher-quality solution that benefits everyone, while also preventing unsupported, vulnerable, or outdated dependencies right from the start.
Extensive tooling
Tools make day-to-day work with a programming language smoother. Essential features like testing, benchmarking, fuzzing, debugging, formatting, linting, code generation, dependency management, and refactoring should be part of the official tooling. This reduces ecosystem fragmentation and enhances the tools’ quality by reaching a wider audience
Now about additional but still important features.
Fast compilation times
As you can guess, Stable is designed to be a compiled language. Having a single binary makes it simpler to manage, deploy, and upgrade. Ideally, there should be at least two independent implementations to cross-check them and catch hard-to-find bugs.
Speaking of compilation, easy cross-compilation is also one of the key goals.
Runtime support
A programming language with the runtime is easier to pick and be productive from day one. While there’s no silver bullet to solve all problems, a well-designed runtime can be seamless and resource-efficient with minimal configuration.
Garbage collection
Back to trade-offs: nowadays, the cost of garbage collectors (GCs) is almost negligible for most tasks, and GCs continue to improve year after year.
However, to allow full control over memory, the GC will be tunable, enabling each program to be optimized for specific use cases.
Support for custom allocators
With all the respect to garbage collectors (GCs), they shouldn’t prevent developers from managing memory semi-manually. While not everyone needs allocators everywhere, having this tool available out of the box can lead to faster software where it’s necessary.
Allocators should be part of the standard library, and the runtime shouldn’t restrict developers from implementing their own allocator strategies.
SIMD-friendly
The 21st century is 25% done, and SIMD hasn’t been a niche technology for years. A programming language shouldn’t overlook CPU capabilities without good reason. But simply providing access to SIMD intrinsics is a local maximum, and that’s not the right approach.
var a, b []int32 = { ... }
c := a * b // will be the same as:
for i := 0; i < c.len; i += 1 {
c[i] = a[i] * b[i]
}
The ability to perform operations on arrays should be seamless, with all responsibilities handled by the compiler.
Low-friction with C
C is the lingua franca of programming, and Stable should be easily linkable with C libraries.
Furthermore, C libraries should be sandboxed to enhance security and prevent memory issues.
Next steps
The Stable compiler is currently in its alpha stage. After proof-of-concept cleanups, it will be released under the MIT license for everyone. Below is an image illustrating the process of how everything will be implemented:
Next blog post will dive deeper into the implementation details and upcoming plans. Stay tuned!
See Github Discussion for comments