Project / researching
A new programming language
A brand new programming language

I’ve always love writing parsers and playing around with DSLs. I’d love to be a bit more ambitious, though. This is quite a lot more ambitious.
My perfect language
Of all the languages I’ve used so far, Rust is my favourite. There are a few key reasons:
- It produces very fast code, much like C++
- The compiler catches way more bugs than any other language I’ve used
- Enums (sum data types) are really excellent for modelling states and complex logic.
It’s not perfect though, and there are many issues:
- Slow compile times
- Language server experience is suboptimal vs say C# or Kotlin
- Complexities around async/await (Pin, function colouring)
- No specialization (unsound)
- Complexity around a bunch of fairly common system programming techniques - use of uninitialized memory, mmaps, custom (and fallible) allocation.
- I find it quite verbose sometimes.
I’d like to try to build something that solves some of my perceived problems - my “perfect” language.
Other
Mojo (https://mojolang.org) has actually made a lot of inroads into this:
- Alternative borrow checking system and move semantics mean no Pin for asnyc/await, and in theory easier learning curve for people not used to ownership semantics.
- Clever use of MLIR dialects for progressive lowering and refinement before hitting LLVM reduces the impact of monomorpized generics, for much faster compilation.
I’d recommend people check out mojo if they haven’t Mojo isn’t quite the language I want though, so I’ll hopefully produce something worthwile here, borrowing ideas that look cool.
I’m not a PL guy, and this is as much a learning project as anything else so we’ll see how it goes.
Goals
- Safety/Performance of Rust
- Strong abstract modelling
- Excellent compile time support
- Less boilerplate
- Faster compilation
- More ergonomic support for high performance techniques
Current Status
Researching and learning some of the technologies I want to use. I do have some thoughts
Linear types
Linear types is an interesting idea stemming from functional programming languages (as most interesting type ideas seem to…). In all the most popular languages (JS/TS, Python, C/C++, Swift, Kotlin) types can be referenced, copied and generally thrown around however you like. Rust is different: types (by default) can only be used at most once. Used in this sense means moved (to another location or function), or consumed (by destructuring). Once they have been used, they are gone and cannot be accessed. If you don’t use it, then it disappears at the end of the scope. If it requires cleanup, the compiler inserts calls to Drop::drop at appropriate places (this is called destructors in other languages). This is key to how the borrow checker works. In PL language circles these are known as affine linear types.
A linear type by contrast must be moved or consumed exactly once. This means you have to explicitly “consume” the type (by destructuring it), or call a function that takes the type by value, moving the parameter. You can think of it as forcing you to either pass the object to something else to deal with, or explicitly call destructor functions.
Let’s use some pseudorust as an example, with linear types, declared using a linear keyword.
linear struct Point {
x: i32,
y: i32,
}
fn destroy(p: Point) {
Point { .. } // Consuming "destroys" the type, leaving just it's members. None of them are linear types, so they don't need explicit destruction.
}
fn print_bad(p: Point) {
println!("x: {}, y: {}", p.x, p.y);
// Doesn't compile - p is not consumed or moved.
}
fn print_good(p: Point) {
println!("x: {}, y: {}", p.x, p.y);
destroy(p); // Works as p is moved.
}
linear struct Point {
x: i32,
y: i32,
}
Gotchas:
Suppose I have a linear type, with two linear fields, only one of which is public:
mod FooMod {
#[derive(Default)]
linear struct Foo {
pub a: Linear,
b: Linear.
}
pub fn frobnicate(x: Linear) { .. } // Consumes a linear type.
pub fn consume_foo(F: Foo) {
Foo { a, b };
frobnicate(a);
frobnicate(b);
}
}
use FooMod::{Foo, consume_foo, frobnicate};
fn main() {
let foo = Foo::default();
frobnicate(foo.a);
consume_foo(foo); // Can't do this, as a is not moved...
}
The problem is that you’ve moved a, thus Foo is no longer a complete struct, and can’t be moved around, and hence can’t be passed to functions. You also can’t destructure it, as at least one member is private.
Possible solutions:
- Mechanism to allow functions to take “partial” structs (i.e. indicate that they don’t use certain members). Possibly contracts could be used to do this - a precondition on the function?