HNHacker News
TopNewBestAskShowJobs

oconnor663

7,581 karma · joined September 4, 2012

[ my public key: https://keybase.io/oconnor663; my proof: https://keybase.io/oconnor663/sigs/6u7ycNDOZvuO8qNrg69nGayUCyATOnCNq6H9tBV0BsM ]
submissionscomments
oconnor663··on Futurelock: A subtle risk in async Rust
There's nothing `select!` could do here to force `future1` to drop, because it doesn't receive ownership of `future1`. If we wanted to force this, we'd have to forbid `select!` from polling futures by reference, but that's a pretty fundamental capability that we often rely on to `select!` in a loop for example. The blanket `impl<F> Future for &mut F where F: Future ...` isn't a Tokio thing either; that's in the standard library.
oconnor663··on Futurelock: A subtle risk in async Rust
The author clearly understands these details. I think it's just a question of wording: did we "drop a reference (which has no effect)" or did we "not drop anything (because references don't implement Drop)"?
oconnor663··on Futurelock: A subtle risk in async Rust
> FAQ: doesn’t future1 get cancelled?

I guess cancellation is really two different things, which usually happen at the ~same time, but not in this case: 1) the future stops getting polled, and 2) the future gets dropped. In this example the drop is delayed, and because the future is holding a guard,* the delay has side effects. So the future "has been cancelled" in the sense that it will never again make forward progress, but it "hasn't been cancelled yet" in the sense that it's still holding resources. I wonder if it's practical to say "make sure those two things always happen together"?

* Technically a Tokio-internal `Acquire` future that owns a queue position to get a guard, but it sounds like the exact same bug could manifest after it got the guard too, so let's call it a guard.

oconnor663··on Uv is the best thing to happen to the Python ecosystem in a decade
PyO3 and Maturin are excellent. I've been maintaining a Python-module-written-in-Rust for several years now, and it's been quite smooth.
oconnor663··on RFC 9861: KangarooTwelve and TurboSHAKE
If you want to try an optimized AVX-512 implementation of KangarooTwelve on the command line, you can `cargo install k12sum`. On my machine it's neck-and-neck with `b3sum --no-mmap` (which does not use threads).

Edit: Oh it looks like another option, `KeccakSum`, was released a couple months ago? https://github.com/XKCP/K12/commit/5271b58c990c1ac33c1097b4e...

oconnor663··on Exploring PostgreSQL 18's new UUIDv7 support
It's relatively common for it to be a privacy concern. Imagine if I'm making an online payment or something, and one of the IDs involved tells you exactly when I created my bank account. That's a decent proxy for my age.
oconnor663··on Why Is SQLite Coded In C
That could be well defined for POD types like arrays of bytes (I think in that case they call it "freezing"), but you'd still have undefined behavior if the type in question contained pointers. Also at least in Rust it's UB to create illegal values of certain types, like a bool that's anything other than 0 or 1.

Which is all kind of to say, all of Rust's different safety rules end up being surprisingly densely interconnected. It's really hard to guarantee one of them (say "no use-after-free") without ultimately requiring all of them ("no uninitialized variables", "no mutable aliasing", etc).

oconnor663··on Why Is SQLite Coded In C
Yes. You have to write `unsafe { ... }` around it, so there's an ergonomic penalty plus a more nebulous "sense that you're doing something dangerous that might get some skeptical looks in code review" penalty, but the resulting assembly will be the same as indexing in C.
oconnor663··on Why Is SQLite Coded In C
> and the rate of change is low (or zero)

This jives with a point that the Google Security Blog made last year: "The [memory safety] problem is overwhelmingly with new code...Code matures and gets safer with time."

https://security.googleblog.com/2024/09/eliminating-memory-s...

oconnor663··on Why Is SQLite Coded In C
> questioning the correctness of this auto-generated code

I wouldn't put it that way. Usually when we say the compiler is "incorrect", we mean that it's generating code that breaks the observable behavior of some program. In that sense, adding extra checks that can't actually fail isn't a correctness issue; it's just an efficiency issue. I'd usually say the compiler is being "conservative" or "defensive". However, the "100% branch testing" strategy that we're talking about makes this more complicated, because this branch-that's-never-taken actually is observable, not to the program itself but to its test suite.

oconnor663··on I/O Multiplexing (select vs. poll vs. epoll/kqueue)
select() is at least kind of deprecated, in that its own man page says not to use it in new code.
oconnor663··on A webshell and a normal file that have the same MD5
This is a common misconception, based on the difference between password hashing and other general uses for a cryptographic hash. Password hashing is special, because we want to protect people who pick terrible passwords, so we need guess-and-check to be expensive. But for most other use cases, like say HMAC or signing, the number of possible inputs is so astronomically large that guess-and-check would be impossible even if each guess was e.g. just a single add instruction. This distinction is why we say never to use a general purpose hash with passwords.
oconnor663··on A webshell and a normal file that have the same MD5
It's "easy" to do it right but also very common to do it wrong: https://jacko.io/tree_hashing.html
oconnor663··on Zig feels more practical than Rust for real-world CLI tools
Indexes: https://jacko.io/object_soup.html
oconnor663··on Zig feels more practical than Rust for real-world CLI tools
I think "writing Rust in C++" so to speak means at least two distinctly different things, which are both important. The first thing is that, as an individual programmer, you're being disciplined with memory and thinking about who owns what. The second thing is that as a group of programmers (over time), you all agree about who owns what. There are a lot of ways to learn the first thing, but I'm not sure there are a lot of ways to accomplish the second thing in a large system.
oconnor663··on A clickable visual guide to the Rust type system
What is your goal for this comment?
oconnor663··on I'm too dumb for Zig's new IO interface
I know you've thought carefully about these issues, but still it can't be that simple, can it? Closing a file or a socket is a fallible operation too.
oconnor663··on Show HN: Implement BLAKE3 with Awk

    function xor(x, y) {
        return (x + y - 2 \* and(x, y));
    }
TIL!
oconnor663··on Io_uring, kTLS and Rust for zero syscall HTTPS server
Note that C++ coroutines use heap allocation to avoid the problems that Pin is solving, which is a pretty big carve-out from the "zero overhead principle" that C++ usually aims for. The long development time of async traits has also been related to Rust not heap allocating futures. Whether that performance+portability-vs-complexity tradeoff is worth it for any given project is, of course, a different question.
oconnor663··on Io_uring, kTLS and Rust for zero syscall HTTPS server
> don't use `let mut buf = [0u8; 16]; socket.read_all(&mut buf).await?;`. If you can't see why such arguments are bonkers, we don't have anything left to talk about.

It doesn't seem bonkers to me. I know you already know these details, but spelling it out: If I'm using select/poll/epoll in C to do non-blocking reads of a socket, then yes I can use any old stack buffer to receive the bytes, because those are readiness APIs that only write through my pointer "now or never". But if I'm using IOCP/io_uring, I have to be careful not to use a stack buffer that doesn't outlive the whole IO loop, because those are completion APIs that write through my pointer "later". This isn't just a question of the borrow checker being smart enough to analyze our code; it's a genuine difference in what correct code needs to do in these two different settings. So if async Rust forces us to use heap allocated (or long-lived in some other way) buffers to do IOCP/io_uring reads, is that a failure of the async model, or is that just the nature of systems programming?

oconnor663··on GPT-5
The fast thing: 100/300 per day -> 640 per day (ish)

The expensive thing: 100 per week -> 200 per week

This is...the opposite of a nerf? The numbers goed up? (We can quibble about the daily vs hourly difference, but certainly for me the weekly cap was the only thing that mattered.)

oconnor663··on A deep dive into Rust and C memory interoperability
Here's one of my recorded talks going through an example of using a `#[repr(C)]` struct (in this case one that's auto-generated by Bindgen): https://youtu.be/LLAUzghhNHg?t=2168
oconnor663··on The borrowchecker is what I like the least about Rust
> It's perfectly clear to a human that this code is fine and doesn't have any actual ownership issues.

I think there's another part of the story in a few of these examples that the author might be missing. Sometimes Rust doesn't want to infer/allow things because of the Halting Problem or whatever, but other times it's because that inference would amount to a compatibility hazard that the user might not've realized they were creating. "Types implement `Copy` automatically" is a good example of one of these. It would be an easy rule to implement (and it is in fact how `Send` and `Sync` and some other things work), but it would also mean that adding a non-Copy private field to a struct that didn't previously have one could break callers. I wouldn't call it a slam-dunk case for the rules being what they are -- and you could argue that it prioritizes the needs of library code over the needs of application code -- but anyway it's some context.

oconnor663··on The borrowchecker is what I like the least about Rust
C++ teachers like Herb Sutter like to jump in here and make a correction: A moved-from object is in a valid state, you just might not know which state. You can still call methods on it that are valid in any state. ("What is your length?") But you shouldn't fall methods that are only valid in some states. ("Give me your first element.")
oconnor663··on Tree Borrows
Here's another data point: https://lobste.rs/s/yubalv/pointers_are_complicated_ii_we_ne...

> I spoke to Apple folks when their compiler team switched the default to strict aliasing. They reported that it made key workloads 5-10% faster and the fixes were much easier to do and upstream than I would have expected. My view of -fstrict-aliasing at the time was that it was a flag that let you generate incorrect code that ran slightly faster. They had actual data that convinced me otherwise.

oconnor663··on Tree Borrows
To elaborate on that some more, safe Rust can guarantee that mutable aliasing never happens, without solving the halting program, because it forbids some programs that could've been considered legal. Here's an example of a function that's allowed:

    fn foo() {
        let mut x = 42;
        let mut mutable_references = Vec::new();
        let test: bool = rand::random();
        if test {
            mutable_references.push(&mut x);
        } else {
            mutable_references.push(&mut x);
        }
    }
Because only one if/else branch is ever allowed to execute, the compiler can see "lexically" that only one mutable reference to `x` is created, and `foo` compiles. But this other function that's "obviously" equivalent doesn't compile:

    fn bar() {
        let mut x = 42;
        let mut mutable_references = Vec::new();
        let test: bool = rand::random();
        if test {
            mutable_references.push(&mut x);
        }
        if !test {
            mutable_references.push(&mut x); // error: cannot borrow `x` as mutable more than once at a time
        }
    }
The Rust compiler doesn't do the analysis necessary to see that only one of those branches can execute, so it conservatively assumes that both of them can, and it refuses to compile `bar`. To do things like `bar`, you have to either refactor them to look more like `foo`, or else you have to use `unsafe` code.
oconnor663··on Tree Borrows
You're already getting a lot of replies, and I don't want to pile on, but I think the clearest way to see the intent there is at the start of the following paragraph:

> Given that aliasing optimizations are something that the Rust compiler developers clearly want to support, we need some way of “ruling out” counterexamples like the one above from consideration.

oconnor663··on Fun with uv and PEP 723
> it's defined in both .python-version and pyproject.toml

The `requires-version` field in `pyproject.toml` defines a range of compatible versions, while `.python-version` defines the specific version you want to use for development. If you create a new project with uv init, they'll look similar (>=3.13 and 3.13 today), but over time `requires-version` usually lags behind `.python-version` and defines the minimum supported Python version for the project. `requires-version` also winds up in your package metadata and can affect your callers' dependency resolution, for example if your published v1 supports Python 3.[old] but your v2 does not.

oconnor663··on Writing toy software is a joy
> Perhaps you’re a user of LLMs. I get it, they’re neat tools. They’re useful for certain kinds of learning. But I might suggest resisting the temptation to use them for projects like this. Knowledge is not supposed to be fed to you on a plate.

I get where this is coming from, and I even agree with it today, but I also want to tag it as "don't cache this opinion too hard". It's interesting to notice when and how our advice for getting help from AI is different from our advice for getting help from other humans. It would be kind of odd, wouldn't it, to put a paragraph at the bottom of a blog post that said "by the way if you have friends who are expert programmers, I don't recommend asking them for help." I think there are two clear reasons that feels odd: 1) expert friends can actually answer your questions and get you personally unstuck, which is huge, and 2) expert friends usually understand why you're doing what you're doing and that they're supposed to help do it yourself instead of just doing it for you.

One thing I bet few people have tried (because I haven't tried it myself) is actually asking an LLM to guide you like an expert friend would, instead of just spitting out code to solve your problem. Maybe they're bad at that, I wouldn't be surprised. But if so, I bet in a year or two they'll be amazing at it. It might be good to build the habit of clarifying what sort of help you need, instead of assuming an LLM will give you the wrong kind of help?

oconnor663··on The Unreliability of LLMs and What Lies Ahead
A pedantic but maybe-not-entirely-pedantic point: It depends on what you mean by 99%.

If the calculator has a little gremlin in it that rolls a random 100-sided die, and gives you the wrong answer every time it rolls a 1, then you certainly can use it to build a bridge. You just need to do each calculation say 10 or 20 times and take the majority answer :)

If the gremlin is clever, it might remember the wrong answers it gave you, and then it might give them to you again if you ask about the same numbers. In that case you might need to buy 10 or 20 calculators that all have different gremlins in them, but otherwise the process is the same.

Of course if all your gremlins consistently lie for certain inputs, you might need to do a lot of work to sample all over your input space and see exactly what sorts of numbers they don't like. Then you can breed a new generation of gremlins that...

← PreviousPage 3 of 34Next →