HNHacker News
TopNewBestAskShowJobs

OskarS

8,386 karma · joined September 4, 2016

submissionscomments
OskarS··on Partial Operator Precedence
I don't think your experience is particularly universal, it vastly differs depending on what kind of programming you do. If you're doing games stuff or graphics or signal processing or any other number of subfields of programming that require a lot of math, operators are a god-send for readability.

This Zig change is the best of both worlds: avoid the ambiguity, but keep the readability.

OskarS··on The lost malloc()
> Many allocators must keep 2 sizes for every allocated object

And wouldn't it be nice to just make them keep one number! That's a size_t and a couple of cycles saved for every malloc()!

I will say, this is not some far-out niche idea of mine, it's basically universal for all "new" implementations of memory allocators to require exactly this information for deallocation. There's a reason Zig allocators take a slice and not a pointer [0], why std::pmr::polymorphic_allocator::deallocate takes a size [1] and why Rust allocators require it as well (in the layout param) [2]. It's just a better API design.

EDIT: for C++, the better reference is the Allocator named requirement, which the polymorphic allocator is just one example of. But you can see it there as well in the deallocate() method [3]

[0]: https://ziglang.org/documentation/master/std/#std.mem.Alloca...

[1]: https://en.cppreference.com/cpp/memory/polymorphic_allocator...

[2]: https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html#t...

[3]: https://en.cppreference.com/cpp/named_req/Allocator

OskarS··on The lost malloc()
I don't know what the exact impact would be, but it's certainly not zero, and multiplied by every malloc, it adds up. My point is mostly that the API for malloc()/free() is designed in such a way to both limit the kind of implementations you can make for it, and make most implementations a tiny bit slower than they need to be.

BTW, when malloc() takes 10,000 cycles, you've either hit some weird edge case in an allocator or (much more likely) malloc() has run out pages it can use to hand over to the application and has to ask the kernel for more memory. I measured malloc() for a recent change in one of our products, and in my test the median time was less than 100ns. malloc() is usually very fast. But with a better API, it could be faster. Multiplied by every memory allocation on every computer in the world (more or less) for the last half-century, it's becomes a very big number.

OskarS··on The lost malloc()
If you think you can convince the entire computer industry to stop using malloc(), I wish you the best of luck. In actuality, your computer likely calls malloc() somewhere thousands of times per second in low use, probably millions of times during intensive use. Wouldn't it be nice if all those calls were just a bit less resource intensive?
OskarS··on Nixpkgs for the frontend impaired
I don't think I would alone in saying that your JavaScript example is much easier to read than the Nix variant. It reads very cleanly, it's obvious what's a function and what are arguments to the function, and you can easily read it left to right. You have to track the parentheses a long way to see that builtins.filter takes two arguments, and it's very non-obvious what the first argument even is. It's much harder to read than `Object.keys(licenses).filter(...)`, IMHO.

Maybe there's no inherent cosmic superiority to the C/C++/Java style syntax of JavaScript/TypeScript compared to the Haskell-style syntax of Nix, but in reality, most programmers can read it easily. Which is not something that can be said for Nix.

On the `nix.thunk` thing, that was for the code that was automatically transpiled from Nix while maintaining the exact semantics, so yeah, it looks ugly. But the article has examples of what it would look like hand-written, and it looks just fine.

OskarS··on The lost malloc()
I don't agree. Zig allocators require the size of the allocation (technically they take a slice, but a slice is ptr + size), as does the C++ polymorphic allocator, as does many allocator interfaces in many libraries (see for instance the allocator interface in Lua, my personal favorite C interface for memory allocation [0]). Clearly having it as a correctness requirement works just fine.

[0]: https://www.lua.org/manual/5.4/manual.html#lua_Alloc

OskarS··on The lost malloc()
> Not only that, mfree() receives the size of the object it is deallocating, as it should.

I've always thought that one of the biggest tiny mistakes in the history of this industry. It means, more or less, that you're forcing the memory allocator to keep track of these sizes when it shouldn't have to. There's virtually no sensible case where the person calling free() doesn't know the size of the allocation, so now we're keeping that book-keeping twice, in "user-space" and inside the memory allocator. Like, think about the cases when you'd call malloc:

* For allocating a single object of type T, the size is just `sizeof(T)`, so you can just pass that to free()

* For allocating an array of objects, the size is `count * sizeof(T)`, and I can't think of a single time where you DON'T know what `count` is. It's either statically sized (in which case it's just a constant), or if you have a dynamically growing array, you need to keep track of the reserved space to know when you reallocate. If nothing else, you need it for bounds-checking.

* For chunks of raw memory, you also presumably know when deallocating what the size of the raw memory you allocated is, for the same reasons as the previous point. Like, just having a raw char* pointer lying around with no size information is just terrible C practice.

If you truly don't know the size of your allocation (very hard to imagine when this would ever happen), you should be forced on the user-side to keep track of it somehow, not delegate that to the allocator.

It's a tiny amount of overhead, but it's for EVERY SINGLE MEMORY ALLOCATION since like 1975. Imagine how many times malloc() and free() has called since then! How much electricity, CPU usage, and memory has been wasted! It boggles the mind!

OskarS··on Nixpkgs for the frontend impaired
Yes, you're absolutely right, but you're also illustrating the author's broader point a little bit. For someone not very deeply familiar with Nix (the language), that is a very difficult one-liner to parse. You have to be pretty hard-core nix-pilled to understand what that one is doing just reading it straight-on (my NAS is using NixOS, so I've written some amount of Nix). On the other hand, the TypeScript code the author provides is reasonably straight-forward, you don't have to be particularly familiar with either TypeScript or Nix to understand what it is doing.

You're correct in that the article is wrong in saying you can't use a nix repl this way, but I took the broader point of it to be "Nix-the-language gets in the way of the Nix-the-model". Which is something that I personally agree with 100% and I think the article illustrates beautifully.

OskarS··on How Google determines the names for bodies of water in Google Earth (2008)
It's a very lawyerly distinction you're making here that's willfully blind to what's really going on. The common-sense reading "primary, common, local name" is that it should be called the Gulf of Mexico, as that is what it is called in the USA.

If the nutso president of Turkmenistan decided one day that the Caspian Sea's real name was the Turkmen Sea, there's absolutely no way that Google Maps would rename it on their maps. The reason they renamed it in this case is not because "well, the government said so, so we have no choice!". It's (obviously!) done to not piss of Trump, and pissing off Trump would be bad for their business. They can hide behind lawyerly arguing all they want, but that is the real reason.

OskarS··on Composite objects and C++26 the std:polymorphic type
I agree. The comment is correct in that it sort-of violates the "purity" of std::indirect that it has this pathological in moved-from state, but it's only an issue when directly using moved-from objects, which is something you essentially never do. Exactly what you say, "in practice" this never happens. Throwing the move constructor out because of it would be throwing out the baby with the bathwater.

std::variant has a similar issue with valueless_after_exception, which given the semantics of the language, it has to have. But similarly, this essentially never happens in practice, and I've never encountered a bug with a valueless std::variant.

OskarS··on Composite objects and C++26 the std:polymorphic type
> Which the version I showed has none of.

No, it has something much worse, it has a memory leak. The nested types inside the union allocate memory, and they don't get destructed when Event gets destructed. Don't do this in modern C++, it's a terrible terrible idea, these kinds of constructs are very hard to get correct manually. Just use std::variant.

OskarS··on Composite objects and C++26 the std:polymorphic type
Yes, though in modern C++, don't do this, use std::variant instead. It has better type-safety (nothing stops you from writing to one member of the union but reading from another, causing UB) and handles lifetimes much better. Your version leaks, because struct Event wont destruct any of the nested types inside the union, and in the example, they have heap-allocated data (std::strings and the like). In order to that properly with a tagged union, you need to define the destructor of Event yourself (which is not done in your example) and call the correct destructor manually of the active union member.

Tagged unions (especially with resource-managing types) is hard to implement correctly in C++ (and not trivial in C either). Don't do it, use std::variant.

OskarS··on Writing Efficient C++ Code (2013)
> One in a trillion times.

This is wildly overstating it. Yeah, I agree, they're much less often the right choice compared to a good-ol' growable array, but they have lots of uses in high-performance code and concurrent code, and they're building blocks in lots of other data structures. Like, in a bucket hash-table, the buckets are linked lists, in a LRU cache you interleave a hash table and linked list, std::hive is a linked list of chunks of elements, etc. Anything that has ever had to deal with memory pooling/allocation uses free-lists which are linked lists. And on and on and on.

OskarS··on A Necessary History of the Oddest Letter: W
I remember vividly being so annoyed when they added W to the Swedish alphabet in 2006. The alphabet I learned to recite in first grade is suddenly wrong?! I still don't say it correctly unless I concentrate, if I have to recite the alphabet, I always skip over W.

The argument at the time was that no "native" Swedish words are spelled with a W, so it shouldn't be part of our alphabet. However, but 2006, enough loan-words had been imported to Swedish with W's (like "webben", "the web") and it was in enough brand names (like "Volkswagen") that it should be included.

But I'm still mad about it! This is my tiny ridiculous hill to die on, W should not be part of the Swedish alphabet!

OskarS··on High-performance garbage collection for C++
It's interesting reading this, wondering how much C++26 static reflection could improve the ergonomics of a system like this. Like, if you tag the class with a specific attribute, can you have have it generate the Trace() function automatically? Can you have it automatically wrap the other GC classes using the Member<> template? I think implementing the Trace() function might be doable (you'd do it in the GarbageCollected<> base class that uses CRTP, right?), but maybe not wrapping the types, I'm not sure if reflection allows you to modify types of fields in that way.
OskarS··on Move in C++ without a std:move
Fair enough, I don't know the C++ ABI to this extent.
OskarS··on Using jq to format JSON on the clipboard
Lovely little alias, but I much prefer fracjson[0] pretty printing to jq, so I might go with that instead!

[0]: https://github.com/j-brooke/FracturedJson

OskarS··on Move in C++ without a std:move
Yes, it works exactly like this, this is a demo on godbolt [0]. rdi stores the pointer in both cases, makeS1() uses RVO, makeS2() takes it explicitly and constructs with placement new.

I will say before testing this i didn't realize the RVO calling convention was to return the pointer you pass in, but apparently so. If makeS2() returned void, it's just a tail call to the constructor, but makeS1() has to spill rbx and use it to save the pointer.

[0]: https://godbolt.org/z/ovd1n99P8

OskarS··on DoltLite: A SQLite fork with Git-style version control, built with 2k agent PRs
That's what I'm saying, if you have a CoW filesystem, backing up with atomic snapshots is almost certainly fine, SQLite's advice against copying probably doesn't apply to that. But just typing `cp` in a terminal is probably not a good idea if a transaction is active (even if you're on a CoW filesystem, `cp` in experience usually isn't atomic without some flags).
OskarS··on DoltLite: A SQLite fork with Git-style version control, built with 2k agent PRs
> The simple way to fork a sqlite db is just to copy it

SQLite recommends against doing that [1] while a transaction is active, because the copy might have some pre/post data in it which could potentially cause corruption. Presumably this isn't an issue with copy-on-write filesystems assuming they copy atomically, but I probably still wouldn't do it, instead use the backup API or VACUUM INTO (if nothing else, because VACUUM INTO will also obviously VACUUM the copy).

If you're not attached/writing to it, then of course it's fine.

[1]: https://www.sqlite.org/howtocorrupt.html#_backup_or_restore_...

OskarS··on uv: Deduplicate all files in the wheel cache
I think it's reasonable for a DB like SQLite to delegate that to the filesystem. There is an overhead for doing it on the DB level, and since SQLite is just a file on the filesystem which, presumably, is serving many other files as well, why would you trust anything else on the filesystem if you don't trust SQLite? Like, your PHP script (or nginx server executable, or whatever) that is calling SQLite, that's not going to be check-summed either. Either you trust your filesystem or you don't, and if you don't, checksum and error correct on the filesystem level.

Though fair enough, it could offer it as an opt-in thing.

OskarS··on Nebula Sans
I think that's totally fair, it's just different people come to youtube for different things. Nebula is just a different value proposition.
OskarS··on Nebula Sans
Obviously YMMV, but I haven't had any issues like that, the quality of the videos is the same I get in the YouTube app. They take a few seconds longer to start though.
OskarS··on Nebula Sans
Yes, of course you can, but this sense of always having to curate your watch history and hide from the algorithm is just not there for Nebula. I guess it's different from person to person, but it's remarkable how freeing it feels to me to use a service where that isn't a concern. I'm watching what I want to watch, not what the algorithm is manipulating me to click on.

Maybe it will enshittify like most everything else, but it hasn't so far, and for now I'm a very happy customer.

OskarS··on Nebula Sans
It's a little beside the point of the post, but I just want to fly a flag for Nebula: I subscribed solely to get early versions of Jet Lag: The Game, but I'm finding myself watching it more and more! It has many of the channels I would go to YouTube for, but I've also discovered several really good ones that I had never heard of. And it has a similar feel to Kagi, in the sense of "freeing" yourself from the algorithm. It happens so often on YouTube that I see a video recommended but think to myself "if I click this, am I going to get recommendations for terrible stuff for the next month? What does this tell Google about me?". Same thing with all the big streaming services.

With Nebula, no such worry. The homepage and app isn't creepily algorithmically designed to optimize my engagement with the platform, it's just a bunch of high-quality videos and channels that I can watch with no fuss. It's remarkable how nice that feels in 2026. Really highly recommended.

OskarS··on GIMP Development Update
The obvious alternative is SQLite, and it has many advantages. If you want to do a "zipped list of files", SQLite does that just fine (that's what SQLar is), but it can do so much richer data. Even if you don't want that, it still offers resiliency that "zipped XML" can't match: if your software or computer crashes in the middle of saving your file, it'll almost certainly corrupt it. With SQLite, not an issue: all transactions are atomic, they either happen entirely or not at all.

I don't know what "mature tooling" you're talking about for zipped XML, but I guarantee you it's not going to be better (or more mature) than SQLite and its ecosystem.

OskarS··on Rust project goals: Immobile types and guaranteed destructors
mem::forget isn’t the only way you can safely leak a value, you can do it with reference cycles too, right? And there is no way for the compiler to detect that?

Isn’t that why mem::forget is safe, because you can always implement it yourself safely? How do you get around that?

OskarS··on Stinkpot: SQLite-backed shell history
I think you're underestimating the speed of modern computers and hard-drives, fzf can search through 100k entries easily without any delay. Try it with `cat /usr/share/dict/words | fzf`, it's extremely responsive (my dictionary is about 235k lines). If you want hard numbers, try piping your shell history through `time grep` and see how long finding an entry takes.

Even if you're on spinning rust, your shell history is probably in cache. You don't really need FST index for this usecase, IMHO.

OskarS··on Prefer strict tables in SQLite
Does this trick work with foreign keys? Like, if you have an ON CASCADE DELETE, does it delete a bunch of rows in other tables when converting the table to strict?
OskarS··on Wikipedia cofounder Larry Sanger blocked from editing Wikipedia
One useful thing to do sometimes when looking at whether or not a user is interested in participating in Wikipedia or just participating in arguing about things is to look at their contributions to actual wikipedia articles, the thing Wikipedia is all about.

This is the list of articles Sanger contributed to in 2026: [1]

Compare that too all his contributions: [2]

Does it seem like this person is participating in Wikipedia in a genuine way? Or is he there to start political arguments on various internal pages?

[1]: https://en.wikipedia.org/w/index.php?title=Special%3AContrib...

[2]: https://en.wikipedia.org/w/index.php?title=Special%3AContrib...

Page 1 of 34Next →