A Few Good Ideas in Programming Languages
A Few Good Ideas in Programming Languages
Posted: 2026-05-10
Author: Pranoy Dutta
Source: prydt's site
Below is a curated list of programming language features that I find particularly impressive.
1. Flow Typing
One of the most elegant concepts I encountered in the Crystal language is simple flow typing.
Crystal is a compiled language that utilizes static type-checking, yet it adopts a syntax heavily inspired by Ruby. What makes Crystal unique is that it maintains the "feel" of a dynamically typed language by allowing a single variable to be associated with different types throughout its lifecycle—a rarity in most statically typed environments.
How it Works
Consider the following logic flow:
my_var = 5 # The type is currently Int32
assert(my_var.is_a?(Int32))
if some_complex_condition()
my_var = "hello!" # The type shifts to String
assert(my_var.is_a?(String))
end
# At this point, what is the type?
# It is now a union: Int32 | String
assert(my_var.is_a?(Int32 | String))
The compiler tracks the variable's type across different regions of the code. As shown in the diagram below:
If you attempt to call a String method on my_var at the end of this block, the compiler will throw an error because the variable is an Int32 | String. To fix this, you must use a type check (like if my_var.is_a?(String)), which narrows the type back down to just a String. This allows for a dynamic developer experience without the typical runtime performance penalties.
2. Borrow Checking
Rust is a systems-level language designed to ensure memory safety without relying on a garbage collector. It specifically targets the "loathsome" data race, which occurs when multiple threads access the same memory location simultaneously, and at least one access is a write, without proper synchronization.
The Borrow Checker is Rust's static mechanism for preventing these races at compile-time. It enforces a strict set of ownership rules:
- Lifetime Constraint: No borrow can outlast the scope of the original owner.
- Reference Exclusivity: You must choose one of the following:
- Exactly one mutable reference ()
- One or more immutable references ()
Comparison to Synchronization
This logic mirrors a readers-writer lock. The fundamental goal of concurrent synchronization is to serialize writes to memory.
| Reference Type | Quantity Allowed | Access Level |
|---|---|---|
| Immutable () | Read-Only | |
| Mutable () | Read/Write |
While the borrow checker introduces added complexity, I view it as an elegant, zero-cost abstraction. The complexity is simply a reflection of the inherent difficulties of concurrent programming.
3. Contract Programming
Most developers are familiar with the assert statement for checking invariants (e.g., "this tree must remain balanced"). However, the D programming language (which is significantly underrated) takes this further with Contract Programming, providing formal syntax for function- and object-level invariants.
assert vs. enforce
D distinguishes between internal logic errors and external failures:
assert: Used for correctness bugs. If this triggers, the programmer made a mistake.enforce: Used for external issues (e.g., invalid user input or environment failures) and throws an exception.
Function Contracts
D supports in (pre-conditions) and out (post-conditions). For example:
int daysInFebruary ( int year )
out ( result ) {
assert (( result == 28 ) || ( result == 29 ));
} do {
return isLeapYear ( year ) ? 29 : 28 ;
}
In this case, the out block ensures that any return value other than 28 or 29 is flagged as a logical error.
Class-Level Invariants
D also allows invariant() blocks to ensure object data remains consistent across all operations.
Example: Bank Account Implementation
![]()
class BankAccount {
private double balance;
invariant () {
balance >= 0; // Object-level invariant: balance can never be negative
}
this ( double initialBalance )
in ( initialBalance >= 0 ) { // Pre-condition
balance = initialBalance;
}
void deposit ( double amount )
in ( amount > 0 )
out ( balance == old.balance + amount ) {
balance += amount;
}
void withdraw ( double amount )
in ( amount > 0 && amount <= balance )
out ( balance >= 0 ) {
balance -= amount;
}
double getBalance ()
out ( result >= 0 ) {
return balance;
}
}
Using an invariant() block is far more maintainable than manually calling a consistency check at the start and end of every single method; it provides a clear, idiomatic way to guarantee object integrity.