HN user

oconnor663

7,546 karma

[ my public key: https://keybase.io/oconnor663; my proof: https://keybase.io/oconnor663/sigs/6u7ycNDOZvuO8qNrg69nGayUCyATOnCNq6H9tBV0BsM ]

Posts20
Comments1,592
View on HN
jacko.io 4mo ago

Never Snooze a Future

oconnor663
4pts0
bfswa.substack.com 8mo ago

6 years after too much crypto

oconnor663
3pts0
www.youtube.com 1y ago

From Rust to C and Back Again: an introduction to "foreign functions" [video]

oconnor663
2pts0
jacko.io 1y ago

Two Attacks on Naive Tree Hashes

oconnor663
6pts0
jacko.io 1y ago

Smart Pointers Can't Solve Use-After-Free in C++

oconnor663
5pts2
jacko.io 1y ago

How to Pronounce Chinese Names a Little Better

oconnor663
23pts7
jacko.io 1y ago

Async Rust in Three Parts

oconnor663
162pts69
www.youtube.com 1y ago

Rust Can Compete with Python

oconnor663
3pts0
www.youtube.com 2y ago

A First Look at Lifetimes in Rust [video]

oconnor663
1pts0
jacko.io 2y ago

Object Soup Is Made of Indexes (Rust)

oconnor663
14pts2
jacko.io 3y ago

Safety and Soundness in Rust

oconnor663
90pts90
www.youtube.com 3y ago

Unsafe Rust is not C

oconnor663
4pts1
github.com 4y ago

The SHA-256 Project: learn how hash functions work by implementing one yourself

oconnor663
213pts45
github.com 4y ago

Show HN: The BLAKE3 hash function running on a 6502 microprocessor

oconnor663
91pts21
research.nccgroup.com 4y ago

NCC Group’s Cryptopals Guided Tour

oconnor663
77pts11
blog.timescale.com 4y ago

Function pipelines: Building functional programming into PostgreSQL

oconnor663
349pts68
www.youtube.com 4y ago

Why Is Starship Important?

oconnor663
2pts0
www.youtube.com 5y ago

A Firehose of Rust, for busy people who know some C++

oconnor663
5pts1
www.youtube.com 7y ago

Show HN: Bao, a cryptographic tree hash, perhaps the fastest hash in the world

oconnor663
2pts0
github.com 11y ago

Peru – A tool for including other people's code in your projects

oconnor663
25pts8

Several air launch systems have been tried, with limited but non-zero commercial success. The altitude and speed you get from the plane is very small compared to the total work the rocket needs to do, so the benefit in terms of "the rocket can be smaller" is minimal. The main benefit in practice has been launching from ~wherever you like, since regular fixed launch sites usually have strict limits about the direction you have to fly to avoid people. But the economics of reusability are pushing rockets to get bigger, and no air launch system can fly anything nearly as big as a Falcon 9, much less a Starship + Super Heavy. Other scattered problems:

- Hanging under-wing is a totally different set of forces than standing vertically, especially for a big rocket with thin walls. You're more like a bridge than a tower, or rather like a bridge one moment and then a tower the next. You need reinforcement for that, which makes the vehicle heavier.

- Modern reusable rockets do quick "load and go" filling to keep their propellant as cold and dense as possible. You can't do that if you need to fuel on the ground and then hang off an airplane for ~an hour while it climbs.

https://web.archive.org/web/20120503175355/https://www.nasa....

The percent propellant has huge implications on the ease of fabrication and robustness in achieving the engineering design (and cost). If a vehicle is less than 10% propellant, it is typically made from billets of steel. Changes to its structure are readily done without engineering analysis; you simple weld on another hunk of steel to reinforce the frame according to what your intuition might say. I can easily overload my ¾ ton pickup by a factor of two. It might be moving slowly but it is hauling the load.

Once the vehicles become airborne, the engineering becomes more serious. Light weight structures made of aluminum, magnesium, titanium, epoxy-graphite composites are the norm. To alter the structure takes significant engineering; one does not simply weld on another chunk to your airframe if you want to live (or drill a hole through some convenient section). These vehicles cannot operate far from their designed limits; overloading an airplane by a factor of two results in disaster. Even though these vehicles are 30 to 40% propellant (60 to 70% structure and payload), there is room for engineering to comfortably operate thus there is a robust, safe, and cost effective aviation industry.

Rockets at 85% propellant and 15% structure and payload are on the extreme edge of our engineering ability to even fabricate (and to pay for!). They require constant engineering to keep flying. The seemingly smallest modifications require monumental analysis and testing of prototypes in vacuum chambers, shaker tables, and sometimes test launches in desert regions. Typical margins in structural design are 40%. Often, testing and analysis are only taken to 10% above the designed limit. For a Space Shuttle launch, 3 g’s are the designed limit of acceleration. The stack has been certified (meaning tested to the point that we know it will keep working) to 3.3 g’s. This operation has a 10% envelope for error. Imagine driving your car at 60 mph and then drifting to 66 mph, only to have your car self-destruct. This is life riding rockets, compliments of the rocket equation.

I don't know anything about this particular launch, but one reason static fires sometimes load more fuel than you'd think is that the hold-down clamps aren't rated for the total thrust of the vehicle. Launch thrust is usually 1.2-1.6x the launch weight (if it's <1x you will not go to space today), so after subtracting gravity you've got 0.2-0.6x the weight acting upwards on the clamps. But rockets are mostly fuel by weight, so if you static fire it nearly empty, then that gravity term goes to ~zero, and the clamps have to hold the full 1.2-1.6x. You could overbuild them to handle that -- which isn't the end of the world, because they don't need to fly -- but it can be easier to just add extra fuel and detank it afterwards.

Bear in mind that a lot of what's happening to the tiles now is deliberate experiments to see how much weight they can shave off and how many failed tiles they can survive. Given that the vehicle is routinely surviving reentry at this point, it doesn't seem "hard" to make the tiles more robust by paying for it with added weight. The question is whether they'll have enough weight budget to pay for it? But at this point...probably? Not my area ofc.

Scott Manley pointed out that it seemed to flip in the wrong direction, with one of the grid fins passing through the plume and inducing a roll. Will be interesting to hear more about that.

Just Use Go 3 months ago

Some weird nils don't compare equal to other nils. It's surprisingly easy to construct the weird kind by accident, because the conversion is automatic and invisible.

The trap is that get_user_by_name ends up loading shared libraries from the new root filesystem to resolve the username.

That's kind of horrifying. Is there a reliable list somewhere of all the functions that do that? Is that list considered stable?

async/await introduced entirely new categories of bugs that threads don’t have. O’Connor documents a class of async Rust deadlocks he calls “futurelocks”

I didn't coin that term, the Oxide folks did: https://rfd.shared.oxide.computer/rfd/0609. I want to emphasize that I don't think futurelocks represent a "fundamental mistake" or anything like that in Rust's async model. Instead, I believe they can be fixed reliably with a combination of some new lint rules and some replacement helper functions and macros that play nicely with the lints. The one part of async Rust that I think will need somewhat painful changes is Stream/AsyncIterator (https://github.com/rust-lang/rust/issues/79024#issuecomment-...), but those aren't yet stable, so hopefully some transition pain is tolerable there.

The pattern scales poorly beyond small examples. In a real application with dozens of async calls, determining which operations are independent and can be parallelized requires the programmer to manually analyze dependencies and restructure the code accordingly.

I think Rust is in an interesting position here. On the one hand, running things concurrently absolutely does take deliberate effort on the programmer's part. (As it does with threads or goroutines.) But on the other hand, we have the borrow checker and its strict aliasing rules watching our back when we do choose to put in that effort. Writing any sort of Rust program comes with cognitive overhead to keep the aliasing and mutation details straight. But since we pay that overhead either way (for better or worse), the additional complexity of making things parallel or concurrent is actually a lot less.

At the function level, adding a single i/o call to a previously synchronous function changes its signature, its return type, and its calling convention. Every caller must be updated, and their callers must be updated.

This is part of the original function coloring story in JS ("you can only call a red function from within another red function") that I think gets over-applied to other languages. You absolutely can call an async function from a regular function in Rust, by spinning up a runtime and using `block_on` or similar. You can also call a regular function from an async function by using `spawn_blocking` or similar. It's not wonderful style to cross back and forth across that boundary all the time, and it's not free either. (Tokio can also get mad at you if you nest runtimes within one another on the same thread.) But in general you don't need to refactor your whole codebase the first time you run into a mismatch here.

I question the choice of the phrase "to the Moon". I get it, it's technically true, but ~100% of normal people hear that and assume it means boots on the ground. Every single time it gets mentioned, it's immediately followed by a clarification that disappoints the audience. This isn't the sort of marketing choice that a self-confident program makes.

I wouldn't read too much into pre-1.0 versions. Folks take SemVer pretty seriously, and that makes some folks reluctant to declare v1.0 even when a crate has been in use and "mostly stable" for years. There can also be compatibility issues with a 1.0 bump if a crate's types are common in public APIs, e.g. the `libc` crate. I'm a big fan of the curated list of crates at blessed.rs, or honestly just looking at download numbers. (Obviously not a perfect system.)

I had a similar first reaction. It seemed like the AI used some particular buzzwords and forced the initial response to be deferential:

- "kindly ask you to reconsider your position"

- "While this is fundamentally the right approach..."

On the other hand, Scott's response did eventually get firmer:

- "Publishing a public blog post accusing a maintainer of prejudice is a wholly inappropriate response to having a PR closed. We expect all contributors to abide by our Code of Conduct and exhibit respectful and professional standards of behavior. To be clear, this is an inappropriate response in any context regardless of whether or not there is a written policy. Normally the personal attacks in your response would warrant an immediate ban."

Sounds about right to me.

Infant mortality was also 1.5x higher in the US than it is today. (Depending on who we mean by "we", this difference can be much larger.) Cystic fibrosis was a death sentence. "Late 90's" barely includes the development of effective AIDS treatment, though certainly not the rollout. (Maybe part of what you were getting at above, besides gay marriage.) Etc etc.

I made my own Git 6 months ago

It's mixed. You get something in the neighborhood of a 3-4x speedup with SHA-NI, but the algorithm is fundamentally serial. Fully parallel algorithms like BLAKE3 and K12, which can use wide vector extensions like AVX-512, can be substantially faster (10x+) even on one core. And multithreading compounds with that, if you have enough input to keep a lot of cores occupied. On the other hand, if you're limited to one thread and older/smaller vector extensions (SSE, NEON), hardware-accelerated SHA-256 can win. It can also win in the short input regime where parallelism isn't possible (< 4 KiB for BLAKE3).

I made my own Git 6 months ago

As far as I know, most CDC schemes requires a single-threaded pass over the whole file to find the chunk boundaries? (You can try to "jump to the middle", but usually there's an upper bound on chunk length, so you might need to backtrack depending on what you learn later about the last chunk you skipped?) The more cores you have, the more of a bottleneck that becomes.

It's important to think about the exact procedure you want to use for verifying something. Running with your thought experiment, let's say we publish "the root hash of the whole world" (not too far off from what Keybase did) each day in the Times. Now I open my phone to read some messages from Billy Bob, and my phone needs to get that hash somehow. This is just a thought experiment, so let's say for the sake of argument that it tells me to walk down to the convenience store, buy a copy of the day's paper, and scan a QR code on page 12. The problem with that arrangement (even in thought experiment land, where I'm happy to perform these steps every day) is that all the evil server needs to do to trick me is to put a doctored copy of the Times in that one newspaper stand. That's not the level of security we were hoping for. To get real security here, I'd need to do some sort of random sampling of newspaper stands distributed across the country, to build confidence that whatever QR code I'm seeing is the same one that everyone else is seeing. And the kicker is, everyone has to do this. We can't just pay one guy to sample the papers every day and tell us what the QR code was, because now our security depends on trusting that one guy, and the whole point of peer-to-peer security is avoiding that kind of centralized trust.

I think this is actually a great way to talk about the difficulty of the problem that Bitcoin solved, and why so many nerds were so interested in the whitepaper, long before all the real money got involved.

Another important parameter with BLAKE3 is "Do you want to use threads?" There's no one-size-fits-all answer to that question, but it's also a parameter that ~no other hash function needs.

Fwiw, I think the RustCrypto effort also tends to suffer a bit from over-abstraction. Once every year or two I find myself wanting to get a digest from something random, let's say SHAKE128. So I pull up the docs: https://docs.rs/sha3/latest/sha3/type.Shake128.html. How do you instantiate one of those? I genuinely have no idea. When I point Claude at it, it tells me to use `default` instead of `new` and also to import three different traits. It feels like these APIs were designed only for fitting into high-level frameworks that are generic over hash functions, and not really for a person to use.

There are a lot of old assumptions like "hash functions are padding + repeated applications of block compression" that don't work as well as they used to. XOFs are more common now, like you said. There's also a big API difference between an XOF where you set the length up front (like BLAKE2b/s), and one where you can extract as many bytes as you want (like BLAKE3, or one mode of BLAKE2X).

Maybe the real lesson we should be thinking about is that "algorithm agility" isn't as desirable as it once was. It used to be that a hash function was only good for a decade or two (MD5 was cracked in ~13 years, but it was arguably looking bad after just 6), so protocols needed to be able to add support for new ones with minimal friction. But aside from the PQC question (which is unlikely to fit in a generic framework with classic crypto anyway?), it seems like 21st century primitives have been much more robust. Protocols like WireGuard have done well by making reasonable choices and hardcoding them.

Despite benefits, I don't actually think the memory safety really plays a role in the usage rate of parallelism.

I can see what you mean with explicit things like thread::spawn, but I think Tokio is a major exception. Multithreaded by default seems like it would be an insane choice without all the safety machinery. But we have the machinery, so instead most of the async ecosystem is automatically multithreaded, and it's mostly fine. (The biggest problems seem to be the Send bounds, i.e. the machinery again.) Cargo test being multithreaded by default is another big one.

They aren't useful outside of "financial engineering."

Without disagreeing with your overall point in 99% of cases, we did actually have a good use for pinning things in the Bitcoin blockchain when I worked at Keybase. If you're trying to do peer-to-peer security, and you want to prove not only that the evil server hasn't forged anything (which you do with signatures) but also that it hasn't deleted anything legitimate, "throw a hash in the blockchain" really is the Right Way to solve that problem.

C Is Best (2025) 7 months ago

With C you may, if you wish, develop a big sensibility to race conditions, and stay alert. In general it is possible that C programmers have their "bugs antenna" a bit more developed than other folks.

I think there are effects in both directions here. In C you get burned, and the pain is memorable. In Rust you get forced into safe patterns immediately. I could believe that someone who has done only Rust might be missing that "healthy paranoia". But for teaching in general, it's hard to beat frequent and immediate feedback. Anecdotally it's common for experienced C programmers to learn about some of the rules only late in their careers, maybe because they didn't happen to get burned by a particular rule earlier.

Rust may create a false sense of security, and in the unsafe sections the programmer sometimes, when reviewing the code, is falsely convinced by the mandatory SAFETY comment.

This is an interesting contrast to the previous case. If you write a lot of unsafe Rust, you will eventually get burned. If you're lucky, it'll be a Miri failure. I think this makes folks who work with unsafe Rust extremely paranoid. It's also easier to sustain an that level of paranoia with Rust, because you hopefully only have to consider small bits of unsafe code in isolation, and not thousands of lines of application logic manipulating raw pointers or whatever.

Yes I'm especially interested in what OP thinks about the overlap (or not?) between the ideas in this post and the ideas in this part of boats' post:

One could imagine an alternative design in which instead of places being unpinned by default and opting into pinning, places are pinned (or perhaps “immovable”) by default, and have to opt into supporting the ability to move out of them. This would make it so that by default places have the least power (can only access via shared reference) and they gain a monotonically increasing set of powers (can assign to them, can move out of them).

In addition to places having to opt into moving, there would be three reference types instead of two: immutable, mutable, and movable references.

You have to consider the class of problems as a whole, from the perspective of management:

- The cheap solution would be equally good, and it's just a blame shifting game.

- The cheap solution is worse, and paying more for the name brand gets you more reliability.

There are many situations that fall into the second category, and anyone running a business probably has personal memories of making the second mistake. The problem is, if you're not up to speed on the nitty gritty technical details of a tradeoff, you can't tell the difference between the first category and the second. So you accept that sometimes you will over-spend for "no reason" as a cost of doing business. (But the reason is that information and trust don't come for free.)

We recommend using Nix to easily ensure you are running the right versions of the tools and libraries.

Ooof I remember when everything used to be like this. Cargo has really spoiled me.

What is the dreaded UB? I think the best way to understand it is to remember that, for any running program, there are FATES WORSE THAN DEATH.

I love this. I'm gonna steal this :)

I’m not the first person to pick on this particular Github comment, but it perfectly illustrates the conceptual density of Rust:

The overall point here is fair, but I think it's important to clarify that (iiuc) this comment is talking about a "soundness hole". Soundness holes are cases where there's a bug in the compiler or in a library that allows someone to commit UB without writing `unsafe`. Given the goals of Rust, it doesn't matter how ungodly complicated and contrived the example is. If it produces UB without `unsafe`, then it's a bug that needs to be fixed. In practice, that means a lot of issue threads about soundness involve mind-numbing code samples that mash different features together in unintuitive ways.

But that's a good thing! No one's saying you'll ever need to look at code like this in the wild. They're saying that no matter how hard you (or your coworkers or your dependencies) try, Rust should never fail to protect memory safety in safe code.

Yeah it's worth emphasizing, if I spawn two threads, and both of them print a message when they finish (and don't interact with each other in any other way), that's technically a race condition. The output of my program depends on the order on which these threads complete. The question is whether it's a race that I care about.