Wait. This doesn’t make sense to me. Statically typed programming languages cannot be deployed nor can they run with a type error that happens at runtime. Untyped languages CAN run and error out with a type error AT runtime. The inevitable consequence of that truth is this:
In the spectrum of runtime errors statically typed languages mathematically and logically HAVE less errors. That by itself is the definition of more reliable. This isn’t even a scientific thing related to falsifiability. This comes from pure mathematical logic. In science nothing can be proven, things can only be falsified. But in math and logic things can be proven and it is provable that static types are more reliable than untyped.
It is definitely not vibes and feels. Not all of banking uses statically typed languages but they are as a result living with a less reliable system then the alternative and that is a logical invariant.
There are many reasons why someone would choose untyped over typed but reliability is not a reason why they would do this unless they are ignorant.
> Statically typed programming languages cannot be deployed nor can they run with a type error that happens at runtime.
This is so completely untrue that I'm confused as to why anyone would try to claim it. Type Confusion is an entire class of error and CVE that happens in statically typed languages. Java type shenanigans are endless if you want some fun but baseline you can cast to arbitrary types at runtime and completely bypass all compile time checks.
I think the disagreement would come additionally by saying a language like Ruby doesn't actually have any type errors. Like how it can be said that GC languages can't have memory leaks. And that this model is stronger than just compile time checking. Sure you get a thing called TypeError in Ruby but because of the languages dynamism that's not an error the way it would be in C. You can just catch it and move. It doesn't invalidate the program's correctness. Ruby is so safe in it's execution model that Syntax Errors don't invalidate the running program's soundness.
> Java type shenanigans are endless if you want some fun but baseline you can cast to arbitrary types at runtime and completely bypass all compile time checks.
For this reason Java is a bad example of a typed language. It gives static typing a bad rep because of its inflexible yet unreliable type system (only basic type inference, no ADTs, many things like presence of equality not checked at compile time etc ) Something like ocaml or fsharp have much more sound and capable type systems.
Like other people replying to you C++ and Java gave types a bad rep by being so error prone and having a weak type system.
What I am saying is not untrue. It is definitive. Java just has a broken type system and it has warped your view. The article is more talking about type systems from functional programming languages where type errors are literally impossible.
You should check out elm. It’s one of the few languages (that is not a toy language and is deployed to production) where the type system is so strong that run time errors are impossible. You cannot crash an elm program because the type system doesn’t allow it. If you used that or Haskell for a while in a non trivial way it will give you deeper insight into why types matter.
> Ruby is so safe in it's execution model that Syntax Errors don't invalidate the running program's soundness.
This isn’t safety. Safety is when the program doesn’t even run or compile with a syntax error. Imagine if programs with syntax errors still tried their best effort to run… now you have a program with unknown behavior because who knows what that program did with the syntax error? Did it ignore it? Did it try to correct it? Now imagine that ruby program controlling a plane. That’s not safe.
> Wait. This doesn’t make sense to me. Statically typed programming languages cannot be deployed nor can they run with a type error that happens at runtime. Untyped languages CAN run and error out with a type error AT runtime. The inevitable consequence of that truth is this
There is nothing inevitable about the consequence you’re imagining because statically typed languages also reject correct programs.
It is 100 percent inevitable. Your reasoning here is illogical.
How does a statically typed language rejecting a correct program affect reliability? The two concepts are orthogonal. You’re talking about flexibility of a language but the topic is on reliability.
Let me be clear… as long as a language is Turing complete you can get it to accomplish virtually any task. In a statically typed language you have less ways to accomplish the same task then a dynamically typed language; but both languages can accomplish virtually any task. By logic a dynamically typed language is categorically more flexible than a static one but it is also categorically less reliable.
>How does a statically typed language rejecting a correct program affect reliability?
Because in some cases it will reject code that is simple and obviously correct, which will then need to be replaced by code that is less simple and less obviously correct (but which satisfies the type checker). I don't think this happens most of the time, but it does mean that static typing isn't a strict upgrade in terms of reliability. You are paying for the extra guarantees on the code you can write by giving up lots of correct programs that you could otherwise have written.
>I don't think this happens most of the time, but it does mean that static typing isn't a strict upgrade in terms of reliability.
It is a strict upgrade in reliability. You're arguing for other benefits here, like readability and simplicity. The metric on topic is reliability and NOT other things like simplicity, expressiveness or readability. Additionally, like you said, it doesn't happen "most" of the time, so even IF we included those metrics in the topic of conversation your argument is not practical.
>You are paying for the extra guarantees on the code you can write by giving up lots of correct programs that you could otherwise have written.
Again the payment is orthogonal to the benefit. The benefit is reliability. The payment is simplicity, flexibility, expressiveness, and readability. For me, personally, (and you as you've seem to indicate) programs actually become more readable and more simple when you add types. Expressiveness and flexibility is actually a foot gun, but that's not an argument I'm making as these are more opinions and therefore unprovable. You're free to feel differently.
My argument is that in the totality of possible errors, statically typed programs have provably LESS errors and thus are definitionally MORE reliable than untyped programs. I am saying that there is ZERO argument here, and that it is mathematical fact. No amount of side stepping out of the bounds of the metric "reliability" will change that.
Your definition of reliability seems different to how people use the word. I think most would consider a program that was statically checked, but often produces a wrong result as less reliable than a dynamically checked program that produces the right result.
>My argument is that in the totality of possible errors, statically typed programs have provably LESS errors and thus are definitionally MORE reliable than untyped programs. I am saying that there is ZERO argument here, and that it is mathematical fact. No amount of side stepping out of the bounds of the metric "reliability" will change that.
Making such broad statements about the real world with 100% confidence should already raise some eyebrows. Even through the lens of math and logic, it is unclear how to interpret your argument. Are you claiming that sum of all possible errors in all runnable programs in a statically checked language is less than sum of all possible errors in all runnable programs in an equivalent dynamically checked language? Both of those numbers are infinity, although i remember from school that some infinities are greater than others, I'm not sure how to prove that. And if such statement was true, how does it affect programs written in the real world?
Or is your claim that a randomly picked program from the set of all runnable statically checked programs is expected to have less errors than randomly picked program from the set of all runnable dynamically checked programs? Even this statement doesn't seem trivial, due to correct programs being rejected by type checker.
If your claim is about real world programs being written, you also have to consider that their distribution among the set of all runnable programs is not random. The amount of time, attention span and other resources is often limited. Consider the act of twisting an already correct program in various ways to satisfy the type checker, Consider the time lost that could be invested in further verifying the logic. The result will be much less clear cut, more probabilistic, more situation-dependent etc.
I think the disagreement here comes from overcomplicating what is actually a very simple claim.
I am not reasoning about infinities, cardinalities of infinite sets, or expectations over randomly sampled programs. None of that is needed. You do not need infinities to see that one set is smaller than another. You only need to show that one set contains everything the other does, plus more.
Forget “all possible programs” and forget randomness entirely. We only need to reason about possible runtime outcomes under identical conditions.
Take a language and hold everything constant except static type checking. Same runtime, same semantics, same memory model, same expressiveness. Now ask a very concrete question: what kinds of failures can occur at runtime?
In the dynamically typed variant, there exist programs that execute and then fail with a runtime type error. In the statically typed variant, those same programs are rejected before execution and therefore never produce that runtime failure. Meanwhile, any program that executes successfully in the statically typed variant also executes successfully in the dynamic one. Nothing new can fail in the static case with respect to type errors.
That is enough. No infinities are involved. No counting is required. If System A allows a category of runtime failure that System B forbids entirely, then the set of possible runtime failure states in B is strictly smaller than in A. This is simple containment logic, not higher math.
The “randomly picked program” framing is a red herring. It turns this into an empirical question about distributions, likelihoods, and developer behavior. But the claim is not about what is likely to happen in practice. It is about what can happen at all, given the language definition. The conclusion follows without measuring anything.
Similarly, arguments about time spent satisfying the type checker or opportunity cost shift the discussion to human workflow. Those may matter for productivity, but they are not properties of the language’s runtime behavior. Once you introduce them, you are no longer evaluating reliability under identical technical conditions.
On the definition of reliability: the specific word is not doing the work here. Once everything except typing is held constant, all other dimensions are equal by assumption. There is literally nothing else left to compare. What remains is exactly one difference: whether a class of runtime failures exists at all. At that point, reliability reduces to failure modes not by preference or definition games, but because there is no other remaining axis. I mean everything is the same! What else can you compare if not the type errors? Then ask the question which one is more reliable? Well… everything is the same except one has run time type errors, while the other doesn’t… which one would you call more “reliable”? The answer is obvious.
So the claim is not that statically typed languages produce correct programs or better engineers. The claim is much narrower and much stronger: holding everything else fixed, static typing removes a class of runtime failures that dynamic typing allows. That statement does not rely on infinities, randomness, or empirical observation. It follows directly from what static typing is.
Right and it’s completely off topic. This is a tangent you decided to turn the conversation toward. Tangents are fine, I’m just saying that you are wrong on both the main topic and the tangent, which is also fine.
The point is that a dynamic language will in some cases enable code that is simpler and more readable (and hence probably more reliable) because sometimes the simplest code is code that wouldn’t type check. Even if statically typed languages are more readable on average, this fact invalidates your claim that statically typed languages are strictly better in terms of reliability. This can only be true if you artificially restrict attention to the subset of programs in the dynamic language that could have been statically typed.
By the way, could you tone down the rhetoric a notch?
My claim is not invalid. It’s just being evaluated against a different question.
The original post says that claims like “static typing improves reliability” are unfalsifiable and therefore just vibes. That’s false, because the claim being made is not empirical to begin with. It’s a statement about language semantics.
Holding everything else constant, static typing eliminates a class of runtime failures by construction. Programs that would fail at runtime with type errors in a dynamic language are rejected before execution in a statically typed one. That is not a hypothesis about the real world, teams, or productivity. It’s a direct consequence of what static typing is. No evidence or falsification is required.
When you argue that dynamic languages can sometimes enable simpler or more readable code that may be more reliable in practice, you’ve changed the claim. That’s a discussion about human factors and development process. It may be true, but it does not invalidate the original claim, because it addresses a different level of analysis.
Additionally in practice it isn’t true. Your entire argument flips context and is trying to point out a niche corner case to show that my overall correct argument is not absolute. You’re already wrong practically, and you’re also wrong absolutely.
So the correct framing is:
- At the language level, static typing is strictly more reliable with respect to runtime type errors.
- At the human/process level, tradeoffs exist, and outcomes can vary.
Calling the first claim “invalid” only works if you silently replace it with the second. That’s the source of the disagreement.
You can't "hold everything else constant" because the set of programs that satisfy whatever type system is a proper subset of the set of valid programs.
The claim that admitting a larger set of programs improves reliability goes through several speculative steps: that the extra programs are correct, that they are simpler, that simplicity leads to fewer mistakes, and that those mistakes would not have been caught elsewhere. None of that follows from the language semantics. It’s a human-factor argument layered on top of assumptions.
By contrast, static typing removing a class of runtime failures is immediate and unconditional. Programs that would fail at runtime with type errors simply cannot execute. No assumptions about developer skill, code style, review quality, or time pressure are needed.
Even in practice, this is why dynamic languages tend to reintroduce types via linters, contracts, or optional typing systems. The extra expressiveness doesn’t translate into higher reliability; it increases the error surface and then has to be constrained again.
So the expressiveness argument doesn’t invalidate the claim. It changes the topic. One side is a direct property of the language. The other is a speculative, multi-step causal story about human behavior. That’s why the original claim is neither unfalsifiable nor “just vibes.”
So regardless of speculative human factors, the claim stands: holding the language semantics constant, static typing strictly reduces the set of possible runtime failures, and therefore strictly increases reliability in the only direct, non-contingent sense available.
Also Did you read what I wrote? I Covered your argument here DIRECTLY in my response. It's like you read the first sentence and then responded while ignoring the next paragraph.
This logic is both too broad and rigid to be of much practical use[1]. It needs to be tightened to compare languages that are identical except for static type checks, otherwise the statically typed language could admit other kinds of errors (memory errors immediately come to mind) that many dynamic languages do not have and you would need some way of weighing the relative cost to reliability of the different categories of errors.
Even if the two languages are identical except for the static types, then it is clearly possible to write programs that do not have any runtime type errors in the dynamic language (I'll leave it as an exercise to the reader to prove this but it is very clearly true) so there exist programs in any dynamic language that are equally reliable to their static counterpart.
[1] I also disagree with your definition of reliability but I'm granting it for the sake of discussion.
The claim was about reliability and lack of empirical evidence. Once framed that way, definitions matter. My argument is purely ceteris paribus: take a language, hold everything constant, and add strict static type checking. Once you do that, every other comparison disappears by definition. Same runtime, same semantics, same memory model, same expressiveness. The only remaining difference is the runtime error set.
Static typing rejects at compile time a strict subset of programs that would otherwise run and fail with runtime type errors. That is not an empirical claim; it follows directly from the definition of static typing. This is not hypothetical either. TypeScript vs JavaScript, or Python vs Python with a sound type checker, are real examples of exactly this transformation. The error profile is identical except the typed variant admits fewer runtime failures.
Pointing out that some dynamic programs have no runtime type errors does not contradict this. It only shows that individual programs can be equally reliable. The asymmetry is at the language level: it is impossible to deploy a program with runtime type errors in a sound statically typed language, while it is always possible in a dynamically typed one. That strictly reduces the space of possible runtime failures.
Redefining “reliability” does not change the result. Suppose reliability is expanded to include readability, maintainability, developer skill, team discipline, or development velocity. Those may matter in general, but they are not variables in this comparison. By construction, everything except typing is held constant. There is literally nothing else left to compare. All non-type-related factors are identical by assumption. What remains is exactly one difference: the presence or absence of runtime type errors. At that point, reliability reduces to failure count not as a philosophical choice, but because there is no other dimension remaining.
Between two otherwise identical systems, the one that can fail in fewer ways at runtime is more reliable. That conclusion is not empirical, sociological, or debatable. It follows directly from the setup.
I don't consider a human subjects study to be "hard evidence".
So, we can safely disregard these papers. They got exactly the result that they sought out to get, and the papers were published because they confirmed the preexisting groupthink.
You mean psychology? There’s no hard evidence there. The papers you’re citing are using human subjects in that sort of way. It’s pseudoscience at best
Medicine that involves testing human subject response to treatments is very different from the papers you’re citing and does involve falsifiable theses (usually, definitely not always).
I didn't link any studies. I'm not the person you originally replied to. I was trying to engage in your point that studies involving human subjects cannot contain hard evidence. And no I wasn't referring to psychology in my comment.
The first paper shown above was presented at ICPC, which has a history of bias in reviewing, and typically only assigns one or two reviewers in any case.
Which makes it not surprising that the paper itself doesn't really prove anything, except that the authors themselves are good at creating dissimilar situations.
Subjects had to modify existing systems, which were provided by the experimenters.
The experimenters deliberately removed any semblance of anything that might hint at types (comments, variable names, etc.) and did who the fuck knows what else to the dynamically typed code to make it difficult to work with.
They also provided their own IDE and full environment which the participants had to use.
Now, of course, we've all seen the graphs which show that for simple problems, dynamic is better, and there's a crossover point, and, of course Cooley's experiment is on the far left of that graph, so it certainly doesn't prove that strict static typing isn't better for large programs, but it's at least honest in its approach and results, using self-selected working practitioners (and there was never any shortage of working practioners swearing by how much better VHDL is).
Functional programming makes DSLs easier/possible, so you express your domain in natural domain language/constructs leading to easier comprehension, standardization, reuse, and testing, thereby improving reliability. This is the exact opposite of write-only code, DSLs are comprehensible/editable to non-programmers. With a strong enough type system these benefits accrue while ensuring the program in stays a subset of more correct programs than allowed by other compilers.
… I mean, if we’re just making global assertions :)
Gimme the “write only” compiler verified exhaustively pattern matched non-mutable clump in a language I can mould freely any day of the week. I will provide the necessary semantic layer for progress backed with compiler guarantees. That same poop pile in a lesser language may be unrecoverable.
> ... functional programming and static typing make things more reliable.
> But this isn't a falsifiable claim.
Saying "this isn't falsifiable" is a wild claim. Indeed the claim "functional programming and static typing make things more reliable" is falsifiable, as long as you admit a statistical understanding. The world is messy and experiments have noise, so what would you use if not statistics? Anecdotes?: no. Purely deductive methods?: no; we should not expect any single technique to be a silver bullet.
Good studies and analyses lay out a causal model and use strong methodologies for showing that some factor has a meaningful impact on some metric of interest. I recommend this as a starting point [1]
It could mean different things I guess, but here’s my take:
If you do very risky R&D in a big corpo then the risk creeps into other things: other projects might look at the R&D and say, “we will just use that when it’s done”. It’s a lazy kind of move for tech leaders to make, because it makes you look like a team player, and if the R&D goes belly up then you have someone else to blame. This ultimately leads to risky R&D in a big corpo not being compartmentalized as much as it should be. If it fails, it fails super hard with lots of fallout.
But risky R&D at a startup is compartmentalized. Nobody will say they use the output of the startup until there are signs of life, and even then healthy caution is applied.
C and C++ as defined by their current standards are memory unsafe. You may argue that some specific implementations manage to stay as memory safe as they can get away with, but even then, features like union prevents a fully memory-safe implementation.
> C and C++ as defined by their current standards are memory unsafe.
I don’t think the spec says one way or another (but please correct me if you find verbiage indicating that the language must be memory unsafe).
It’s possible to make the whole language memory safe, including unions. It’s tricky, but possible.
Someone else mentioned Fil-C but Fil-C builds on a lot of prior art. The fact that C and C++ can be memory safe is no secret to those who understand language implementation.
By definition, C and C++ are memory safe as long as you follow the rules. The problem is that the rules cannot be automatically checked and in practice are the source of unenumerable issues from straight up bugs to subtle standards violations that trigger the optimizer to rewrite your code into what you didn’t intend.
But yes, fil-c is a huge improvement (afaik though it doesn’t solve the UB problem - it just guarantees you can’t have a memory safety issue as a result)
> By definition, C and C++ are memory safe as long as you follow the rules.
This statement doesn't make sense to me.
Memory safety is a property of language implementations, which is all about what happens when the programmer does not follow the rules.
> The problem is that the rules cannot be automatically checked and in practice are the source of unenumerable issues from straight up bugs to subtle standards violations that trigger the optimizer to rewrite your code into what you didn’t intend.
They can be automatically checked and Fil-C proves this. The prior art had already proved it before Fil-C existed.
> But yes, fil-c is a huge improvement (afaik though it doesn’t solve the UB problem - it just guarantees you can’t have a memory safety issue as a result)
Fil-C doesn't have UB. If you find anything that looks like UB to you, please file a GH issue.
Let's also be clear that you're referring to nasal demons specifically, not UB generally. In some contexts, like CPU ISAs, UB means a trap, rather than nasal demons. So let's use the term "nasal demons".
C and C++ only have nasal demons because:
- Policy decisions. For example, making signed integer addition have nasal demons is because someone wanted to cook a benchmark.
- Lack of memory safety in most implementations, combined with a refusal to acknowledge what happens when the wrong kind of memory access occurs. (Note that CPU ISAs like x86 and ARM are not memory safe, but have no nasal demons, because they do define what happens when any kind of memory access occurs.)
So anyway, Fil-C has no nasal demons, because:
- I turned off all of those silly policy decisions for cooking benchmarks.
- The memory safety means that I define what happens when the wrong kind of memory access occurs: the program gets killed with a panic.
First, let me say that I really respect the work you’re doing in fil-c. Nothing I say is intended as a knock and you’re doing fantastic engineering work moving the field forward and I hope you find success.
That’s good to know about nasal demons. Are you saying you somehow inhibit the optimizer from injecting a security vulnerability due to UB ala https://www.cve.org/CVERecord?id=CVE-2009-1897 ? I’m kinda curious how you trick LLVM into not optimizing through UB since it’s UB model is so tuned to the C/C++ standard.
Anyway, Fil-C is only currently working on (a lot of, but not all yet I think right?) Linux userspace while C and C++ as a standard language definition span a lot more environments. I agree the website should call out Fil-C as memory safe but I think it’s also fair to say that Fil-C is more an independent dialect of C/C++ (eg you do have to patch some existing software) - IMHO it’s too confusing for communicating out to say that C/C++ is memory safe and I’d rather it say something like Fil-C is memory safe or C/C++ code running under Fil-C is memory safe.
> Memory safety is a property of language implementations, which is all about what happens when the programmer does not follow the rules.
By this argument no language is memory safe because every language has bugs that can result in memory safety issues. Certainly rustc definitely has soundness issues that haven’t been fixed and I believe this is also true of Python, JavaScript, etc but I think it’s an unhelpful bar or framing of the problem. The language itself is memory safe and any safety issues within the language spec or implementation are a bug to be fixed. That isn’t true of C/C++ where there’s going to always exist environments where it’s impossible to even have a memory safe implementation (eg microcontrollers) let alone mandate one in the spec. And also fil-C does have a performance impact so some software may not ever be a good fit for it (eg video encoders/decoders). For example, a non memory safe conforming implementation of JavaScript is not possible. Same goes for safe rust, Python or Java. By comparison that isn’t true for c/c++.
At a certain point, it's a trade-off. A systems language will offer facilities that can be used to break encapsulation and abstractions, and access memory as a sequences of bytes. (Anything capable of file I/O on stock Linux can write to /proc/self/mem, for example.) The difference to (typical) C and C++ is that these facilities are less likely to be invoked by accident.
Reasonable people will disagree about what memory safety (and type safety) mean to them. Personally, bounds checking for arrays and strings, some solution for safe deallocation of memory, and an obviously correct way to write manual bounds checks is more interesting than (for example) no access to machine addresses and no FFI.
Regarding bounds checking, GNAT offers some interesting (non-standard) options: https://gcc.gnu.org/onlinedocs/gnat_ugn/Management-of-Overfl...
Basically, you can write a bounds check in the most natural way, and the compiler will evaluate the check with infinite precision (or almost, to improve performance). In standard, you might end up with an exception in some corner cases where the check should pass. I wish more languages would offer something like this. Among widely used languages, only Python offers this capability because it uses infinite-precision integers.
> Are you saying you somehow inhibit the optimizer from injecting a security vulnerability due to UB ala https://www.cve.org/CVERecord?id=CVE-2009-1897 ? I’m kinda curious how you trick LLVM into not optimizing through UB since it’s UB model is so tuned to the C/C++ standard.
Yes that is inhibited. There’s no trick. LLVM (and other compilers) choose to do those stupid things by policy, and the policy can be turned off. It’s not even hard to do it.
> Fil-C is more an independent dialect of C/C++ (eg you do have to patch some existing software)
Fil-C is not a dialect. The patches are similar to what you’d have to do if you were porting a C program to a new CPU architecture or a different compiler.
> By this argument no language is memory safe because every language has bugs that can result in memory safety issues.
You rebutted this argument for me:
> any safety issues within the language spec or implementation are a bug to be fixed
Exactly this. A memory safe language implementation treats outstanding memory safety issues as a bug to be fixed.
This is what makes almost all JS implementations, and Fil-C, memory safe.
The standard(s) very often say that a certain piece of C code has undefined behavior. Having UB means that there is behavior that is not necessarily explainable by the standard. This includes e.g. the programming seemingly continuing just fine, the program crashing, or arbitrary code running as part of an exploited stack buffer overflow.
Now, certain implementations of C might give your more guarantees for some (or all) of the behavior that the standard says is undefined. Fil-C is an example of an implementation taking this to the extreme. But it's not what is meant when one just says "C." Otherwise I would be able to compile my C code with any of my standard-compliant compilers and get a memory-safe executable, which is definitely not the case.
My meager understanding of unions is that they allow data of different types to be overlayed in the same area of memory, with the typical use case being for data structures that may contain different types of data (and the union typically being embedded in a struct that identifies the data type). This certainly presents problems with the interpretation of data stored in the union, but it also strikes me that the union object would have a clearly defined sized and the compiler would be able to flag any memory accesses outside of the bounds of the union. While this is clearly problematic, especially if at least one of the elements is a pointer, it also seems like the sort of problem that a compiler can catch (which is the benefit of Rust on this front).
Please correct me if I'm wrong. This sort of software development is a hobby for me (anything that I do for work is done in languages like Python).
A trivial example of this would be a tagged union that represents variants with control structures of different sizes; if the attacker can induce a confusion between the tag and the union member at runtime, they can (typically) perform a controlled read of memory outside of the intended range.
Rust avoids this by having sum types, as well as preventing the user from constructing a tag that’s inconsistent with the union member. So it’s not that a union is inherent unsafe, but that the language’s design needs to control the construction and invariants of a union.
The standard does not assign meaning to this sequence of execution, so an implementation can detect this and abort. This is not just hypothetical: existing implementations with pointer capabilities (Fil-C, CHERI targets, possibly even compilers for IBM i) already do this. Of course, such C implementations are not widely used.
The union example is not particularly problematic in this regard. Much more challenging is pointer arithmetic through uintptr_t because it's quite common. It's probably still solvable, but at a certain point, changes the sources becomes easier, even at at scale (say if something uses the %p format specifier with sprintf/sscanf).
> The standard does not assign meaning to this sequence of execution, so an implementation can detect this and abort.
Real C programs use these kinds of unions and real C compilers ascribe bitcast semantics to this union. LLVM has a lot of heavy machinery to make sure that the programmer gets exactly what then expected here.
The spec is brain damage. You should ignore it if you want to be able to reason about C.
> This is not just hypothetical: existing implementations with pointer capabilities (Fil-C, CHERI targets, possibly even compilers for IBM i) already do this
Fil-C does not abort when you use this union. You get memory safe semantics:
- you can use `i` to change the pointer’s intval. But the capability can’t be changed that way. So if you make a mistake you’ll end up with an OOB pointer.
- you can use `i` to read the pointer’s current intval just as if you had done an ptrtoint cast.
I think CHERI also does not abort on the union itself. I think storing to `i` removes the capability bit so `p` crashes on deref.
> The union example is not particularly problematic in this regard. Much more challenging is pointer arithmetic through uintptr_t because it's quite common.
The union problem is one of the reasons why C is not memory safe, because C compilers give unions the expected structured assembly semantics, not whatever nonsense is in the spec.
This engine restricts JS in all of the ways I wished I could restrict the language back when I was working on JSC.
You can’t restrict JS that way on the web because of compatibility. But I totally buy that restricting it this way for embedded systems will result in something that sparks joy
I bet MQJS will also be very popular. Quite impressive that bro is going to have two JS engines to brag about in addition to a lot of other very useful things!
Yes, quite! Monsieur Bellard is a legend of computer programming. It would be hard to think of another programmer whose body of public work is more impressive than FB.
Unfortunate that he doesn't seem to write publicly about how he thinks about software. I've never seen him as a guest on any podcast either.
I have long wondered who the "Charlie Gordon" who seems to collaborate with him on everything is. Googling the name brings up a young ballet dancer from England, but I doubt that's the person in question.
> It would be hard to think of another programmer whose body of public work is more impressive than FB.
I am of the firm belief that "Monsieur Fabrice Bellard" is not one person but a group of programmers writing under this nom de plume like "Nicolas Bourbaki" was in Mathematics ;-)
I don't know of any other programmer who has similar breadth and depth in so many varied domains. Just look at his website - https://bellard.org/ and https://en.wikipedia.org/wiki/Fabrice_Bellard No self-aggrandizing stuff etc. but only tech. He is an ideal for all of us to strive for.
Watson's comment on how Sherlock Holmes made him feel can be rephrased in this context as;
"I trust that I am not more dense than my neighbours [i.e. fellow programmers], but I was [and am] always oppressed with a sense of my own stupidity in my dealings with [the works of Fabrice Bellard]."
Since you’re on the topic, what ever happened to the multi threading stuff you were doing on JSC? Did it stop when you left Apple? Is the code still in JSC or did it get taken out?
They are not interchangeable. The semantics are observably different. Therefore, RC is not GC.
Reference counting gives you eager destruction. GC cannot.
GC gives lets you have garbage cycles. RC does not.
I think a part of the GC crew reclassified RC as GC to try to gain relevance with industry types during a time when GC was not used in serious software but RC was.
But this is brain damage. You can’t take a RC C++ codebase and replace the RC with GC and expect stuff to work. You can’t take a GC’d language impl and replace the GC with RC and expect it to work. Best you could do is use RC in addition to GC so you still keep the GC semantics.
> GC gives lets you have garbage cycles. RC does not.
This is the biggest difference, but if you disallow cycles then they come close. For example, the jq programming language disallows cycles, therefore you could implement it with RC or GC and there would be no observable difference except "eager destruction", but since you could schedule destruction to avoid long pauses when destroying large object piles, even that need not be a difference. But of course this is a trick: disallowing cycles is not a generic solution.
> Reference counting gives you eager destruction. GC cannot.
Tracing GC can't. Reference counting, which is by definition a GC can. It's like insects vs bugs.
And destructors are a specific language feature. No one says that they are a must have and if you don't have them then you can replace an RC with a tracing GC. Not that it matters, a ladybug is not the same as an ant, but they are both insects.
The best part of these conversations is that if I say “garbage collection”, you have zero doubt that I am in fact referring to what you call “tracing garbage collection”.
You are defining reference counting as being a kind of garbage collection, but you can’t point to why you are doing it.
I can point to why that definition is misleading.
Reference counting as most of the industry understands it is based on destructors. The semantics are:
- References hold a +1 on the object they point to.
- Objects that reach 0 are destructed.
- Destruction deletes the references, which then causes them to deref the pointed at object.
This is a deterministic semantics and folks who use RC rely on it.
This is nothing like garbage collection, which just gives you an allocation function and promises you that you don’t have to worry about freeing.
They are different approaches for the same thing: automatic memory management. (Which is itself a not trivial to define concept)
One tracks liveness, while the other tracks "deadness", but as you can surely imagine on a graph of black and white nodes, collecting the whites and removing all the others vs one by one removing the black ones are quite similar approaches, aren't they?
You’re not going to convince me by citing that paper, as it’s controversial in GC circles. It’s more of a spicy opinion piece than a true story.
I agree that RC and GC are both kinds of automatic memory management.
RC’s semantics aren’t about tracking deadness. That’s the disconnect. In practice, when someone says, “I’m using RC”, they mean that they have destructors invoked on count reaching zero, which then may or may not cause other counts to reach zero. If you squint, this does look like a trace - but by that logic everyone writing recursive traversals of data structures is writing a garbage collector
A RC algorithm implementation using a cycle collector, or deferred deletion on a background thread, to reduce stop the world cascade deletion impact, is....
reply