Typecheck if's, and do janky predeclaration
Attempt to clean up test output some.
I thiiiiiink binops and uniops should work correctly now.
People keep talking about what a small Rust would look like, and how nice it would be, and whether or not Zig or Hare or whatever fits the bill. So, I think it's time to start advertising, at least in a small way: I'm trying to make basically the language these people want, a language that asks "What would Rust look like if it were small?". I call it Garnet. I'm at the point where I am fairly sure my design is gonna do at least something vaguely useful, but I also think it's time to ask for interested parties to talk or help.
Garnet strives for smallness by having three basic features: functions, types, and structs. Somewhat like Zig, structs double as modules when evaluated at compile time. A lot like SML/OCaml, structs also double as your tool for defining interfaces/traits/typeclasses/some kind of way of reasoning about generics. If you make sure your code can be evaluated at compile time, you can treat types mostly like normal values, and "instantiating a generic" becomes literally just a function that returns a function or struct. Again, this is kinda similar to Zig, but I want to avoid the problem where you don't know whether the code will work before it's actually instantiated. Again, this is quite similar to ML languages, but without an extra layer of awkwardness from having a separate module languages. It seems to work out in theory. Whether it can be made actually convenient... well, I hope so.
I also want to solve various things that irk me about Rust, or at least
make different design decisions and see what the result looks like.
Compile times should be fast, writing/porting the compiler should be
easy, ABI should be well-defined, and behavior of low-level code should
be easy to reason about (even if it misses some optimization
opportunities). The language is intended for low level stuff more than
applications: OS's and systems and embedded programming. This makes
some Rust features unnecessary, like async/await. Better ergonomics
around borrowing would be nice too, though I'm not sure how to do that
yet, I just hate that there's no way to abstract over ownership and so
we have Fn
and FnMut
and FnOnce
. However, trading some runtime
refcounting/etc for better borrowing ergonomics as suggested in Notes
on a Smaller Rust
and Swift's
work
is not on the cards; I think there's a lot of potential for a language
that does this sort of thing, but Garnet is not that language.
Right now the project as a whole is rough around the edges 'cause I've spent a lot of time going in circles trying to learn how to write a type checker that works the way I want it to. I'm still not done (if anything I feel like I've moved backwards), but I consider the language itself maybe like 75% decided on. The main design hole right now is in fact lifetimes and borrowing; my original plan was to just implement lexical lifetimes a la Rust 1.0, then sit down and have a good hard think about that, but I frankly haven't gotten far enough to start working on that in earnest.
fn fib(x: I32): I32 =
if x < 2 then x
else fib(x-1) + fib(x - 2)
end
end
-- {} is an empty tuple, "unit"
fn main(): {} =
__println(fib(40))
end
There’s a bunch of small programs in its test suite here: https://hg.sr.ht/~icefox/garnet/browse/tests/programs?rev=12ee941c3da958f037ba0a9509d0ebc00c6c0465
And some slightly-more-interesting-but-often-still-hypothetical bits of programs here: https://hg.sr.ht/~icefox/garnet/browse/gt?rev=12ee941c3da958f037ba0a9509d0ebc00c6c0465
Just to make sure people have some realistic expectations.
Realistic language goals:
const
, pure
, noalloc
, etc)Giant scary tooling goals necessary for Real Use:
Things where you go "it's a modern language, of COURSE it has this". If it doesn't have something like this, it's a hard error.
Things where you might need to need to make explicit design tradeoffs. It concerns the overlap of design and implementation. These are essentially directions explore rather than hard-and-fast rules, and may change with time.
out of context problem
, since
it's often not undefineable, but rather it's something that the
compiler doesn't have the information to reason about.Another way to think about it is "Garnet wants to be the Lua of system programming languages". Small, flexible, made of a few powerful parts that fit together well, easy to port and implement and toy around with, reasonably fast.
iter()
, into_iter()
, and iter_mut()
.box_pattern
RFC.String
and &str
, and &[]
and []
and [T;N]
, is a little distressingAsRef
and Deref
behavior is a little distressingstd
vs core
vs alloc
-- it'd be better if std
didn't actually
re-export core
, because then more programs could be no_std
implicitly. alloc
is kinda a red-headed stepchild in this
hierarchy; Zig's approach of explicit allocator objects everywhere
may or may not be superior. Talk to some of the stdlib or embedded
people about how they'd want to arrange it if they could; papering
over weird platforms like wasm is a known annoyance.
Maybe something like core
for pure computational things, sys
for
platform-specific low-level stuff like threading and timekeeping
primitives that may appear in a microcontroller or low-level VM
without a full OS, then os
or something for stuff like filesystems,
processes, etc. Need better names though. I do like the idea of
splitting out specific capabilities into specific parts that may or
may not be present on all platforms though, instead of having a
strictly additive model.=
in let statements but :
in struct constructors,->
vs =>
still bleedin' trips me up after five
years[-1, 572)
or arbitrary size such as i23
. Maybe later.logos
lexerargh
for command line optscodespan
for error reportingThings to consider:
string-interner
(for string interning)ryu
for parsing floatsPrograms-as-separate-files tests:
test-generator
generates Rust code and recompiles if files are
changed, demonstrated here:
https://github.com/devsnek/scratchc/blob/main/tests/out.rsgoldentests
crateSomething I need to consider a little is what I want in terms of a
compiler backend, since emitting x86_64
opcodes myself basically
sounds like the least fun thing ever.
Goals:
x86_64
, ideally also Aarch64 and WASM, SPIR-V would be a
nice bonusNon-goals:
Options:
Output Rust for right now, bootstrap the compiler, then think about it.
Trying out QBE and Cranelift both seem reasonable choices, and writing a not-super-sophisticated backend that outputs many targets seems semi-reasonable. Outputting WASM is probably the simplest low-level thing to get started with, but is a little weird since it is kinda an IR itself, so to turn an SSA IR into wasm you need a step such as LLVM's "relooper". So one might end up with non-optimized WASM that leans on the implementation's optimizer.
MIT
Moved to https://man.sr.ht/~icefox/garnet/
A quote from Graydon, original creator of Rust, from https://github.com/graydon/rust-prehistory:
While reading this -- if you're foolish enough to try -- keep in mind that I was balanced between near-total disbelief that it would ever come to anything and miniscule hope that if I kept at experiments and fiddling long enough, maybe I could do a thing.
I had been criticizing, picking apart, ranting about other languages for years, and making doodles and marginalia notes about how to do one "right" or "differently" to myself for almost as long. This lineage representes the very gradual coaxing-into-belief that I could actually make something that runs
As such, there are long periods of nothing, lots of revisions of position, long periods of just making notes, arguing with myself, several false starts, digressions into minutiae that seem completely absurd from today's vantage point (don't get me started on how long I spent learning x86 mod r/m bytes and PE import table structures, why?) and self-important frippery.
The significant thing here is that I had to get to the point of convincing myself that there was something there before bothering to show anyone; the uptick in work in mid-to-late 2009 is when Mozilla started funding me on the clock to work on it, but it's significant that there were years and years of just puttering around in circles, the kind of snowball-rolling that's necessary to go from nothing to "well... maybe..."
I'd encourage reading it in this light: Delusional dreams very gradually coming into focus, not any sort of grand plan being executed.