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 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...

oconnor663··on Human coders are still better than LLMs
> 27 years later, talking to the latest Siri, it makes just as many mistakes as it did back then

Have you tried talking to ChatGPT voice mode? It's mind blowing. You just have a conversation with it. In any language. About anything. The other day I wanted to know about the difference between cast iron and wrought iron, and it turned into a 10 or 15 minute conversation. That's maybe a good example of an "easy" topic for LLMs (lots of textbooks for it to memorize), but the world is full of easy topics that I know nothing about!

oconnor663··on Human coders are still better than LLMs
> don't seem to be getting better recently

o3 came out just one month ago. Have you been using it? Subjectively, the gap between o3 and everything before it feels like the biggest gap I've seen since ChatGPT originally came out.

oconnor663··on There Is No Diffie-Hellman but Elliptic Curve Diffie-Hellman
Fwiw I don't think there's anything inherently sequential about the Keccak permutation itself. KangarooTwelve is a fully parallelizable hash built on Keccak. (Though they did use the sponge construction on the XOF side, so that part is serial.)
oconnor663··on The first year of free-threaded Python
Sure but of those 99%, how many are performance-sensitive, CPU-bound (in Python not in C) applications? It's clearly some, not saying it's an easy tradeoff, but I assume the large majority of Python programs out there won't notice a slowdown.
oconnor663··on The first year of free-threaded Python
I think this is conflating two different things. A Rust Mutex gets poisoned if the thread holding it panics, but that's not the same thing as evaporating into thin air. Destructors run while a panic unwinds (indeed this is how the Mutex poisons itself), and you usually have the option of catching panics if you want. In the panic=abort configuration, where you can't catch a panic, it takes down the whole process rather than just one thread, which is another way of making the same point here: you can't usually kill a thread independently of the whole process its in, because lots of things (like locks) assume you'll never do that.
oconnor663··on Flattening Rust’s learning curve
> Could owner be something else than a stack frame?

Yes. There are lots of ways an object might be owned:

- a local variable on the stack

- a field of a struct or a tuple (which might itself be owned on the stack, or nested in yet another struct, or one of the other options below)

- a heap-allocating container, most commonly basic data structures like Vec or HashMap, but also including things like Box (std::unique_ptr in C++), Arc (std::shared_ptr), and channels

- a static variable -- note that in Rust these are always const-initialized and never destroyed

I'm sure there are others I'm not thinking of.

> Why would a stack frame want to move ownership to its callee, when by the nature of LIFO the callee stack will always be destroyed first

Here are some example situations where you'd "pass by value" in Rust:

- You might be dealing with "Copy" types like integers and bools, where (just like in C or C++ or Go) values are easier to work with in a lot of common cases.

- You might be inserting something into a container that will own it. Maybe the callee gets a reference to that longer-lived container in one of its other arguments, or maybe the callee is a method on a struct type that includes a container.

- You might pass ownership to another thread. For example, the main() loop in my program could listen on a socket, and for each of the connections it gets, it might spawn a worker thread to own the connection and handle it. (Using async and "tasks" is pretty much the same from an ownership perspective.)

- You might be dealing with a type that uses ownership to represent something besides just memory. For example, owning a MutexGuard gives you the ability to unlock the Mutex by dropping the guard. Passing a MutexGuard by value tells the callee "I have taken this lock, but now you're responsible for releasing it." Sometimes people also use non-Copy enums to represent fancy state machines that you have to pass around by value, to guarantee whatever property they care about about the state transitions.

oconnor663··on Matt Godbolt sold me on Rust by showing me C++
This account's been pretending to be Matt Godbolt since 2014. Knows him better than he knows himself. Absolute commitment to the bit.
oconnor663··on Matt Godbolt sold me on Rust by showing me C++
> The borrow checker rejects loads of sound programs - just read https://rust-unofficial.github.io/too-many-lists/

It's important to be careful here: a lot (most? all?) of these rejections are programs that could be sound in a hypothetical Rust variant that didn't assert the unique/"noalias" nature of &mut reference, but are in fact unsound in actual Rust.

oconnor663··on Ask HN: Memory-safe low level languages?
Can you give some examples of Rust features that you don't need? Unfortunately a lot of common answers (the borrow checker, traits, move semantics) are things that are fundamental to the safety story. I guess one thing you could definitely subtract is async (Rust shipped without it after all).
oconnor663··on TLS certificate lifetimes will officially reduce to 47 days
If we know each other's DH public key in advance, then you're totally right, DH is secure over an untrusted network. But if we don't know each other's public keys, we have to get them over that same network, and DH can't protect us if the network lies about our public keys. Solving this requires some notion of "identity", i.e. some way to verify that when I say "my public key is abc123" it's actually me who's saying that. That's why it's hard to have privacy without identity.
oconnor663··on How to Win an Argument with a Toddler
> There's a problem to be solved

"valid"

oconnor663··on TLS certificate lifetimes will officially reduce to 47 days
They MITM the key exchange step at the beginning, and now they do have the keys. The thing that prevents this in TLS is the chain of signatures asserting identity.
oconnor663··on Hacktical C: practical hacker's guide to the C programming language
> It has very few opinions about your code

I understand where this is coming from, but I think this is less true than it used to be, and (for that reason) it often devolves into arguments about whether the C standard is the actual source of truth for what you're "really" allowed to do in C. For example, the standard says I must never:

- cast a `struct Foo*` into a `struct Bar*` and access the Foo through it (in practice we teach this as the "strict aliasing" rules, and that's how all(?) compilers implement it, but that's not what §6.5 paragraph 7 of the standard says!)

- allow a signed integer to overflow

- pass a NULL pointer to memcpy, even if the length is zero

- read an unitialized object, even if I "don't care" what value I get

- read and write a value from different threads without locking or atomics, even if I know exactly what instructions those reads and writes compile into and the ISA manual says it's 100% fine to do that

All of these are ways that (modern, standard) C doesn't really "do what the programmer said". A lot of big real-world projects build with flags like -fno-strict-aliasing, so that they can get away with doing these things even though the standard says they shouldn't. But then, are they really writing C or "C with custom extensions"? When we compare C to other languages, whose extensions are we talking about?

oconnor663··on Hacktical C: practical hacker's guide to the C programming language
> Predictable response: "But they can only occur in unsafe regions which you can grep for" and my response to that: "so?"

The situation is both worse than this and better than this. Consider the .set_len() method on Rust's Vec. It's unsafe, because you could just .set_len(1_000_000) and then the Vec would happily let you try to read the nonexistent millionth element and segfault. However, if you could edit the standard library sources, you could add this new method to Vec without touching any unsafe code:

    pub fn set_len_totally_safe_i_promise(&mut self, new_len: usize) {
        self.len = new_len;
    }
This is exactly the same as the real set_len, except it's a "fn" instead of an "unsafe fn". Now the Vec API is totally broken, and safe callers can corrupt memory. Also critically, we didn't write any unsafe code in "set_len_totally_safe_i_promise". The key detail is that this new method has access to the private self.len field of Vec that unsafe blocks in the same module rely on.

In other words, grepping for all the unsafe blocks isn't sufficient for saying that a program is UB-free. You also have to make sure that none of the safe code ever violates an invariant that the unsafe blocks rely on. Read the comments, think really hard, etc.

So...what's the point of all this? The point is that it lets us define a notion of "soundness", such that if I only write safe code, and I only use libraries that are "sound", we can guarantee that my program is UB-free. In other words, any UB in my program would necessarily point to a bug in one of my dependencies, in the stdlib, or in the compiler. (Or you know, in the hardware, or in mathematics itself.) In other other words, instead of auditing my entire gigantic (safe) program for UB, we can reduce the problem to auditing my dependencies for soundness. Crucially, this decouples the difficulty of the problem from the size of my program. This wouldn't be very interesting if "safe code" was some impoverished subset, like "unsigned integer arithmetic only". But in fact safe code can use pointers, tagged unions, pointers into tagged unions, heap allocation/freeing, and multithreading. Lots of large, complicated, useful, real-world programs are written in 100% safe code. Here the version of this story with all the caveats and footnotes: https://jacko.io/safety_and_soundness.html

oconnor663··on JEP draft: Prepare to make final mean final
(Rereading your comment, it sounds like you might already know all of this. Apologies.)

If I understand correctly, here's a C++ example that has undefined behavior:

    int foo() {
        const int x = 42;
        int *p = (int *)&x;
        *p += 1;
        return x;
    }
foo() is UB, because it modifies the const object x, using a pointer cast to "cast away const". (Unfortunately, UBSan doesn't catch this, and I'm not aware of any sanitizer that does.) It's tempting to say that the pointer cast is "at fault" for the UB, but consider this very similar example that's not UB:

    int bar() {
        int x = 42;
        const int *const_p = &x;
        int *p = (int *)&const_p;
        *p += 1;
        return x;
    }
bar() has exactly the same pointer cast as foo(), however in this case the original x object is not const. That makes "casting away const" legal in this case. So the problem we're left with, is that knowing all the types isn't enough for us to tell whether this cast is going to cause UB. We have to know where the pointer originally came from, which might be in another function or another file.
oconnor663··on A decision to eject from a failing F-35B fighter and the betrayal in its wake
If you're going down in zero visibility, what good can you do staying in the jet?
oconnor663··on I stopped everything and started writing C again
C-with-sanitizers is miles ahead of what C used to be, but just a couple weeks ago I ran into a dangling pointer bug that ASan doesn't catch. (Sidenote 5 here: https://jacko.io/smart_pointers.html) It seems like one of the big downsides of sanitizers is that they can't instrument other people's code, in this case the C standard library.
oconnor663··on Zig's dot star syntax (value.*)
On top of those fundamentals, there have been more recent developments:

https://doc.rust-lang.org/std/ptr/index.html#strict-provenan...

https://www.ralfj.de/blog/2018/07/24/pointers-and-bytes.html

https://www.ralfj.de/blog/2020/12/14/provenance.html

https://www.ralfj.de/blog/2022/04/11/provenance-exposed.html

oconnor663··on Smart Pointers Can't Solve Use-After-Free in C++
The way that expert programmers make these same mistakes is by introducing a few layers of abstraction. Here's a real CVE from Microsoft: https://msrc.microsoft.com/blog/2019/07/we-need-a-safer-syst...

> This bug is possible because of how many complex APIs interact with each other and the programmer not being able to enforce ownership of memory throughout the codebase. At [0], the program gets a pointer to a buffer owned by a JavaScript object. Then at [1], because of the language complexity, to get another variable, it might execute more JavaScript code. At [2], it will use the buffer and width to create a new JavaScript object with the contents of that pointer.

So to adapt that example, the way an expert programmer in a complicated codebase might end up invalidating an iterator, could be by looping over a list that user-provided scripts are allowed to modify, and then accidentally re-entering script code in that loop. It might take a really contrived script to exploit the bug, but such is life in the browser/JS world.

oconnor663··on Greg K-H: "Writing new code in Rust is a win for all of us"
If you haven't been maintaining any Rust code, you might have the impression that breaking changes are far more common than they really are. Rust has about as many breaking changes as Go, probably fewer? (Because Go lacks an edition mechanism.)
oconnor663··on Greg K-H: "Writing new code in Rust is a win for all of us"
I'm no kernel dev, but I assume that DMA bindings (what this round of drama was originally all about) fall squarely into "stuff that drivers obviously need".
oconnor663··on Greg K-H: "Writing new code in Rust is a win for all of us"
Granted, there are plenty of people who don't understand these issue very well who think "Rust = no bugs". Of course they're wrong. But that said, this CVE is an interesting example of just how high the bar is that Rust sets for correctness/security. The bug is that, if you pass 18446744073709551616 as the width argument to this array transpose function, you get undefined behavior. It's not clear whether any application has ever actually done this in practice; the CVE is only about how it's possible to do this. In most C libraries, on the other hand, UB for outrageous size/index parameters would be totally normal, not even a bug, much less a CVE. If an application screwed it up, maybe you'd open a CVE against the application.
oconnor663··on Greg K-H: "Writing new code in Rust is a win for all of us"
This is exactly the position Christoph Hellwig took in the original email chain that kicked off the current round of drama: https://lore.kernel.org/rust-for-linux/20250131075751.GA1672.... I think it's fair to say that this position is getting plenty of attention.
oconnor663··on Go Is a Well-Designed Language
> defers are function-scoped instead of block-scoped.

I think this is a basic tradeoff with defer and not a problem per se. Are users more likely to need to clean up resources every time through a loop, or more likely to acquire resources that outlive the loop? And which is more confusing if you expected the other? I think it's a tough call. (I understand why a systems programming language would insist on block scoping to avoid allocating.)

oconnor663··on Static search trees: faster than binary search
I'm not sure whether this is exactly what you mean, but when you need to support deletions you can switch to a "generational" collection like Slab or SlotMap. Your indexes are still integers, but under the covers some of the bits get repurposed to disambiguate new elements that reuse old slots.
oconnor663··on Static search trees: faster than binary search
I have two different reactions to this:

- From the perspective of e.g. C++, I don't think it's reinventing the heap. We often prefer to use indexes into a std::vector or similar, even when putting everything in std::shared_ptr is an option, because the dense layout of the vector gives us the blazing fast cache performance we crave. It also avoids memory leaks from reference cycles.

- From the perspective of e.g. Python, yeah it's reinventing the heap. Unless you need to serialize the whole collection into JSON or something, it's much less convenient. But (almost) no one uses Rust instead of Python just for the convenience. (There are dozens of us! Dozens!)

oconnor663··on Static search trees: faster than binary search
I don't think the indexing strategy is more straightforward in general. It's a thing you have to learn, and the fact that you have to learn it is part of Rust's famously steep learning curve. That said, there are some common cases where you might wish you'd used indexes instead of pointers in other languages too:

- Working with resizeable collections like std::vector in any non-GC language, where resizing invalidates pointers.

- Serialization. If your objects contain indexes to each other, it's easy to turn them into e.g. JSON. But if they contain pointers to each other, it's tricky.

oconnor663··on Four limitations of Rust's borrow checker
I think there's an important distinction here. A systems programming language needs to have all the max speed / minimum overhead / UB-prone stuff available, but that stuff doesn't need to be the default / most convenient way of doing things. Rust heavily (both syntactically and culturally) encourages safe patterns that sometimes involve runtime overhead, like checked indexing, but this isn't the same as "forcing" you into these patterns.
oconnor663··on Four limitations of Rust's borrow checker
> vs. the obvious legacy/unchecked idioms

You can go crazy with legazy/unchecked/unsafe stuff if you want to in Rust. It's less convenient and more difficult than C in some ways, but 1) it's also safer and more convenient in other ways, and 2) "this will be safe and convenient" isn't exactly the reason we dive into legacy/unchecked/unsafe stuff.

And of course the greatest strength of the whole Rust language is that folks who want to do crazy unsafe stuff can package it up in a safe interface for the rest of us to use.

← PreviousPage 4 of 34Next →