I’ve been designing and working on JMLL for a few months now and I decided to start a blog so I figured it made sense to finally document it publicly here and declare some of the higher level features and changes that are important to it. Programming languages are a lot like standards - very specific and there are both too many and not enough. So I figured it’s time to throw my hat in the ring!

Before that I should probably introduce myself… as this is my first post. I’m Janet, I’m a software developer currently employed working with C#, but wanting to work on things in Rust/Lower down in the system. In the meantime I am working on a language to hopefully be an ergonomic improvement over Rust and some other languages that I have some qualms with (implemented in Rust). Outside of programming language discussion you will find that I blog about anything from technology, cats, (computer) history, and basically whatever I personally find interesting and enjoyable to talk about! I welcome you to read, and I am available on the internet through fosstodon, github, (I am on various group messaging apps, but not for public facing messaging) or privately through Email.

I have been working with Rust for over 6 years at this point, I’m very familiar with its user experience and its pros/cons, I believe there is room for something higher level than Rust but lower level than Javascript, Python, etc, and I’m aiming to make that language a reality in JMLL.

What is JMLL?

JMLL is a language built to be ergonomic, pleasant, and correct. It is built in Rust and compiles down to Rust, and it is built to be compatible such that it can call and use Rust libraries directly from it. It is explicitly not a C/C++/Rust replacement language, it is Just a Medium Level Language.

JMLL is focused on MRC - Minimum Required Complexity

Modern programming is very complex, there is a lot that goes into what makes your program go from source code to machine operations. Each step in the stack has its own massive amount of complexity and it is only getting more complex. Modern programming languages are similarly very complex to meet the needs of modern programmers programming for modern machines. JMLL wants to meet the needs of those programming paradigms with a minimal amount of required mental complexity. There is no magic way to do so, JMLL is not as ambitious as something like Rust in scope, but it has some similar goals meaning the language will be a lot more restricted and focused - by necessity. Decisions and defaults are made in the name of removing mental overhead in writing everyday programs. JMLL aims to be an easy way to write correct, ergonomic, safe, reasonably fast software in a very efficient manner. Developer experience is very important in JMLL, it should be easier to do the correct thing and JMLL is very willing to change in order to allow developers to do the correct thing more often.

JMLL is syntactically a mix of Rust and Scala, with a lot of inspiration also taken from Swift. JMLL is an automatically managed memory safe language, with a focus on allowing you to write simple, correct code while getting in the way as little as possible. The syntax is a little unique at first but is designed to compose very well and allow the entire language to be comprehended at a glance. JMLL strives for correct defaults as “the standard way of doing things” and I want people to be pushed away from patterns that are known to be harmful. This is done through extensive tooling, comprehensive design, and a focus on user-first experience. Much of this is taken from experience with RustC and Cargo itself, but also other tools like Clippy. I have also taken note of a lot of pain points in Rust and it’s tooling ecosystem as well.

JMLL has been a vision in my head for a while, and I actually remember reading Notes on a Smaller Rust (give it a read if you haven’t!) when it was released and completely agreeing with it and wanting to take it further… Now I think the time has come for me to actually put my thoughts and wants into actions.

Hello World, this is JMLL

JMLL is… a lot like Rust, the syntax for Hello World is also very similar

pub main() {
    print("Hello World!")
}

In JMLL

  • Blocks are defined using braces
  • Semicolons are not required
  • pub is used without fn when denoting the main function

Let’s look at something a bit more interesting, let’s port the guessing game example from The Rust Book

use std.cmp.Ordering
use std.rand

pub main(): i32! {
    print("Guess the number!")
    let secret_number = rand.range(1..100)

    loop {
        // Error up on failure
        var guess = input("Please input your guess.")?

        let number: u32 = guess.trim().parse().match {
            Ok(num) => num,
            Err(_) => continue,
        }

        print(f"You guessed: {number}")

        match number.cmp(secret_number) {
            Less => print("Too small!"),
            Greater => print("Too big!"),
            Equal => {
                print("You win!")
                break
            }
        }
    }
}

Okay, that looks like something we can dissect a bit better.

main() here still takes no arguments (it can), but this time it returns i32!, i32 is a 32 bit signed integer, the ! in type position means that it is a fallible type (Result in Rust terms). main() is able to return automatically formatted errors or program codes in an expected way at end of program execution.

in the use statements you will notice that lowercase denotes modules and starting with a capital denotes concrete items -screaming case denotes constants.

? - One of my favorite features in Rust makes a return in JMLL, the unwrapping ?, it can be thought of as you asking “is the type I want inside here?”. In a type context it will work like ! does for Results, marking whether a type is Optional.

f"" Format Strings - taken from Python. Static string formatting supporting local interpolation and Rust’s standard formatting options.

loop {} Same as Rust

var and let are how you define variables in JMLL. var declares them allowing mutation and reassignment, let is for immutable and read only variables.

.match An addition that I’ve really been enjoying, post-fix match (and similar operators), which lets you read expressions in a match statement completely from left to right (prefix match still supported as shown)

match automatically brings matched members into scope. This is a personal preference but I believe its one that makes sense for projects. To introduce a new variable into scope with a name you would use var or let like done when introducing a variable otherwise.

Other Changes

Generics - JMLL will support a subset of the generics functionality similarly offered by Rust. Generics use [] for notation. They come before the arguments of a function () and use that syntax to denote “Type-level” arguments, while still being visually distinct from normal functions/function calls.

Indexing - Your next question after the above might be “Well, how do you index then?”, and I simply say - Call the indexing function! indexing is a functional operation on collections and one that usually has need to be implemented as one anyways, so I don’t see why it needs syntax outside of normal function call syntax.

Variable Shadowing - Disallowed. I believe that due to various rules when programming (closure capturing, unassigned variables, automatic assignment/destructuring, etc) there are scenarios where variable shadowing can obscure the true nature of the flow of data, and can impede debugging visually and logically. The cons of variables shadowing outweigh the pros. Take this rust block for example (taken and modified from a discord server)

.map(|Pos {row, col }| {
    let Pos {row, col} = match row {
        1 | 2 => Pos {
            row: row - 1, col: col -1
        },
        3 => match col {
         // cut for brevity
        }
    };
    row * 10 + col
})

It logically works out, but the combined destructuring, assignment, matching, and closure interactions just makes it confusing to somebody who isn’t intimately familiar with Rust’s rules to determine what is actually going on in a block like this. I don’t believe it’s rust’s convenience rules that are the problem here, i believe it is shadowing and the way it interacts with those rules.

Till next time

My next JMLL post will likely be talking about the memory and move semantics. But to give you a teaser: when programming in JMLL you will generally not worry about the Stack or the Heap, you will generally only be worrying about mutability, access, and movement.

the JMLL compiler and tooling are not yet open source, that will be coming in the next few months but it needs a fair amount of polish first (and a lot more general implementation)

I sincerely thank you for reading my first blog post of the new year! Here’s to 2024!