Most active commenters
  • dvt(7)
  • JoshTriplett(4)
  • eru(4)
  • jibal(3)
  • kketch(3)

←back to thread

517 points bkolobara | 60 comments | | HN request time: 0.351s | source | bottom
1. merdaverse ◴[] No.45043051[source]
Code written below your line gets executed if you don't return early. More breaking news at 8.

Seriously, why would you think that assigning a value would stop your script from executing? Maybe the Typescript example is missing some context, but it seems like such a weird case to present as a "data race".

replies(8): >>45043245 #>>45043339 #>>45043398 #>>45043537 #>>45043876 #>>45046033 #>>45046975 #>>45049155 #
2. love2read ◴[] No.45043245[source]
It seems weird to shame someone for talking about their own experience?
3. lights0123 ◴[] No.45043339[source]
exit(), execve(), and friends do immediately stop execution—I could understand why you'd think a redirect would as well.
replies(4): >>45043409 #>>45043410 #>>45049020 #>>45049406 #
4. Arch-TK ◴[] No.45043398[source]
Assigning to `window.location.href` has a side effects. The side effect is that your browser will navigate to wherever you assigned, as if you had clicked a link. This is already a surprising behaviour, but given that this assignment is effectively loading a new page in-place, kind of like how `execve` does for a process, I can totally see how someone would think that JS execution would stop immediately after a link is clicked.

It's obviously not a good idea to rely on such assumptions when programming, and when you find yourself having such a hunch, you should generally stop and verify what the specification actually says. But in this case, the behaviour is weird, and all bets are off. I am not at all surprised that someone would fall for this.

replies(4): >>45043684 #>>45043902 #>>45044345 #>>45048334 #
5. JoshTriplett ◴[] No.45043409[source]
Exactly. Given that JavaScript runs in the context of a page, redirecting off of the page seems like it should act like a "noreturn" function...but it doesn't. That seems like a very easy mistake to make.
6. dvt ◴[] No.45043410[source]
The redirect is an assignment. In no language has a variable assignment ever stopped execution.
replies(6): >>45043422 #>>45043544 #>>45043663 #>>45043805 #>>45047504 #>>45047601 #
7. dminik ◴[] No.45043422{3}[source]
That doesn't seem that obvious to me. You could have a setter that just calls exit and terminates the whole program.
replies(1): >>45043543 #
8. jemiluv8 ◴[] No.45043537[source]
This is more a control flow issue than a data race issue. I've seen this countless times. And it is often a sign that you don't spend too much time writing JavaScript/Typescript. You get shot in the foot by this very often. And some linters will catch this - most do actyally
9. dvt ◴[] No.45043543{4}[source]
Yeah, this is actually a good point, could have a custom setter theoretically that simply looks like assignment, but does some fancy logic.

    const location = {
      set current(where) {
        if (where == "boom") {
            throw new Error("Uh oh"); // Control flow breaks here
        }
      }
    };

    location.current = "boom" // exits control flow, though it looks like assignment, JS is dumb lol
10. JoshTriplett ◴[] No.45043544{3}[source]

    $ python3
    Python 3.13.7 (main, Aug 20 2025, 22:17:40) [GCC 14.3.0] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> class MagicRedirect:
    ...     def __setattr__(self, name, value):
    ...         if name == "href":
    ...             print(f"Redirecting to {value}")
    ...             exit()
    ... 
    >>> location = MagicRedirect()
    >>> location.href = "https://example.org/"
    Redirecting to https://example.org/
    $
replies(1): >>45045418 #
11. lock1 ◴[] No.45043663{3}[source]
You could overload operator=() in C++ with a call to exit(), which fulfills "variable assignment that halts the program".
replies(3): >>45043699 #>>45045377 #>>45050093 #
12. stouset ◴[] No.45043684[source]
Part of the problem is that we unknowingly make a million little assumptions every day in the course of software development. Many of them are reasonable, some of them are technically unreasonable but fine in practice, and some of them are disasters waiting to happen. And it's genuinely hard to not only know which are which, but to notice even a fraction of them in the first place.

I'm sure I knew the href thing at one point. It's probably even in the documentation. But the API itself leaves a giant hole for this kind of misunderstanding, and it's almost certainly a mistake that a huge number of people have made. The more pieces of documentation we need to keep in our heads in order to avoid daily mistakes, the exponentially more likely it is we're going to make them anyway.

Good software engineering is, IMHO, about making things hard to hold the wrong way. Strong types, pure functions without side effects (when possible), immutable-by-default semantics, and other such practices can go a long way towards forming the basis of software that is hard to misuse.

replies(2): >>45049734 #>>45051440 #
13. dvt ◴[] No.45043699{4}[source]
I was ignoring these kinds of fancy overload cases, but even in JS you can mess with setters to get some unexpected behavior (code below).
14. ordu ◴[] No.45043805{3}[source]
Try this in C:

*(int*)0 = 0;

Modern C compilers could require you to complicate this enough to confuse them, because their approach to UB is weird, if they saw an UB they could do anything. But in olden days such an assignment led consistently to SIGSEGV and a program termination.

replies(1): >>45043955 #
15. drdrey ◴[] No.45043876[source]
OP thought the redirect was synchronous, not that it would stop the script from executing
replies(1): >>45044099 #
16. ngruhn ◴[] No.45043902[source]
I use JavaScript for ~15 years. I thought it worked like that.
replies(1): >>45044023 #
17. DannyBee ◴[] No.45043955{4}[source]
Unless you were on systems that mapped address 0 to a writable but always zero value so they could do load and store speculation without worry.

IBM did this for a long time

replies(3): >>45044327 #>>45045931 #>>45051939 #
18. svieira ◴[] No.45044023{3}[source]
I'm pretty sure it did used to work the other way. Even if it didn't something changed recently so that the "happen later" behavior was significantly more likely to be encountered in common browsers.
19. jibal ◴[] No.45044099[source]
No, you're mistaken. Read the other comments under the parent. If it were synchronous then it would have stopped the script from executing, much the way POSIX exec() works. If the OP didn't think that the script would stop, then why would he let execution fall through to code that should not execute ... which he fixed by not letting it fall through?
replies(1): >>45045513 #
20. ◴[] No.45044327{5}[source]
21. dkarl ◴[] No.45044345[source]
> when you find yourself having such a hunch, you should generally stop and verify what the specification actually says

It greatly heartens me that we've made it to the point where someone writing Javascript for the browser is recommended to consult a spec instead of a matrix of browsers and browser versions.

However, that said, why would a person embark on research instead of making a simple change to the code so that it relies on fewer assumptions, and so that it's readable and understandable by other programmers on their team who don't know the spec by heart?

22. BobbyJo ◴[] No.45045377{4}[source]
idk if I'd consider overloading the assignment operator to call a function, then using it, actually an assignment in truth.
replies(1): >>45048519 #
23. dvt ◴[] No.45045418{4}[source]
You're overloading a setter here. It's cute, I did it in JS as well, but I don't really think it's a counterexample. It would be odd to consider this the norm (per the thought process of the original blog post).
replies(2): >>45045557 #>>45045946 #
24. drdrey ◴[] No.45045513{3}[source]
I see, thanks
25. rowanG077 ◴[] No.45045557{5}[source]
This is not some weird thing. Here is a run of the mill example where python can have properties settting do anything at all. And it's designed like that.

    import sys
    
    class Foo:
        @property
        def bar(self):
            return 10
            
        @bar.setter
        def bar(self, value):
            print("bye")
            sys.exit()
    
    foo = Foo()
    foo.bar = 10
Or in C# if you disqualify dynamic languages:

    using System;

    class Foo
    {
        public int Bar
        {
            get { return 10; }
            set
            {
                Console.WriteLine("bye.");
                Environment.Exit(0);
            }
        }
    }

    class Program
    {
        static void Main()
        {
            Foo obj = new Foo();
            obj.Bar = 10;
        }
    }

This is not some esoteric thing in a lot of programming languages.
replies(1): >>45046687 #
26. mabster ◴[] No.45045931{5}[source]
My favourite were older embedded systems where 0 was an address you actually do interact with. So for some portion of the code you WANT null pointer access. I can't remember the details but I do remember jumping to null to reset the system being pretty common.
replies(2): >>45046092 #>>45049285 #
27. dminik ◴[] No.45045946{5}[source]
But window.location.href is already an overloaded setter. It schedules a page navigation.
28. IshKebab ◴[] No.45046033[source]
Whenever someone talks about a surprising paper cut like this you always see misguided "this is obvious" comments.

No shit. It's obvious because you literally just read a blog post explaining it. The point is if you sprinkle dozens of "obvious" things through a large enough code based, one of them is going to bite you sooner or later.

It's better if the language helps you avoid them.

29. marshray ◴[] No.45046092{6}[source]
Probably the system interrupt table. Index 0 might reference the handler for the non-maskable interrupt NMI, often the same as a power-on reset.

I recall that on DOS, Borland Turbo C would detect writes to address 0 and print a message during normal program exit.

30. dvt ◴[] No.45046687{6}[source]
You're also overriding a setter. Maybe I'm going against the grain here, but it's absolutely esoteric. The assignment operator is not supposed to have side-effects, and maybe this is the logician in me, but the implication that we should be aware that weird stuff might be happening when we do `x = 5` is fundamentally bonkers.
replies(4): >>45046718 #>>45047382 #>>45048993 #>>45050028 #
31. JoshTriplett ◴[] No.45046718{7}[source]
You started with "In no language has a variable assignment ever stopped execution", and now you're saying "The assignment operator is not supposed to have side-effects". location.href is a counterexample, and there are many counterexamples throughout various tools and languages and libraries. Deciding how you think things should work does not affect how things do work, and it's important to understand the latter. (I do agree it's bad practice, but it happens and people do not always fully control the environments they must work with.)

And given that location.href does have a side effect, it's not unreasonable for someone to have assumed that that side effect was immediate rather than asynchronous.

That said, if you don't like working with such languages, that's all the more reason to select languages where that doesn't happen, which comes back to the point made in the article.

replies(1): >>45046819 #
32. dvt ◴[] No.45046819{8}[source]
> You started with "In no language has a variable assignment ever stopped execution",

The irony is that I'm still technically correct, as literally every example (from C++, to C#, to Python, to JS) have been object property assignments abusing getters and setters—decidedly not variable assignments (except for the UB example).

replies(2): >>45046918 #>>45047457 #
33. rowanG077 ◴[] No.45046918{9}[source]
The entire discussion is about a property assignment. Which in colloquial usage is also called variable assignment. Which is obvious since nobody corrected you on that. You now trying to do a switcheroo is honestly ridiculous.
replies(1): >>45047059 #
34. Humphrey ◴[] No.45046975[source]
Whether you think that or not is not the issue - the fix is very obvious once pointed out to you. The arguement the author is making is that a bug like that TS issue can be very difficult and time consuming to track down and is not picked up on by the compiler.
35. dvt ◴[] No.45047059{10}[source]
The entire discussion is about “=“ doing weird stuff, which in 99.9% of cases it does not do. And my point was that no language, without doing weird stuff (like overloading), does not let “=“ do weird stuff (and thus is pure). The counterarguments all involve nonstandard contracts. Therefore, thinking that using “=“ will have some magical side-effect is absolutely never expected by default.
replies(2): >>45047506 #>>45048333 #
36. kketch ◴[] No.45047382{7}[source]
assignments are side effects, even more so when they are done through a setter on an object / class instance
37. kketch ◴[] No.45047457{9}[source]
Technically no. Producing side effects from a setter is not unexpected, even if it often the best idea to have a setter have a lot of unexpected side effects. However producing side effects from getters is definitely unexpected and should not be done. Interestingly it's one of the areas where rust is really useful, it forces you express your intent in terms of mutability and is able to enforce these expectations we have.
replies(2): >>45047582 #>>45048499 #
38. AdieuToLogic ◴[] No.45047504{3}[source]
> The redirect is an assignment. In no language has a variable assignment ever stopped execution.

Many languages support property assignment semantics which are defined in terms of a method invocation. In these languages, the method invoked can stop program execution if the runtime environment allows it to do so.

For example, source which is defined thusly:

  foo.bar = someValue
Is evaluated as the equivalent of:

  foo.setBar (someValue)
39. JoshTriplett ◴[] No.45047506{11}[source]
> The counterarguments all involve nonstandard contracts. Therefore, thinking that using “=“ will have some magical side-effect is absolutely never expected by default.

That sounds like a recipe for having problems every time you encounter a nonstandard contract. Are you actually saying you willfully decide never to account for the possibility, or are you conflating "ought not to be" with "isn't"?

If I'm programming in a language that has the possibility of properties, it's absolutely a potential expectation at any time. Which is one reason I don't enjoy programming in such languages as much.

To give a comparable example: if I'm coding in C, "this function might actually be a macro" is always a possibility to be on guard against, if you do anything that could care about the difference (e.g. passing the function's name as a function pointer).

40. galangalalgol ◴[] No.45047582{10}[source]
Overloading assignment operators to maintain an invariant is one thing, but this particular case of it running off and effectively doing ionis weird to me coming from an embedded c++ background. I don't like operator overloading and think it should be avoided, just to make my bias obvious. I don't code c++ anymore either, rust and no looking back for a few years now.
replies(1): >>45050085 #
41. kji ◴[] No.45047601{3}[source]
In Blink setHref is automatically bound to C++ code [1]. I think it's fair to say that anything goes.

[1]: https://source.chromium.org/chromium/chromium/src/+/main:thi...

42. scheme271 ◴[] No.45048333{11}[source]
So, with anything that isn't a primitive type (e.g. int, bool, etc), there's a chance that assignment is going to require memory allocation or something similar. If that's the case then there's a chance of bad things happening (e.g. a out of memory error and the program being killed).

More commonly, if you look at things like c++'s unique_ptr, assignment will do a lot of things in the background in order to keep the unique_ptr properties consistent. Rust and other languages probably do similar things with certain types due to semantic guarantees.

replies(1): >>45048505 #
43. hsbauauvhabzb ◴[] No.45048334[source]
Is this a JavaScript wart or a browser wart though? JavaScript is communicating to the browser via an API and rust would need to do the same.
44. eru ◴[] No.45048499{10}[source]
> Interestingly it's one of the areas where rust is really useful, it forces you express your intent in terms of mutability and is able to enforce these expectations we have.

Though Rust only cares about mutability, it doesn't track whether you are going to launch the nukes or format the hard disk.

replies(1): >>45051413 #
45. eru ◴[] No.45048505{12}[source]
In languages like JavaScript (or Python etc) you can get memory allocation etc even when 'primitives' like int and bool are involved.

Haskell is the same, but for different reasons.

46. eru ◴[] No.45048519{5}[source]
Well, when you read the source of the caller, it looks exactly like a normal assignment.
47. toast0 ◴[] No.45048993{7}[source]
> The assignment operator is not supposed to have side-effects,

Memory mapped I/O disagrees with this. Writing a value can trigger all sorts of things.

48. sintax ◴[] No.45049020[source]
Until they don't. A common issue is not checking if the execve() actually worked and thinking nothing after the execve() will execute, which is an assumption that it not always true.
49. masklinn ◴[] No.45049155[source]
> Seriously, why would you think that assigning a value would stop your script from executing?

This assignment has a significant side-effect of leaving the page, assuming this is immediate rather than a scheduled asynchronous action is not unfair (I’m pretty sure I assumed the same when I saw or did that).

50. ghurtado ◴[] No.45049285{6}[source]
RANDOMIZE USR 0
51. jacquesm ◴[] No.45049406[source]
See, that's what they meant about making assumptions upthread:

https://man7.org/linux/man-pages/man3/atexit.3.html

52. buu700 ◴[] No.45049734{3}[source]
Honestly, the href thing feels like a totally reasonable assumption to me. I think the API design is unfortunate, but given that the API is designed as it is, it stands to reason that the script would also halt execution upon hitting that line.

For me, that's exactly the kind of thing that I tend to be paranoid about and handle defensively by default. I couldn't have confidently told you before today what the precise behavior of setting location.href was without looking it up, but I can see that code I wrote years ago handled it correctly regardless, because it cost me nothing at the time to proactively throw in a return statement.

As in this example, defensiveness can often prevent frustrating heisenbugs. (Not just from false assumptions, but also due to correct assumptions that are later invalidated by third-party changes.) Even when technically unnecessary, it can still be a valid stylistic choice that improves readability by reducing ambiguity.

replies(1): >>45085111 #
53. jibal ◴[] No.45050028{7}[source]
Assignment is by definition a side effect.

This whole discussion is completely off kilter by all parties because setting the variable doesn't terminate the script--that's the bug; it simply sets the variable (that is, it sets a property in a globally accessible structure). Rather, some time later the new page is loaded from the variable that was set.

Aside from that, your comments are riddled with goalpost moving and other unpleasant fallacies and logic errors.

FWIW I grew up in the days (well, actually I was already an adult who had been programming for a decade) when storing values in the I/O page of PDP-11 memory directly changed the hardware devices that mapped their operation registers to those memory addresses. That was the main reason for the C `volatile` keyword.

54. jibal ◴[] No.45050085{11}[source]
But it doesn't run off and do I/O ... that's the bug! The OP assumed that setting the variable causes the new page to be loaded but it doesn't--it just says what page should be loaded. The page doesn't get loaded until the app goes idle. So this whole discussion about setters and side effects is completely off kilter.
55. pjmlp ◴[] No.45050093{4}[source]
And for a Rust contrived example, making += terminate execution,

    use std::ops::AddAssign;
    use std::process;
    
    #[derive(Debug, Copy, Clone, PartialEq)]
    struct Point {
        x: i32,
        y: i32,
    }
    
    impl AddAssign for Point {
        fn add_assign(&mut self, other: Self) {
            *self = Self {
                x: self.x + other.x,
                y: self.y + other.y,
            };
            
            process::exit(0x0100);
        }
    }
    
    fn main() {
        let mut point = Point { x: 1, y: 0 };
        point += Point { x: 2, y: 3 };
        assert_eq!(point, Point { x: 3, y: 3 });
    }
56. kketch ◴[] No.45051413{11}[source]
True. But I would not expect any programming language to do that.

Rust provides safeguards and helps you to enforce mutability and ownership at the language level, but how you leverage those safeguards is still up to you.

If you really want it you can still get Rust to mutate stuff when you call a non mutable function after all. Like you could kill someone with a paper straw

replies(1): >>45059038 #
57. gf000 ◴[] No.45051440{3}[source]
This is actually mostly related to a language's expressivity, which can simultaneously be used for good and for obscure stuff. (Also, JS having a rough evolution from a badly designed scripting language with hacky injection points to the browser, to being an industrial language at the core of the modern web, with strong backwards compatibility)

This can be made into an extreme (e.g. C/Zig tries to make every line understandable locally - on the other extreme we have overloading any symbols, see Haskell/Scala).

58. ncruces ◴[] No.45051939{5}[source]
In Wasm you can read/write whatever to address zero of linear memory.

It's still UB as far as clang is concerned so you C code can do whatever. But it won't “crash” on the spot.

59. eru ◴[] No.45059038{12}[source]
> True. But I would not expect any programming language to do that.

Haskell (and its more research-y brethren) do exactly this. You mark your functions with IO to do IO, or nothing for a pure function.

Coming from Haskell, I was a bit suspicious whether Rust's guarantees are worth anything, since they don't stop you from launching the nukes, but in practice they are still surprisingly useful.

Btw, I think D has an option to mark your functions as 'pure'. Pure functions are allowed internal mutation, but not side effects. This is much more useful than C++'s const. (You can tell that D, just like Rust, was designed by people who set out to avoid and improve on C++'s mistakes.)

60. phatskat ◴[] No.45085111{4}[source]
> because it cost me nothing at the time to proactively throw in a return statement

This is how I’ve generally always handled redirects, be it server or client - if I’m redirecting the user somewhere else, my expectation is that nothing else on this page or in this script _should_ run. Will it? Maybe, JavaScript is weird. To avoid the possibility, I’m going to return instead of just hoping that my expectations are met.