Language design is hard because it involves fitting together features and making sure that they interact well — in a consistent, ergonomic, understandable, and performant way.
Here are some design problems that are hard to solve, most of which have multiple solutions with different trade-offs.
Pass by value/reference Given the language doesn’t have explicit referencing:
a = 5
f(a)
Is a
copied or moved, or passed by reference? Can f
mutate a
? Does this change for different types?
Copy vs move semantics Given that you support move semantics, when do you copy/clone?
value = "hello"
a = value
b = value
Is value
moved into a
, copied into both, or referenced in both? If copied, is it a shallow or deep copy? What if some types are more expensive to deep copy than others? As above, what happens in function calls?
Method dispatch Dynamic dispatch vs monomorphization. Is overloading allowed? Which interface takes precedence?
Variable/reference immutability
What syntax is used for variables? let
, var
, const
, etc. Can a reference specify mutability in its type?
Code reuse Inheritance, composition, polymorphism, delegation, etc?
Side effect management Algebraic effects, monads, capability systems, or don’t care? Can effect handlers also invoke other effect handlers? How does the effect system dispatch to the handler (does an inner or outer or default handler take precedence?)
Concurrency What concurrency model? Are there multiple? Are there 1:1 threads? How is everything synchronized, and how can threads communicate? Is there support for structured concurrency?
Variadic functions Self-explanatory. Are they a zero-cost abstraction?
Metaprogramming Are there syntactic macros? Can code be generated by code? Can code run at compile time, and can it delete your filesystem? What effect does this have on compilation speed? Can IDEs figure it out?
Modularity
How do modules correspond to files? What is public or private? Is there a notion of pub(crate)
like in Rust? How does importing work?
Structural vs nominal subtyping Nominal requires that the type explicitly implement the interface, while structural relies on the type implementing the methods of the interface.
Self-referencing data structures Quite a headache if you implement an ownership system like Rust does.
Language vs library 3 tiers: built into language, built into standard library, included in third-party libraries. What builtins are automatically in scope?
Error handling Error handling is a philosophy. What different kinds of errors are there?
- fatal errors or bugs that crash the program like index out of bounds
- recoverable but unexpected errors like disk full
- expected errors caused by user error like file doesn’t exist How do you get users to properly handle errors, and not just propagate everything? How do you avoid the big every-error-kind enums bad Rust code has?