HNHacker News
TopNewBestAskShowJobs

wasmperson

230 karma · joined July 4, 2025

submissionscomments
wasmperson··on A year of fighting scrapers on my 1.5 million-page website
This reminds me of all the confused comments that pop up whenever someone calls plain HTML with forms and links a "REST API." "But APIs are for programs! People visiting websites aren't programs."

https://news.ycombinator.com/item?id=32141027

wasmperson··on Fil-C: Garbage In, Memory Safety Out [video]
> Exceptions are a mechanism for resumable error-handling. Rust's `catch_unwind` function is a last-ditch mechanism for failure isolation

Tomayto Tomahto.

> I have seen many Rust programs in my day, and not a single one has ever used `catch_unwind`

Yeah, cause it sucks. Hence my original point: all downsides and no benefits. Also how people use Rust's exceptions has no bearing on whether or not they are exceptions.

> The fact that unwinding can be (and regularly is) trivially disabled is the ultimate motivator of why panics fundamentally cannot be used for error handling

Unwinding can be disabled in C++, too. They still call them exceptions over there.

> you appear to misunderstand the purpose of destructors.

In a language with exceptions, destructors (or something similar like try-finally) are required to guarantee resource cleanup. In a language without exceptions, it is enough to simply write the cleanup code at scope exit points. Rust's static analysis is strong enough to ensure you do this correctly in the hypothetical world where the language does not have exceptions.

If your insinuation here is that I don't understand why you might still want destructors in the absence of exceptions, then you've missed my point.

> Feel free to provide code if you would like to try to make a more precise argument against unwinding.

I already linked the precise argument against unwinding. It's written by Niko Matsakis, one of the core Rust language designers. It explains both the "temporary move from behind an exclusive reference" point and the "return a value from a drop impl" point (which the article calls "must move").

wasmperson··on "Clean" Code, Horrible Performance (2023)
> When you're updating 23 if-statements because you had to add support for some new business workflow, you'll wish you had a conceptual entity that encapsulated the operations on the type of workflows so you just had to implement them in one place.

Polymorphism won't get rid of the 23 if statements, it will just replace them with 23 method implementations. Then when you try to serialize that "conceptual entity" to a file or network socket you'll yearn for the if statements once more.

The main benefit of polymorphism is that it allows you to modify one part of a program without recompiling the other parts. In the absence of pre-compiled modules, polymorphism is isomorphic to branching/switch statements:

https://en.wikipedia.org/wiki/Expression_problem

wasmperson··on "Clean" Code, Horrible Performance (2023)
> I consider Clean Code to be in the category of books/styles that is helpful for early developers who need some structure

Clean Code is unhelpful to beginners too, though: misuse of industry-standard terms, shunning of comments in favor of tiny functions with long names, shunning function arguments in favor of mutating state, polymorphism obsession, etc. So much of the concrete advice the book gives is just plain bad.

The reason people get more pissed off at Clean Code than they would at any other book that gives bad advice is the preachy and authoritative tone it uses. It frames people who don't do "Clean Code" as unprofessional and lazy, and this framing is very convincing to some people, as evidenced by some of the replies in this thread.

wasmperson··on The Art of 64-bit Assembly
Yes the support is still there, although official MS documentation no longer mentions it. Some of those headers rather flagrantly violate C's strict aliasing rules, which I suspect is the reason MSVC never implemented type-based aliasing optimizations.
wasmperson··on Introduction to Data-Oriented Design [pdf]
One good example of DoD which isn't CPU-cache-related is relational databases. When designing a CRUD application, the Data-Oriented way of doing it is to figure out what data you will be storing and how to organize that data to minimize access times, which is what you're doing when you design the DB schema.

An example of failing to follow DoD is the N+1 query problem: a programmer builds an abstraction that operates on individual DB rows, but "where there's one, there's more than one": you will inevitably be running that code in a loop so that you can process multiple rows. If instead the programmer had abstracted over groups of rows, then per-item query overheads suddenly become per-batch overheads.

wasmperson··on Introduction to Data-Oriented Design [pdf]
I feel like DoD is one of those things that only makes sense after you already believe in it. There's a sort of KISS epiphany that you have to go through which I don't think the commonly available info on DoD helps you to reach. It doesn't help that everything about it tends to get hung up on overly-specific C++ optimization advice.
wasmperson··on Fil-C: Garbage In, Memory Safety Out [video]
It's hard for me to think of a definition of "exception" which doesn't include Rust's panics:

  use std::panic::catch_unwind;
  fn throws_exception(){
      panic!("hello");
  }
  fn main(){
      match catch_unwind(|| {
          throws_exception();
          println!("Never reached");
      }) {
          Ok(_) => (),
          Err(e) => println!(
              "threw an exception: {:?}",
              e.downcast_ref::<&'static str>().unwrap())
      }
  }
I can disable exceptions in rustc, but I can't disable the influence they have on language and library design (as detailed in the link I posted). Exceptions are the main reason you can't temporarily move something out from behind an exclusive reference, or return an error code from a Drop impl. Heck, Rust wouldn't even need destructors if it weren't for exceptions: the compiler could just tell you when you forgot to free something.
wasmperson··on Fil-C: Garbage In, Memory Safety Out [video]
Now it's your example that's too simple. I can't present a counter-example that wouldn't look totally different: if you avoid the indexing operator then trivially incorrect code like that becomes unrepresentable. It's like if I said destructors help you avoid memory leaks and you asked how you'd avoid a leak in a single-function program that does nothing but call `Box::leak()`.

Again: I'm not disputing the fact that the rust index operator does a dynamic bounds check.

wasmperson··on Fil-C: Garbage In, Memory Safety Out [video]
The worst part about rust not having exceptions is that it actually does have exceptions, you just shouldn't use them. All the downsides and none of the benefits:

https://smallcultfollowing.com/babysteps/blog/2024/05/02/unw...

wasmperson··on Fil-C: Garbage In, Memory Safety Out [video]
The way I see it, as far as memory safety is concerned that's just framing. Both Fil-C and Rust have a boundary below which the unsafe lives. If Fil-C is only safer than rust when you don't pass the `-F unsafe_code` argument to rustc then IMO "safer" in this case is somewhat of an empty claim.
wasmperson··on Fil-C: Garbage In, Memory Safety Out [video]
Yes, rust didn't invent the concept of an iterator, and is thus not the only language which offers a solution to statically avoid bounds checks. Feel free to give a more "interesting" case you believe rust wouldn't be able to handle.
wasmperson··on Fil-C: Garbage In, Memory Safety Out [video]
This isn't entirely true. Rust's indexing operator performs a dynamic check but the language and stdlib have a number of ways to access array elements without indexing, which is effectively a form of static checking. For example, the following loop is statically guaranteed to not go OOB, and will not emit any unnecessary bounds checks:

  for i in some_arr {
    println!("{i}");
  }
I suspect most techniques you could think of to statically avoid bounds checks are possible in rust's type system.
wasmperson··on Fil-C: Garbage In, Memory Safety Out [video]
> For example you can call mmap in Fil-C and it is still guaranteed to be memory safe, while in Rust you can easily violate memory safety by calling mmap.

That's the thing, though. Your Fil-C code isn't calling mmap, it's calling the Fil-C wrapper for mmap. In neither Fil-C nor Rust('s safe subset) can you call the actual system mmap.

wasmperson··on Fil-C: Garbage In, Memory Safety Out [video]
He claims that all syscalls are safe because they're implemented by his custom libc, but that libc then calls out to the system libc, where the system calls are unsafe. He then claims that this is unlike rust, where making a syscall is unsafe. If you stick to the rust standard library in the same way that fil-c programs stick to the fil-c standard library, then all of your rust "system calls" are safe, too.

Someone in the audience also pointed out that a tool like this could be used to compile rust programs, not just C programs, in which case it's odd to hear Fil-C repeatedly framed as a language in opposition to rust rather than as a tool which might complement it.

wasmperson··on Measuring Input Latency on Linux: X11 vs. Wayland, VRR, and DXVK
> Its their time to spend as they see fit.

I'm complaining on these developers' behalf, not bemoaning them doing what they were more or less forced to do. If glibc decided tomorrow to remove the `malloc` function from their library and every C project suddenly had to implement its own allocator, that would be a massive waste of everybody's time, no?

wasmperson··on How I use HTMX with Go
> If you're curious, and you too aren't in love with the "Modern frontend" philosophy

I'm also going to hesitantly mention sveltekit. From the outside it looks like yet another JS front-end framework but having been forced to use it recently I've learned it actually has great support for the more hypertext-focused design philosophy promoted by HTMX and friends.

wasmperson··on Measuring Input Latency on Linux: X11 vs. Wayland, VRR, and DXVK
Sure Wayland works fine. What you're not seeing is the hours and hours of volunteer labor wasted to get it to that point. Time that could have been spent working on features users actually cared about, now wasted "adding wayland support" to your favorite applications. If you could quantify it, the waste would be borderline criminal:

- Effort spent writing sway that could have been spent improving i3

- Effort spent writing GNOME-Wayland that could have been spent improving GNOME

- Effort spent writing KDE-Wayland that could have been spent improving KDE (much of this work duplicated effort with GNOME-Wayland)

- Effort spent writing wlroots to try and mitigate the effort being wasted by people writing bespoke compositors

- Wine/Proton devs needing to waste time getting every windows application to work in Wayland

- Firefox needing to target both Wayland and X

- A bunch of graphical toolkits and window managers that were working perfectly fine but will now be "left behind" since they lack the maintainers to support a porting effort

- low-level toolkits like SDL needing to implement their own window decorations now that they're not guaranteed to be provided by the OS (what?!)

What Wayland proves to me is just how easy it is for a small number of developers to unintentionally sabotage productivity in a much larger project.

wasmperson··on How the FSF sysadmins block botnets with reaction
I indeed did not know about this. There seem to be some caveats:

https://anubis.techaro.lol/docs/admin/configuration/challeng...

I guess for the meta refresh challenge it's less "proof of work" and more "proof of patience".

wasmperson··on How the FSF sysadmins block botnets with reaction
It's somewhat interesting to see the FSF's approach to this. From what I understand they can't really use something like anubis since they want their websites to be accessible without javascript:

https://www.gnu.org/philosophy/javascript-trap.html

Users can't consent to running a page's javascript the way they can consent to running a program they've intentionally downloaded, so it's effectively "non-free" regardless of license.

wasmperson··on Why Vanilla JavaScript
> but then product introduces a business rule where some fields need to be hidden when another option is selected somewhere

  function initWidget(root){
    // Or a bunch of calls to document.createElement, whatever you want.
    const inp = root.querySelector('.input');
    const check = root.querySelector('.check');

    function update(){
      inp.style.display = check.checked ? 'none' : 'block';
    }
    check.onchange = update;
  }
> O, but not when this checked, etc.

  function initWidget(root){
    // Or a bunch of calls to document.createElement, whatever you want.
    const inp = root.querySelector('.input');
    const check = root.querySelector('.check');
    const check2 = root.querySelector('.check2');

    function update(){
      inp.style.display = check.checked && !check2.checked ? 'none' : 'block';
    }
    check.onchange = update;
    check2.onchange = update;
  }
Compare to React:

  function Widget(){
    const [check1, setCheck1] = useState(false);
    const [check2, setCheck2] = useState(false);

    return <>
      <input type="checkbox"
        checked={check1} onChange={e => setCheck1(e.target.checked)}/>
      <input type="checkbox"
        checked={check2} onChange={e => setCheck2(e.target.checked)}/>
      {check1 && !check2 && <input type="text" />}
    </>;
  }
Compare to Svelte:

  <script>
    let check1 = $state(false);
    let check2 = $state(false);
  </script>

  <input type="checkbox" bind:checked={check1}>
  <input type="checkbox" bind:checked={check2}>
  {#if check1 && !check2}
    <input type="text">
  {/if}
IME almost none of the complexity in any of the web applications I've worked with has been mitigated by the front-end framework in use. You still need to write the code to do the thing, whatever that thing is. You might as well write it in the framework that gives you the smallest bundle size and the best possible backwards compatibility, and that's vanilla JS + the standard web APIs.
wasmperson··on Understanding the Odin programming language
Agreed. IME the main reason to choose arena allocators is for correctness, not speed. They make similar time/space tradeoffs to garbage collectors in that they grant higher allocation throughput in exchange for more memory usage.

The perf argument against RAII is very abstract and is less "RAII causes bad performance" and more "the kind of design that leads you to reach for RAII is the kind of design that's bad for performance." There exist similar hand-wavy arguments against many other C++/Rust features.

More generally, "you shouldn't even want that" is basically a meme at this point in programming language design. Every new-ish language has some version of it.

wasmperson··on Almost Always Unsigned
Thinking of it as a "stopping condition" is backwards, that part of the loop is called the invariant:

https://en.wikipedia.org/wiki/Loop_invariant

You should think of it as the condition that's true for all iterations, not a one-time event that halts the loop. The loop is short for this:

  for(size_t i = size - 1; 0 <= i && i < size; i--){
  
  }
Which works for both signed and unsigned numbers. It just so happens that for unsigned numbers you can omit the left-hand side of the &&, and for signed numbers you can omit the right-hand side. To support arbitrary lower bounds, you omit neither.
wasmperson··on A native graphical shell for SSH
> without having to do any sudo commands or expose anything new to the network.

Again I'm not understanding the distinction. I don't need to run sudo commands to install a web server, and depending on your definition of "exposing something new" to the network then either I don't have to do that either or your solution also does that.

Something is getting downloaded and run on the remote machine, correct? Why is it problematic for that something to be a web server (with SSH-forwarding I guess) instead of this custom thing?

And why install anything on the server at all if it'll just serve a binary that downloads and runs on your local computer anyway? For example, if I type `sftp://username@server.domain/file/path` into my file manager's address bar, I get the nice file browsing experience you demonstrate without installing anything on my computer or the server.

EDIT: OK, after reading through your earlier posts, I think the value proposition really is just that you've implemented a slightly better UX for proxying remote web servers via ssh, and that the "run native code" thing is an independent idea you are also pursuing. So the answer to the question "isn't this just proxying an http server over ssh" is basically yes.

I think I incorrectly read this as attempting to propose a radically new idea and not as an incremental improvement to the status quo.

wasmperson··on A native graphical shell for SSH
> it requires exposing a port to the internet or using some SSH port forwarding tool

This sentence is bizarre to me. Your SSH-based solution also requires exposing a port to the internet and installing a special tool (on both server and client!). What's so special about SSH that using HTTPS is a problem but using SSH isn't?

The industry also tried the whole "use the web browser to run native binaries" thing with ActiveX (and the unity web player I guess). The idea was thrown out along with flash and java applets for what I presume were security and portability reasons.

wasmperson··on How's Linear so fast? A technical breakdown
In gamedev, "optimistic updates" are called "client-side prediction," and are a standard part of multiplayer games. IMO it's somewhat risky to apply the technique to web-apps, since each network request typically corresponds to some important operation, and optimistically updating the UI is lying to the user about whether that operation completed successfully.

IMO a good approach is to update the UI immediately but still show some indication that the operation hasn't completed. So in a chat app, for example, add the message to the list of messages, but with contrast reduced slightly to indicate that other people can't see it yet.

wasmperson··on C++: The Documentary
Your perspective on this may be distorted due to your personal involvement. Do you believe MSVC++ or Turbo C++ would still have existed without Zortech C++ arriving first? Because if so then I don't think you can really take credit for C++ popularity on the PC.
wasmperson··on C array types are weird
It matters in single-pass compilers. You can't allocate a variable in a register if its address is ever taken, but by the time a single-pass compiler knows that information it has already spit out all of the assembly for the function.
wasmperson··on What color is your function? (2015)
> This article is about async/await.

It is not. It tries to address async/await part-way through, but it does so without the context of 10 years of successful async/await usage in javascript, the language it's criticizing.

> Threads do solve this problem because they are just regular functions being called by other regular functions. They don't require the entire function stack to be `async` in order to work.

This is fixating on syntax: it would be trivial for all functions to simply be `async` by default and for all calls to an `async` function to automatically `await`. This might "fix" the coloring problem as you describe it but I argue wouldn't meaningfully change anything.

wasmperson··on What color is your function? (2015)
IMO the function coloring problem was solved with async/await. This article was posted before Javascript's async/await syntax cleaned up that ecosystem, so the author is only guessing when they say it doesn't fix the issue. It did fix the issue, and now function coloring isn't really a problem.

If async/await doesn't solve the coloring problem, then neither do threads. Why would you ever need to start a thread to invoke a function when you could just invoke the function directly? Because the function is a red function.

← PreviousPage 2 of 4Next →