Three Good Language Design Ideas Learned from Crystal and Rust
Explaining language design based on an essay covering Crystal's flow typing, Rust's borrow checking, and contract programming.
A personal essay published on May 10, 2026, organized excellent ideas in language design. The essay was featured in reporting on Lobsters via prydt.xyz by anex9d. The author is Pranoy Dutta. The three topics covered are flow typing, borrow checking, and contract programming. All are mechanisms already implemented in practical languages. The focus is on balancing static checking and runtime cost.
The essay is not intended to promote any particular language. It focuses on type and checking techniques that improve the developer experience. Crystal, Rust, and TypeScript appear as examples. Ruby-like syntax and concurrency safety are also discussed. The content can be read as guidance for language choice.
Flexibility and Safety Through Flow Typing
Flow typing is a mechanism that narrows a variable’s type according to control flow. Crystal is a compiled language with static type checking. Its syntax is close to Ruby. A variable can hold values of different types during its lifetime. This behavior differs from many statically typed languages.
The essay shows an example like the following. Immediately after assigning an integer to a variable, it is treated as Int32. If a string is assigned inside a conditional branch, it is treated as String in that region. At the join point of the branches, both possibilities remain. As a result, the type becomes a union of Int32 and String.
my_var = 5
# Type at this point is Int32
if some_complex_condition()
my_var = "hello!"
# Type in this region is String
end
# Type at the join point is Int32 | String
While it remains a union type, string-specific operations cannot be performed. The implementation rejects misuse through type checking. Users add narrowing through conditionals. A check with is_a? limits the type candidates. It is a technique that preserves the feel of a dynamic language while ensuring safety.
TypeScript also provides similar narrowing. Conditionals and type predicates reduce union type candidates. Static checking can detect incorrect operations in advance. A notable feature is that increased runtime overhead can be kept down. It can be seen as a reconciliation of flexibility and rigor.
Concurrency Safety Through Borrow Checking
Rust guarantees memory safety without using garbage collection. Preventing data races in concurrent processing is an important challenge. If multiple threads read and write the same memory without synchronization, bugs occur. The borrow checker prevents this problem at compile time.
The rules are simple. A borrow must not outlive the owner’s scope. Only one mutable reference is allowed at a time. Multiple immutable references can coexist. Mutable and immutable references may not coexist. It is a discipline similar to a reader-writer lock.
The passage quoted from the original is as follows.
Any borrow must not out-live the scope of the owner
Reads do not conflict with each other. Simultaneous execution of writes and reads is dangerous. Serializing writes avoids conflicts. This idea is at the core of the rules. Checking does not require additional runtime mechanisms. The cost is limited to checking by the implementation and complexity in writing code.
Increased complexity is a practical burden. Concurrent processing itself is inherently complex. It can also be seen as a design that surfaces complexity on the language side. The benefit of eliminating incorrect sharing early is large. It is highly valuable in developing system software and drivers. Interest in low-level development is growing. Even in kernel-related changes such as AMDGPU Separates HDMI Passive VRR Support for Linux 7.4, safety and separation are challenges. Stricter memory management directly affects the reliability of overall processing.
Contract Programming and Guaranteeing Invariants
Contract programming is a technique for making invariants explicit. Many developers have encountered assertions. Assertions report an error when an assumption is broken. They describe promises at the entry and exit of processing. The purpose is to maintain consistency of internal state.
Preconditions are promises the caller must uphold. Postconditions are results the callee guarantees. Invariants are properties maintained throughout execution. Embedding these as checks allows early detection of misuse. It also has the effect of keeping documentation and checks consistent.
Assertions are often evaluated at runtime. Combined with static checking, detection can be made earlier. There is also a side benefit of reducing ambiguity in specifications. It helps in designing tests and ensuring reproducibility. Interest in safety is growing beyond languages. As shown by OpenAI Announces Private Safety Processing, Achieving Zero Data Retention, promises and verification in processing are common challenges in infrastructure design.
Design by contract faces the challenge of balancing runtime cost. Too many checks affect performance. Too few checks delay detection. Judgment is required to focus checks on important boundaries. Effective use in combination with operational logging is also useful.
Implications for Language Design and Future
Directions
What the three ideas share is early detection of misuse. Flow typing tracks type possibilities. Borrow checking eliminates dangerous sharing. Contracts guarantee agreement between assumptions and results. All make developer intent machine-checkable.
There are also implications for implementation. Advanced type inference can reproduce the feel of dynamic languages. Tracking ownership reduces concurrency errors. Systematizing assertions narrows the gap between specification and implementation. Lowering the burden of explanation to users can be seen as promoting adoption.
Integration with tools is also important. Immediate diagnostics in editors help understanding of narrowing. The quality of error messages determines learning cost. Improving explanations of ownership errors is known as a challenge for Rust. For contract violation reports, ease of identifying causes is also key.
Language choice depends on use. In application development, the flexibility of flow typing is effective. In infrastructure development, the rigor of borrow checking is effective. Contracts are effective when boundaries need clarification. The realistic solution is a combination, not a single correct answer.
Editorial Opinion
Regarding short-term impact. In the next 3 to 6 months, use of narrowing features in existing languages is expected to advance. Explanatory materials on TypeScript type narrowing and Rust ownership may increase. Sharing of design guidelines based on static checking is also expected to advance. Improvements in error reporting are assessed to lower barriers to adoption.
Regarding the long-term perspective. In the next 1 to 3 years, the ideas of ownership and contracts are expected to spread to other languages. Concurrency safety may expand from infrastructure to applications. Integration of specifications and checks is assessed to approach a standard for quality management. Instruction on preconditions and invariants is also expected to increase in curricula.
Questions from the editorial team. Which should be prioritized, flexibility or rigor? Will stricter checking raise learning costs? Can tool support sufficiently absorb complexity? The merits of language features are seen to vary with use cases. Consideration in light of readers’ own development areas is deemed necessary.
References
- “A Few Good Ideas in Programming Languages”, by prydt.xyz via anex9d — Lobsters, 2026-09-12T18:19:16.000Z (ARR)
- Source URL: https://prydt.xyz/blog/a-few-good-ideas-in-pl/
Frequently Asked Questions
- What is flow typing?
- It is a mechanism that narrows a variable's type candidates according to control flow. Crystal and TypeScript implement it. Conditional checks can limit union type candidates. It is a technique that combines the feel of a dynamic language with static checking.
- What does Rust's borrow checker prevent?
- It is a mechanism that checks the rules of ownership and borrowing of memory. It prevents memory misuse such as data races at compile time. It enforces the exclusivity and scope of mutable references. A key feature is that it adds no runtime mechanism.
- Where is contract programming effective?
- It is a technique that makes preconditions, postconditions, and invariants explicit. It is effective for detecting misuse at boundaries. Combined use of runtime checking with assertions and static checking can be considered. It is used to keep specification and implementation consistent.
Comments