Briar: I Hate Wrapper Tax

and also some other weird features

Programming languages are weird. And yet I've felt really good when working on mine.

I’ve only just gotten to the point where I can finally talk about my own programming language. Named Briar, it’s a tentative mix of Rust, Zig, TS, and hints of C#. More syntax to be implemented, of course.

As with almost any nerd, I have Opinions™ about programming languages. So, when the opportunity materialized for me to make my own, I kind of took it.

The following is an example using only the raw/builtin types and systems Briar provides (since there’s no stdlib) to implement stdio, and some basic wrappers.

import builtins::utf8_view;
import builtins::{integer_64, unsigned_integer_8, integer_32, unsigned_integer_64};

enum Fd {
  Stdin,
  Stdout,
  Stderr,
}

fn fd_to_num(fd: &Fd) -> integer_32 {
  return match fd {
    Fd::Stdin => 0,
    Fd::Stdout => 1,
    Fd::Stderr => 2
  };
}

fn read(fd: Fd, buf: &mut [unsigned_integer_8]) -> integer_64 {
  let mut bytes_read: integer_64 = 0;

  let mut res = builtins::syscalls::read(fd_to_num(&fd), &mut buf[bytes_read..]);

  if (res < 0) {
    return res;
  }

  while (res > 0) {
    bytes_read += res;

    if (unsigned_integer_64::from(bytes_read) == buf.len()) {
      return bytes_read;
    }

    res = builtins::syscalls::read(fd_to_num(&fd), &mut buf[bytes_read..]);

    if (res < 0) {
      return res;
    }
  }

  return bytes_read;
}

fn write(fd: Fd, buf: &[unsigned_integer_8]) -> integer_64 {
  let mut written: integer_64 = 0;

  let mut res = builtins::syscalls::write(fd_to_num(&fd), &buf[written..]);

  if (res < 0) {
    return res;
  }

  while (res > 0) {
    written += res;

    if (unsigned_integer_64::from(written) == buf.len()) {
      return written;
    }

    res = builtins::syscalls::write(fd_to_num(&fd), &buf[written..]);

    if (res < 0) {
      return res;
    }
  }

  return written;
}

fn println(str: &utf8_view) {
  guard(write(Fd::Stdout, str.bytes()) >= 0, "must succeed writing");
  let newline = "\n";
  guard(write(Fd::Stdout, newline.bytes()) >= 0, "must succeed writing");
}

fn main() -> integer_32 {
  println("Hello, world!");
  println("Say hi!");

  let mut buf: [unsigned_integer_8; _] = [0; 2048];

  let res = read(Fd::Stdin, &mut buf);

  guard(write(Fd::Stdout, &buf[..res]) == res, "must write successfully");

  return 0;
}

Feels quite a bit like Rust, yeah? With maybe a hint of a couple other things.

What, Why?

Briar is… I’d describe it as a systems language capable of OOP and composition, with strong typing and explicit knowledge-based narrowing. None of which is really demonstrated here, to be fair.

But the snippet at the top of the post is the first piece of Briar I wrote that wasn’t a syntax showcase. I used Briar’s builtin LSP and wrote it as just a demo of basic stdio. And to exercise how I feel when writing.

Turns out, I felt great. Nothing felt like it got in my way, and the compiler (but mostly LSP) was there to help me not get stuck in the syntax. Of course, completions for builtin types and functions is useful, too.

I don’t really know why I’m making it, but what I can say is that I haven’t enjoyed a non-CireilClaw project as much since I first started CireilClaw. But that’s a whole different thing.

Words

I don’t know what else to say in the post. I felt like I should write something down and publish it.

If you wish to ask me questions, lyssieth on discord or lyssieth.moe on bsky are the best way to reach me. I exist elsewhere too, but I’m most active here.

Oh, wait, right. Wrapper Tax.

So, in Rust,

struct UserId(String);

requires you to reimplement or re-derive everything on UserId, that String would normally get you, and prevents you from cleanly using UserId as a String.

In Briar,

type UserId base String {}

equivalent code would inherit everything of String’s. You would get comparisons, functions like .len, etc… basically everything for free. And it’d structurally be the same as a String. The compiler knows, so it’ll emit the right code, and at runtime you can’t easily go wrong. No real wrapper, just… nominal types with extra functions if you want them.