158 points andsoitis 2 hours ago 54 comments
iacuobos 2 hours ago | parent
andsoitis 2 hours ago | parent
Why Goose - https://aardappel.github.io/goose/#why-goose
FrustratedMonky 2 hours ago | parent
Edit Man, two posting about Goose back to back. Carmack alone might be enough to read further.
tobiolo 1 hour ago | parent
pulse7 2 hours ago | parent
stickfigure 1 hour ago | parent
Apache modules worked like this (still do, presumably). Each request has a stack; allocations bump the stack pointer; when a request is finished, the stack pointer is reset. Incredibly efficient as long as you don't need to keep memory longer than a single request.
Looks like in Goose, each scope acts like an apache "request".
andsoitis 1 hour ago | parent
Check the features section (https://aardappel.github.io/goose/#features), which starts off with how memory works.
malisper 1 hour ago | parent
At compile time, Goose will analyze your program and determine a number of stacks, N, for it. N is the maximum number of "growable" items you can have in existence at any given point in time. Using virtual memory tricks, each stack can grow up to 2GB, but it uses a contiguous address space and only uses as much physical memory as it actually needs.
For the program you're describing, I think you would end up with two stacks: one to parse the input and a second to print it. When you parse the input, Goose reads characters from the string one at a time and puts them onto the stack. Once it has hit the end of value, it will update the stack so it now represents the decoded JSON value (strings will just leave the characters in place, numbers will pop off the characters and push the number onto the stack). Goose repeats this until it has parsed the whole JSON, and now the whole JSON is on the stack.
Then, when you print the JSON, Goose uses the second stack to build a string. Like the parsing approach, it iterates through each element of the JSON and pushes its string representation onto the stack. Once it's done, it resets the stack pointers to free the memory.
This is really nice because allocating memory is just adding to the pointer, and freeing it is subtracting from the pointer.
In contrast, Rust automatically inserts malloc() and free() calls throughout your code, which are much more expensive than pointer arithmetic. On top of that, freeing a vector of heap-allocated objects is O(n), whereas if done right in Goose, it would be O(1)
Aardappel 59 minutes ago | parent
Goose can store entire trees (of variable sized things) inside an array.
andsoitis 2 hours ago | parent
weinzierl 2 hours ago | parent
Where does Goose sit when classified along these dimensions?
Sure, performance is right there on the packaging but no word about UB in the README. I read "Goose has syntax familiar to C and Rust" as DX in the same ballpark, but is it a performance first or safety first language.
If it is the former it occupies the same crowded niche as C++ and its many cousins and will have a hard time.
If it's the latter it will have much more room to prosper. We don't have enough languages in the niche that Rust and Ada fill right now.
vanderZwan 1 hour ago | parent
> Goose: my latest programming language. Safer, faster and less memory than Rust/C++ for algorithms where tightly packed, flat data structures matter.
> It can represent more kinds of data structures as flat, pointer-free bytes than any language I know.
https://xitter.cf/wvo/status/2107979660558766351
So one way to answer might be: how often do you reach for unsafe when using Rust? If you don't I guess Rust is safer. If you do… it gets trickier.
As for UB, I guess you could try looking through the spec and/or the reference? Like, the code is mostly generated by Claude after WvO wrote a specification. If it were anyone else I'd be worried, but given WvO's cv I actually suspect that means the spec leaves basically no ambiguity.
However, Goose compiles to C. Does that by definition mean UB sneaks in, or is there a "UB free" C subset? I don't write C, so I don't really know.
weinzierl 52 minutes ago | parent
The complete omission of UB in the README could be one of two things. Either that the topic is not important or that it is considered a given and not worth mentioning.
vanderZwan 38 minutes ago | parent
Note also the fact that e.g. array indexing has bounds checks without escape hatches for the programmer, but the compiler can elide quite a few of them, and the language has tools to help the programmer write code that is guaranteed to be check-free.
I think that suggests the ambition is to compete with Rust in the domain of combining safety with not compromising on performance.
baalimago 2 hours ago | parent
IshKebab 1 hour ago | parent
ivanjermakov 1 hour ago | parent
rfgplk 1 hour ago | parent
If anyone is interested, I'm fairly close to finalizing Slang and I'm considering open-sourcing it in the near future. What originally started as a simple register based lang for an embeddable VM turned into an insanely expressive meta?programming language. Full system Fω, expansive whole program reflection (you can reflect on absolutely anything), string memory & logic safety..
You can do stuff like this:
(fn answer () -> i64 0)
(defpass build-answer
(phase source)
(transform (lambda (f)
(fn-with f 'body '(let ((a (arr (42)))) (at a 0)))))
(safety (lambda (goal) (syntax (refl)))))
(run-pass build-answer answer)
(fnmut answer
(transform (lambda (f)
(if (memory-safe? (check-memory (current-program)))
f (syntax-error 'unproved-memory)))))
(defpass inspect-ssa
(phase ssa)
(transform (lambda (f)
(if (memory-safe? (check-memory (ssa-current-program)))
f (syntax-error 'unproved-ssa-memory))))
(proof (lambda (goal) (syntax (refl)))))
(run-pass inspect-ssa answer)
(answer)
or this: (fn next ((i i64)) -> i64 (+ i 1))
(fncontract next
(ensures result (successor (Eq i64 result (+ (old i) 1)) auto)))
(fn read-second ((i i64)) -> i64
(let ((a (arr (0 42)))) (at a i)))
(fncontract read-second (requires (one (Eq i64 i 1))))
(let ((i (next 0)))
(with-proof ((one (Eq i64 i 1) (result-proof i successor)))
(print (read-second i))))
it also supports full inline asm or llvm ir: (var answer 0)
(asm (input rdi 20)
(input rsi 22)
(output rax answer)
(mov rdi rax)
(add rsi rax))
(print answer)
(macroop \ (a) (asm (mov a rax)))
(\ 42)
(print (llvm add 20 22))
(print (->i64 (llvm fused-multiply-add (f64) 6.0 7.0 0.0)))
(llvm fn sum32 ((x i32) (y i32)) -> i32
(block entry
(value answer (llvm add x y (declare (flags nsw))))
(llvm return answer)))
(print (->i64 (sum32 (the i32 20) (the i32 22))))
Even though you can write this by hand, the core idea is to get a very dense and terse language that LLMs can emit while guaranteeing safety and performance.anonymous908213 1 hour ago | parent
FrankWilhoit 1 hour ago | parent
mihaaly 1 hour ago | parent
Please share it with logistics guys, since putting stuff from here and there into warehouse storage, then taking away, put some more in than planned sometimes, keeps spinning their heads. They need all the good logic!
So, yet another programming language, that's nice. I never thought there will be one more beyond English.
Evolution of programming languages:
- 1940s → Machine Code ( 0s 1s )
- 1949 → Assembly
- 1957 → FORTRAN
- 1959 → COBOL
- 1964 → BASIC
- 1970 → Pascal
- 1972 → C
- 1983 → C++
- 1991 → Python
- 1993 → Ruby
- 1995 → Java
- 1995 → JavaScript
- 1995 → PHP
- 2000 → C#
- 2009 → Go
- 2010 → Rust
- 2011 → Kotlin
- 2011 → Elixir
- 2012 → TypeScript
- 2014 → Swift
- 2015 → Solidity
- 2026 → Englishanigbrowl 1 hour ago | parent
I wonder if the name of the language was inspired by the way geese fly in formation, with trailing birds saving energy by flying inside the aerodynamic shadow of the leaders, and swapping in and out as needed. If so, a very nice metaphor for the memory management model.
I was also impressed by the documentation. As we know AI-generted docs tends to be rather mediocre, but the author has clearly invested time in making in readable such that the relevance and rationale of design decisions provide the context that a curious programmer needs to understand why things are implemented in a particular way.
I really appreciated the detailed samples, ranging from simple algorithmic theorems up to a small game. Again, I don't have the experience to rate the language vs others, but I was struck by readable the code was and how easy it was to follow task flow. Not having to think about classes is always a plus!
Anyway, I usually the the offering of a new language with dispassionate interest, as something to be aware of and but not get too invested in or familiar with until it gains enough popularity to have a good shot at longevity. Goose is the first language in a long time where I looked at it and immediately thought that I'd like to use it for its own sake.
Aardappel 54 minutes ago | parent
The name is an animal because all my past PL designs are animals (I also made Lobster etc). Goose seemed like a fun name.
malisper 1 hour ago | parent
For instance, how would you represent a hash table where the values are strings and at random, the program inserts or deletes a random string?
This doesn't seem like something you could easily represent. The only way I can think of is to keep a stack that holds all of your strings, and you periodically compact it.
Edit: Maybe you could also have a separate stack for strings of various sizes. One each for strings less than 8, 16, ... bytes. But at that point you're basically implementing your own malloc by hand.
Edit: Fwiw I do think Goose is very cool! I've had to hand-roll a lot of similar stuff in Rust before to speed up memory management
andsoitis 1 hour ago | parent
Goose has a built-in generic hash table called dictionary<K, V>, provided by its standard library.
https://aardappel.github.io/goose/docs/stdlib.html#dictionar...
malisper 1 hour ago | parent
> Keys and values are fixed-size
so not quite what I'm looking for
andsoitis 1 hour ago | parent
Conceptually:
dictionary<i64, StringReference>
|
+----> "hello"
+----> "a much longer string"
+----> "x"MeetingsBrowser 1 hour ago | parent
> Goose code holds typed references where the equivalent Rust holds u32 indices into an arena — and it is where a good share of the benchmark wins come from.
James_K 1 hour ago | parent
It's not entirely mad to do it this way, and I can see some benefits. For one, you might have multiple of these pools across the program for different data-types, which would make allocating to any one of them a cheaper operation. It is much more reference safe since you are only ever freeing stuff in a certain context and you can still never have dangling pointers. When you look at memory safe languages, they all default to this eventually. Like Rust in the advanced case will have you implementing your own allocator, lot's of games use this thing called ECS which is essentially a kind of allocator. Goose would just have it be more common.
malisper 1 hour ago | parent
progval 1 hour ago | parent
malisper 1 hour ago | parent
That example implements a linked list of integers. I'm curious how you would do a linked list of strings
Aardappel 1 hour ago | parent
For example, you can build up a tree of all variable sized nodes inside a single array.
Where it gets interesting is if you must arbitrarily delete tree nodes, and you can't structure it to do that in stack order. The language has various tools for this, like fixed arrays with capacity, or pools of either single elements or runs of elements.
rvz 1 hour ago | parent
Yet, the author expects YOU to read all of what they didn't write. So I ain't reading all of that.
pron 1 hour ago | parent
timcobb 1 hour ago | parent
What are the ergonomics of this I wonder.
skrebbel 1 hour ago | parent
daoxid 1 hour ago | parent
malisper 1 hour ago | parent
Rust does a similar thing. When a function returns a value, a location for it will be put in the callers stack frame. That means it automatically stack-allocates values when feasible. The downside in Rust is you need to know the size of the value at compile time whereas Goose can do it for arbitrarily sized values
Aardappel 1 hour ago | parent
onlyrealcuzzo 51 minutes ago | parent
Something that I'm interested in: How fast is the compilation speed on large programs compared to Zig, C, Go?
My ideal language is safer than Rust (no deadlocks / livelocks, no stack overflow from recursion), is optimized for cache locality, but compiles faster than Zig.
This seems like it's close to 2/3.
Aardappel 14 minutes ago | parent
That said, during development/iteration/testing, you can just the built-in JIT, which is based on libtcc (which is very fast), as don't as you don't mind a ~3x runtime slowdown.
briandw 49 minutes ago | parent
loufe 12 minutes ago | parent
jnpnj 30 minutes ago | parent
ps: Ah well, I could have searched before asking https://strlen.com/programming-languages/
super neat history !
Aardappel 17 minutes ago | parent
pixelesque 16 minutes ago | parent
e.g. I see a general lack of reserve()s on strings and vectors (a few tests do use them a bit, like `graph` and `particles`), and there are several other things which I wouldn't personally call "optimized", so I assume those ones are the 'idiomatic' ones?
Same goes for the Rust code: a lot of the code seems to be using Vec::new() and String::new(), instead of ::with_capacity() which would optimize the memory re-allocation (reducing it as the containers/strings grow).
dash2 12 minutes ago | parent
kadw 1 hour ago | parent
Embassy (https://github.com/embassy-rs/embassy) uses this concept of no heap allocation and I find it really solid. Even if you write in C, most of the times you don't use heap. MISRA, the C coding standard for automotives, doesn't allow dynamic allocation!
Aardappel 1 hour ago | parent