Just foxing about.




Last active 14 hours ago


Last active 4 months ago


Last active 7 months ago


Last active 2 years ago

#8 Thoughts on modules 14 hours ago

Comment by ~icefox on ~icefox/garnet

Okay, it turns out that 1ML might be exactly what we want. Good introductory talk: https://www.youtube.com/watch?v=42Wn-mXWcms

#26 Implement fuzzing 3 days ago

T-TODO added by ~icefox on ~icefox/garnet

#26 Implement fuzzing 3 days ago

Ticket created by ~icefox on ~icefox/garnet

I'm sure it will find all sorts of exciting things, even if only with the parser. quickcheck seems to be the go-to solution, and there's a small useful intro to using it for parsing here: https://adriann.github.io/rust_parser.html

#25 Fix coverage tests with tarpaulin 3 days ago

Comment by ~icefox on ~icefox/garnet

Well I tried updating to tarpaulin 0.18.0-alpha2 on my local machine and it made most of the endtoend.rs tests fail, rustc ICE's at least once, and the error output is 6 megabytes. I'm honestly pretty impressed!

It appears to build endtoend.rs successfully, in fact, and somehow doesn't execute it with the right command line args. Running the resulting test binary by hand is what makes all that stuff happen. garnetc invokes rustc and I don't think anything in the generated test binary itself does, so Something Weird is happening somewhere.

#25 Fix coverage tests with tarpaulin 6 days ago

T-BUG added by ~icefox on ~icefox/garnet

#25 Fix coverage tests with tarpaulin 6 days ago

Ticket created by ~icefox on ~icefox/garnet

With the advent of lang_tester executing garnetc as a separate process, tarpaulin a) takes about a minute or two to start executing, and thus times out builds.sr.ht, and b) doesn't check for code executed in the resulting garnetc binary, so coverage goes from 76% to 62%.

Per xd009642 on the Unofficial Rust Discord:

it won't followed exec'ed processed by default but the alpha releases have a --follow-exec that will and should work reasonably well ... There's not a lot of people using --follow-exec right now (but there is a handful I know of) feel free to let me know if there are any issues.

I'm vendoring my own tarpaulin binaries so CI don't have to rebuild them each time, so I can just update those.

#7 Thoughts on matches/tree-walking (and data in general) 6 days ago

Comment by ~icefox on ~icefox/garnet

Under that assumption, your code would have to work with a static foreach kind of construct, so that iteration over the enumeration variants is achieved.

If I'm understanding what you mean correctly, you could represent this at runtime. Just make each enum automatically generate an EnumType::VALUES constant that is an ordered array of the enum's values. If your compiler's const optimizations are good enough I don't see why this couldn't be just as fast as making it a special case.

The two use cases you've highlighted need to be separated, as they're used in typically quite different contexts.

Exactly, and I'm hoping that having the ability to pack together and unpack the discriminant from a sum type and manipulate them separately we can get the best of both worlds. The "arbitrary discriminant for a sum type" use case is easy, the "set of related integer constants" use case is easy, and with some fairly straightforward screwing around you can write code that translates from one to the other.

#24 Newtype structs and related silliness 6 days ago

Comment by ~icefox on ~icefox/garnet

The issue with public stub functions is that they have to be output, for the sake of external (foreign language) code where it can't be inlined.

Well, either we need a function or we need to be able to define how structs and tuples are constructed as part of the ABI so external code knows how to do MyNewtype(3) on their own. The function is the uniform solution, and Rust kinda ends up with the same pattern: most complex types have a Whatever::new() -> Self constructor. Either way, doesn't need to be solved permanently this instant.

Privacy being the default with pub being opt-in is probably the best solution, yeah. I just need to fit it into my mental model of how the language works. It's not the simplest solution, but it's probably valuable enough to be worth the complexity.

#17 Thoughts on type metaprogramming 9 days ago

Comment by ~icefox on ~icefox/garnet

Oh wow, that article is utterly amazing. Thank you! It definitely has given me plenty of things to think about, and I need to re-read it again a time or two. I'm still pre-coffee though so I need some more time to absorb it. Two things that stand out to me:

  • If we use ZST's for expressing properties of functions, we need syntactic sugar around it. I love the idea and I am very quickly going to get sick of writing stuff like let add: Pure[NoAlloc[Fn(I32, I32): I32]] = .... It also means that adding traits to a function, ie making the restrictions stricter, is an API-breaking change. (If types are expressed in names via mangling or such, it's also an ABI-breaking change.)
  • It also looks like it doesn't compose particularly well. What is the difference between Pure[NoAlloc[Fn(I32, I32): I32]] and NoAlloc[Pure[Fn(I32, I32): I32]]? If we proceed only using something like Rust's ZST's then these types are entirely incompatible, and you'll have a lot of functions that do fairly pointless things like juggle X[Y[Z]] -> X[Z]. I can think of times when that would have actual semantic meaning, but also plenty of times where it doesn't. If you get general enough I bet you could start expressing variable-length lists or unordered sets of properties in the type system, but that brings us to the next point...
  • If you go far enough in this direction to be really cool you also start getting into advanced type systems such as linear types, which I'd like to avoid if possible. They're complicated, they're hard to implement and they're generally unable to be checked efficiently, as far as I can tell. (...and I don't like reading type theory papers.) Rust and Zig both avoid these sorts of systems for IMO pretty good reasons, so I would like to find a pragmatic subset of these capabilities that give you most of the cool stuff that people will actually commonly use.

So I like that idea a lot, and want to keep thinking about that approach and persuing it. But it's kind of raw as-is. One thing I'd like is for the compiler to infer the properties of a function, so if you really don't care whether println() allocates or not you don't have to say anything about it, but if you really care that your function is NoAlloc then the compiler can prevent you from accidentally calling a function that allocates.

Lots of good food for thought there, thank you.

#24 Newtype structs and related silliness 9 days ago

Comment by ~icefox on ~icefox/garnet

"Neatly solves the problem and not intuitive" is a great summary of that part of the problem. A keyword might not be a terrible idea, there's a little bit of Feels Bad to it because it actually is just a function but it might be common enough to be convenient. It might be common enough to be worth it though. I'm not terribly concerned about outputting stub functions, on the assumption that they'll get inlined to nothing. Someone else also might want to call that function from outside your program as well, so having it be public even if it's just a stub might still be useful...

...which brings up an interesting thing also worth considering: permission to access the contained value. I haven't thought of this at all, everything is currently just public. However, Rust lets you specify whether the contained value is public or not, which lets you do some occasionally useful things like make it impossible for anyone else to construct a value. I haven't thought about this at all yet, and my first impulse is "just make everything public like C and Python and if someone wants to mark a struct field private they prefix it with _ or something". Then I think about the kind of C code I've seen this result in and reconsider...