Expurple 12 hours ago

> If someone posts a patch or submits a PR to a codebase written in C, it is easier to review than any other mainstream language. There is no spooky at a distance. [..] Changes are local.

Lol, wut? What about about race conditions, null pointers indirectly propagated into functions that don't expect null, aliased pointers indirectly propagated into `restrict` functions, and the other non-local UB causes? Sadly, C's explicit control flow isn't enough to actually enable local reasoning in the way that Rust (and some other functional languages) do.

I agree that Go is decent at this. But it's still not perfect, due to "downcast from interface{}", implicit nullability, and similar fragile runtime business.

I largely agree with the rest of the post! Although Rust enables better local reasoning, it definitely has more complexity and a steeper learning curve. I don't need its manual memory management most of the time, either.

Related post about a "higer-level Rust" with less memory management: https://without.boats/blog/notes-on-a-smaller-rust/

  • amluto an hour ago

    Even ignoring undefined behavior and nulls, everything in C is mutable. Action at a distance is basically the norm.

    • BiteCode_dev 10 minutes ago

      And global states.

      I remember the first time I was using gettext and wonder "wait, why do I have to switch the language for my whole program if I need it for just this request?" and realized that's because GNU gettext was made like that.

  • whytevuhuni an hour ago

    If anything I'd like an even lower-level Rust.

    There's so many good high-level languages to choose from, but when you need to go low-level, there's essentially only C, C++, Rust. Maybe Zig once it reaches 1.0.

    What we need isn't Rust without the borrow checker. It's C with a borrow checker, and without all the usual footguns.

    • Expurple 35 minutes ago

      > There's so many good high-level languages to choose from

      We have many popular high-level languages, but I disagree that they are good. Most of them are fragile piles of crap unsuitable for writing anything larger than a throwaway script.

      (In my subjective and biased assessment, which is hovewer based on professional experience)

    • rahkiin an hour ago

      It would be an interesting excercise just to see how such a language would look.

      Would modules be needed or can preprocessing work still. How more advanced will the type system need to be. And how will pointers chanhe to fix all footguns and allow static borrow checking.

      • lelanthran 43 minutes ago

        > It would be an interesting excercise just to see how such a language would look.

        I started designing one (C-with-borrow-checker) way back in 2018; never got around to finishing the design or making a MVP, but I believe you can solve maybe 90% of memory problems (use after free/double free, data racing, leaking, etc) with nothing more than some additional syntax[1] to type declarations and support for only fat arrays.

        IIRC, I thought that the above (coupled with an escape hatch for self-referential and/or unsafe values) was all that was needed to prevent 90% of memory errors.

        Somewhere along the way scope-creep (objects would be nice, can't leave out generics, operator overloads necessary for expressing vector math, etc) turned what would have been a small project into a very large one.

        -------------------------------

        [1] By additional syntax, I mean using `&` and `&&` in type declarations as a qualifier similar to how `static`, `const`, `volatile`, etc qualifiers are used.

  • shmerl 3 hours ago

    Yeah, and C can get unreadable fast. Obfuscated C contest is a great example: https://www.ioccc.org

    But that doesn't mean it's a good idea to use such style for PRs, lol.

    • Expurple an hour ago

      That's a really weird example to give. You can write unreadable code in any language if you're determined enough

      • shmerl an hour ago

        Well, C has such contest, not every language on the other hand.

        • staunton an hour ago

          I'd guess that's mostly because C has been around long enough to be chosen for it and people who are into that kind of thing don't urgently need more than one contest, regardless of language...

    • peppingdore an hour ago

      Rust is unreadable by default

      • shmerl an hour ago

        Syntax could be less terse and abbreviated, I agree. But it's not unreadable. On the other hand some complain that Rust sometimes is too verbose, so I guess it's some balance.

uecker 13 hours ago

It is a bit unclear to me why somebody who rejects C++ because "I once spent an entire year in the heaven of C++, walking around in a glorious daze of std::vector and RAII, before one day snapping out of it and realizing that I was just spawning complexity that is unrelated to the problem at hand." (which I can absolutely agree with!) is picking Rust from all options. If there is a language that can rival C++ in terms of complexity, it is Rust.

  • tialaramex 12 hours ago

    I've seen this idea from a few people and I don't get it at all.

    Rust is certainly not the simplest language you'll run into, but C++ is incredibly baroque, they're not really comparable on this axis.

    One difference which is already important and I think will grow only more important over time is that Rust's Editions give it permission to go back and fix things, so it does - where in C++ it's like venturing into a hoarder's home when you trip over things which are abandoned in favour of a newer shinier alternative.

    • rich_sasha 12 hours ago

      C++'s complexity is coming from how eager it is to let you shoot yourself in the foot. Rust will make you sweat blood to prove the bird in the sky you're shooting at is really not your own foot.

      • bcrl 6 hours ago

        And the gun isn't a gun, it's just a bunch of rope that you somehow got tangled into an explosive with a detonator you can't quite seem to locate.

      • WD-42 2 hours ago

        You sure it doesn’t come from, for example, needing a macro just to get the length of an array?

        • saghm an hour ago

          Yes, I think it's pretty easy to be sure that this isn't where the complexity comes from when there's literally a method to do that on arrays. I'm not even sure which language you're talking about here, because you can call `.len()` in Rust or `.size()` in C++.

          If you're trying to remember what the language is where there's no immediately obvious straightforward way to get the length of an array, it's not Rust or C++; you must have been thinking of C.

          • tialaramex an hour ago

            This is a case of exactly the "hoarder" modality in C++ that I described. When somebody says array in C++ you think of std::array, the newer shiny C++ class which you're asked to use instead of the native array types. The native array types in C++ still exist and indeed do not provide methods like size()

            In Rust the arrays aren't stunted left over primitive types which weren't gifted modern features, array.len() works because all Rust's types get features like this, not just the latest and greatest stuff.

      • eldenring 8 hours ago

        It's really not that bad.

    • viccis 3 hours ago

      >where in C++ it's like venturing into a hoarder's home when you trip over things which are abandoned in favour of a newer shinier alternative

      Perl is so bad about this that I once worked on a very old codebase in which I could tell approximately when it was written based on which features were being used.

    • adastra22 2 hours ago

      C++ has editions too btw. C++11, C++14, C++17, etc. These are opt in and allowed to break compatibility, although that is very rarely done in practice.

      • Expurple an hour ago

        > although that is very rarely done in practice.

        That's one difference. And the other important differences are:

        - Rust apps can depend on library "headers" written in other editions. That's the whole deal with editions! Breaking changes are local to your own code and don't fracture the ecosystem.

        - Rust has a built-in tool that automatically migrates your code to the next edition while preserving its behavior. In C++, upgrading to the next standard is left as an exercise for the reader (just like everything else). And that's why it's done so rarely and so slowly.

      • tialaramex an hour ago

        C++ shipping new and slightly incompatible versions of the entire language every three years isn't Editions, there was a proposal to attempt Editions (under the name "Epochs") for C++ but it faced significant headwinds and was abandoned.

  • bryanlarsen 8 hours ago

    Reading between the lines, the author is a Haskell fan. Haskell is another "complicated" language, but the complexity feels much different than the C++ complexity. Perhaps I would describe it as "complexity that improves expressiveness". If you like Haskell for its expressiveness but dislike C++ for it's complexity, I suspect Rust is a language you're going to like.

    • fn-mote 2 hours ago

      My impression was the opposite. I wondered how well the author knew Haskell. They mention:

      (1) The "intimidating syntax". Hey, you do not even need to be using <$> never mind the rest of those operators. Perl and Haskell can be baroque, but stay away from that part of the language until it is useful.

      (2) "Changes are not localized". I'm not sure what this means. Haskell's use of functions is very similar to other languages. I would instead suggest referring to the difficulty of predicting the (time|space) complexity due to the default lazy evaluation.

      FTA:

      > In contrast, Haskell is not a simple language. The non-simplicity is at play both in the language itself, as evidenced by its intimidating syntax, but also in the source code artifacts written in it. Changes are not localized, the entire Haskell program is one whole — a giant equation that will spit out the answer you want, unlike a C program which is asked to plod there step by step.

      Edited to make the critique more objective.

  • dijit 13 hours ago

    You’re right that Rust is a ball of complication.

    I am a fan of Rust but it’s definitely a terse language.

    However there are definitely signs that they have thought about making it as readable as possible (by omitting implicit things unless they’re overwritten, like lifetimes).

    I’m reminded also about a passage in a programming book I once read about “the right level of abstraction”. The best level of abstraction is the one that cuts to the meat of your problem the quickest - spending a significant amount of time rebuilding the same abstractions over and over (which, is unfortunately often the case in C/C++) is not actually more simple, even if the language specifications themselves are simpler.

    C codebases in particular, to me, are nearly inscrutable unless I spend a good amount of time unpicking the layers of abstractions that people need to write to make something functional.

    I still agree that Rust is a complex language, but I think that largely just means it’s frontloading. a lot of the understanding about certain abstractions.

    • uecker 12 hours ago

      I do not really agree with respect to C. I often have to deal with C code written by unexperienced programmers. It is always relatively easy to refactor it step by step. For C++ this is much more painful because all the tools invented to make the code look concise make it extremely hard to follow and change. From the feature set, I would say that Rust is the same (but I have no experience refactoring Rust code).

      • bryanlarsen 8 hours ago

        > but I have no experience refactoring Rust code

        But you're willing to write many comments complaining that Rust is hard to refactor. Rust is the easiest language to refactor with I've ever worked in, and I've used a couple dozen or so. When you want to change something, you change it, and then fix compiler errors until it stops complaining. Then you run it, and it works the first time you run it. It's an incredible experience to worry so little about unknown side-effects.

        • adastra22 2 hours ago

          Also, although it was not designed with this in mind, the described process adopts VERY well to LLMs. You can make a refactoring change, then tell the LLM to "run cargo check and fix the errors." And it does a very good job of doing this.

        • uecker 2 hours ago

          No, I wrote that Rust is too complex. And I responded to a comment that claims that C is hard to refactor, and this does not match my experience.

        • Dylan16807 6 hours ago

          > But you're willing to write many comments complaining that Rust is hard to refactor.

          Their refactoring comments look focused on C versus C++ to me, with a bit of guessing Rust is like C++ in a way that is clearly labeled as speculation.

          So I don't see the problem with anything they said about refactoring.

        • bsder 6 hours ago

          > Rust is the easiest language to refactor with I've ever worked in

          Oh? So how do you refactor a closure into a named function in Rust?

          I have found this to be of the most common failure modes that makes people want to punch the monitor.

          (Context: in almost all programming languages, a closure is practically equivalent to an unnamed function--refactoring a closure to a named function tends to be pretty straightforward. This isn't true for Rust--closures pick up variable lifetime information that can be excruciatingly difficult to unwind to a named function.)

          • bryanlarsen 6 hours ago

            Set the type of all the parameters to your new function to bool, then compile. The compiler error will tell you, "Error: you passed a foo<'bar, baz> instead of a bool". Then you change the type in the function's parameter to "foo<'bar, baz>".

          • afdbcreid 2 hours ago

            You invoke the assist of rust-analyzer that does that automatically.

      • AstralStorm 12 hours ago

        In personal experience, the much stronger and composite type model in Rust makes it easier to refactor.

        Adding features in particular is a breeze and automatically the compiler/language will track for you the places that use only old set of traits.

        Tooling is still newer though and needs polish. Generic handling is interesting at times and there are related missing features for that in the language, vis a vis specializations in particular.

        Basic concurrency handling is also quite different in Rust than other languages, but thus usually safer.

        • uecker 11 hours ago

          I am wondering about this. In C the nice thing is that you usually change things locally. A line of code does not depend on a million other things. I can't quite see how this works in Rust... At least in C++, template, overloading, etc. can make a single line depend on a lot of things.

          • rstuart4133 9 hours ago

            > I can't quite see how this works in Rust...

            You won't understand it unless refactor some Rust programs.

            Bunny summed it up rather well. He said in most languages, when pull on some thread, you end disappearing into a knot and you're changes are just creating a bigger knot. In Rust, when pull on a thread, the language tells you where it leads. Creating a bigger knot generally leads to compile errors.

            Actually he didn't say that, but I can't find the quote. I hope it was something like that. Nonetheless he was 100% spot on. That complexity you bemoan about the language is certainly there - but it's different to what you have experienced before.

            In most languages, complex features tend lead to complex code. That's what made me give up on Python in the end. When you start learning Python, it seems a delightfully simple yet powerful language. But then you discover metaclasses, monkey patching, and decorators, which all seem like powerful and useful tools, and you use them to do cool things. I twisted Python's syntax into grammar productions, so you could write normal looking python code that got turned into an LR(1) parser for example. Then you discover other peoples code that uses those features to produce some other cute syntax, and it has a bug, and when you look closely your brain explodes.

            As you say C doesn't have that problem, because it's such a simple language. C++ does have that problem, because it's a very complex language. I'm guessing you are making deduction from those two examples that complex languages lead to hard to understand code. But Rust is the counter example. Rust's complexity is forces you to write simple code. Turns out it's the complexity of the code that matters, not the complexity of the language.

            • uecker an hour ago

              I only have limited experience with Rust (only playing around a bit). But it seems the language forces you to structure the code in a specific way (and you seem to agree). This is good, because it prevents you from making a mess (at some level at least). But what others report (and it matches my limited experience) is that it makes the structure of the code very rigid. So I do not quite see how this does not limit refactoring? What some of you describe in this thread is type-directed refactoring (i.e. you change a type and the compiler tells what you need to change), but is this not limited to relatively basic changes?

              In C, you can make partial changes and accept a temporary inconsistency. This gives you a lot of flexibility that I find helpful.

              • Expurple 26 minutes ago

                I'll put it like this. Rust causes you to refactor more often (because the interfaces are so rigid), but makes it easy to do a full (and correct) refactoring very quickly. Think of it as docs being part of the interface and the compiler forcing you to always update the docs. That's an amazing property

          • Expurple 11 hours ago

            > A line of code does not depend on a million other things.

            But it does! To qoute my top-level comment:

            > What about about race conditions, null pointers indirectly propagated into functions that don't expect null, aliased pointers indirectly propagated into `restrict` functions, and the other non-local UB causes?

            In other words: you set some pointer to NULL, this is OK in that part of your program, but then the value travels across layers, you've skipped a NULL check somewhere in one of those layers, NULL crosses that boundary and causes UB in a function that doesn't expect NULL. And then that UB itself also manifests in weird non-local effects!

            Rust fixes this by making nullability (and many other things, such as thread-safety) an explicit type property that's visible and force-checked on every layer.

            Although, I agree that things like macros and trait resolution ("overloading") can be sometimes hard to reason about. But this is offset by the fact that they are still deterministic and knowable (albeit complex)

            • uecker 11 hours ago

              This is true to some degree, but not really that much in practice. When refactoring you just add assertions for NULL (and the effect of derefencing a NULL in practice is a trap - that it UB in the spec is completely irrelevant. in fact it helps because it allows compilers to turn it a trap without requiring it on weak platforms). Restrict is certainly dangerous, but also rarely used and a clear warning sign, compare it to "unsafe". The practical issues in C are bounds checking and use-after-free. Bounds checking I usually refactor into safe code quickly. Use-after-free are the one area where Rust has a clear advantage.

              • anon-3988 2 hours ago

                > This is true to some degree, but not really that much in practice. When refactoring you just add assertions for NULL (and the effect of derefencing a NULL in practice is a trap - that it UB in the spec is completely irrelevant.

                This happens a lot in discussion about programming complexity. What you are doing is changing the original problem to a much simpler one.

                Consider a parsing function parse(string) -> Option<Object>

                This is the original problem, "Write a parsing function that may or may not return Object"

                What a lot of people do is they sidetrack this problem and solve a much "simpler problem". They instead write parse(string) -> Object

                Which "appears" to be simpler but when you probe further, they handwave the "Option" part to just, "well it just crashes and die".

                This is the same problem with exceptions, a function "appears" to be simple: parse(string) -> Object but you don't see the myriads of exceptions that will get thrown by the function.

                • uecker 2 hours ago

                  I guess it depends on what the problem is. If the problem is being productive and being able to program without pain "changing the original problem to a much simpler one" is a very good thing. And "crash and die" can be a completely acceptable way to deal with it. Rust's "let it panic" is not at all different in this respect.

                  But in the end, you can write Option just fine it C. I agree though that C sometimes can not express things perfectly in the type system. But I do not agree that this is crucial for solving these problems. And then, also Rust can not express everything in the type system. (And finally, there are things C can express but Rust can't).

                  • anon-3988 an hour ago

                    Yes, all I am saying is that we have to be honest what level of problems we are solving when we encounter a complicated solution.

                    The solution have to scale linearly with the problem at hand, that is what it means to have a good solution.

                    I agree with the article that Rust is overkill for most use cases. For most projects, just use a GC and be done with it.

                    > But I do not agree that this is crucial for solving these problems. And then, also Rust can not express everything in the type system.

                    This can be taken as a feature. For example, is there a good reason this is representable?

                    struct S s = 10;

                    I LOVE the fact that Rust does not let me get away with half-ass things. Of course, this is just a preference. Half of my coding time is writing one-off Python scripts for analysis and data processing, I would not want to write those in Rust.

                    > But in the end, you can write Option just fine it C.

                    Even this question have a deeper question underneath it. What do you mean by "just fine"? Because to me, tagged enums or NULL is NOT the same thing as algebraic data types.

                    This is like saying floating points are just fine for me for integer calculations. Maybe, maybe for you its fine to use floating points to calculate pointers, but for others it is not.

                  • Expurple an hour ago

                    > in the end, you can write Option just fine it C.

                    No, you can't. In the sense that the compiler doesn't have exhaustiveness checks and can't stop you from accessing the wrong variant of a union. An Option in C would be the same as manually written documentation that doesn't guarantee anything.

                    std::optional in C++ is the same too. Used operator* or operator-> on a null value? Too bad, instant UB for you. It's laughably bad, given that C++ has tools to express tagged unions in a more reliable way.

                    > And then, also Rust can not express everything in the type system. (And finally, there are things C can express but Rust can't).

                    That's true, but nobody claims otherwise. It's just that, in practice, checked tagged unions are a single simple feature that allows you to express most things that you care about. There's no excuse for not having those in a modern language.

                    And part of the problem is that tagged unions are very hard to retrofit into legacy languages that have null, exceptions, uninitialized memory, and so on. And wouldn't provide the full benefit even if they could be retrofitted without changing these things.

              • Expurple 10 hours ago

                I agree that in practice NULL checking is one of the easier problems. I used it because it's the most obvious and easy to understand. I can't claim which kinds of unsoundness in C code are more common and problematic in practice. But that's not even interesting, given that safe Rust (and other safe languages) solve every kind of unsoundness.

                > in fact it helps because it allows compilers to turn it a trap without requiring it on weak platforms

                The "shared xor mutable" rule in Rust also helps the compiler a lot. It basically allows it to automatically insert `restrict` everywhere. The resulting IR is easier to auto-vectorize and you don't need to micro-optimize so often (although, sometimes you do when it comes to eliminating bound checks or stack copies)

                > Restrict is certainly dangerous, but also rarely used and a clear warning sign, compare it to "unsafe".

                It's NOT a clear warning sign, compared to `unsafe`. To call an unsafe function, the caller needs to explicitly enter an `unsafe` block. But calling a `restrict` function looks just like any normal function call. It's easy to miss an a code review or when upgrading the library that provides the function. That the problem with C and C++, really. The `unsafe` distinction is too useful to omit.

                • uecker 10 hours ago

                  You are not wrong, but also not really right. In practice, I never had a problem with "restrict". And this is my point about Rust being overengineered. While it solves real problems, you need to exaggerate the practical problems in C to justify its complexity.

                  • Expurple 10 hours ago

                    > In practice, I never had a problem with "restrict".

                    That's exactly because it's too dangerous and the developers quickly learn to avoid it instead of using it where appropriate! Same with multithreading. C leaves a lot of optimization on the table by making the available tools too dangerous and forcing people to avoid them altogether.

                    That's how you get stuff like memory-safe Rust PNG decoders being 1.5x faster than established C alternatives that had much more effort put into them (https://www.reddit.com/r/rust/comments/1ha7uyi/memorysafe_pn...). Or the first parallel CSS engine being written in Rust after numerous failed attempts in C++ (https://www.reddit.com/r/rust/comments/7dczj9/can_stylo_be_i...). Read those threads in full, there are some good explanations there.

                    > you need to exaggerate the practical problems in C

                    I thought, the famous "70% of vulnerabilities" report settled this once and for all.

                    • uecker 2 hours ago

                      Na, sorry. The "70%" is just nonsense. And cherry picking individual benchmarks too.

                      • Expurple an hour ago

                        > The "70%" is just nonsense.

                        Care to elaborate why?

                        > And cherry picking individual benchmarks too.

                        Do you have any general, comprehensive benchmarks or statistics that would indicate the opposite? I would include one if I had one at hand, because that would be a stronger argument! But I'm not aware of such benchmarks. I have to cherry pick individual projects. I don't want to.

                        I still claim that, as a general trend, Rust replacements are faster while also being less bug-prone and taking much less time to write. Another such example is ripgrep.

                  • antonvs 2 hours ago

                    You're focusing purely on the problem-fixing aspects, but Rust's expressiveness is why I like it.

                    C can't match that. In C, you're basically acting as a human compiler, writing lots of code that could be generated if you used a more expressive language. Plus, as has been mentioned, it supports refactoring easily and safely better than any language outside of the Haskell/ML space.

                    The advantages of Rust are a package which includes safety, expressiveness, refactoring support. You don't need to exaggerate anything for that package to make sense.

                    • uecker 2 hours ago

                      I am not criticizing anyone for preferring Rust. I reject the idea that its complexity needs to imposed on everybody because of "safety"

              • nemothekid 7 hours ago

                >When refactoring you just add assertions for NULL

                This is a line of thinking I used to see commonly when dynamic typing was all the rage. I think the difference comes from people who view primarily work on projects where they are the sole engineer vs ones where they work n+1 other engineers.

                "just add assertions" only works if you can also sit on the shoulder of everyone else who is touching the code, otherwise all it takes is for someone to come back from vacation, missing the refactor, to push some code that causes a NULL pointer dereference in an esoteric branch in a month. I'd rather the compiler just catch it.

                Furthermore, expressive type systems are about communcation. The contracts between functions. Your CPU doesn't case about types - types are for humans. IMO you have simply moved the complexity from the language into my brain.

                • uecker 2 hours ago

                  This was about refactoring a code base where a type is assumed to be non-null but it is not obvious. You can express also in C on interfaces that a pointer is non-null.

                  • staunton an hour ago

                    > interfaces

                    You mean obsolete and subtly wrong comments buried among preprocessor directives in some far-upstream header files?

      • anon-3988 2 hours ago

        > I do not really agree with respect to C. I often have to deal with C code written by unexperienced programmers. It is always relatively easy to refactor it step by step.

        Really? How confident are you to change a data structure that uses an array with linear search lookup to a dictionary? Or a pointer that now is nullable (or is now never null)?

        Unless you have rigorous test or the code is something trivial, this would be a project of its own.

        I am pretty sure I can swap out the implementation of the dictionary in the rust compiler and by the time the compilation issues are worked out, the code would be correct by the end of it (even before running the tests)

  • tines 13 hours ago

    The two are incomparable in both quality and quantity. The complexity of Rust comes from the fact that it's solving complex problems. The complexity of C++ comes from a poorly thought out design and backwards-compatibility. (Not to slight the standards committee; they are smart people, and did the best with what they had.)

    Anothere way of putting it is, if you didn't care about backwards-compatibility, you could greatly simplify C++ without losing anything. You can't say the same about Rust; the complexity of Rust is high-entropy, C++'s is low-entropy.

    • materielle 4 hours ago

      The C++ standard committee is definitely smart. But language design requires sense beyond just being smart.

      They didn’t do the best with what they had. Sure, some problems were caused by C backwards compatibility.

      But so much of the complexity and silliness of the language was invented by the committee themselves.

    • uecker 12 hours ago

      I would say that most of the complexity in both cases comes from overengineering.

      • tines 12 hours ago

        Can you give some examples of Rust and C++ design that are over engineered?

        • uecker 12 hours ago

          For C++, just take lambda captures, you can do [&], [=], [a], [&a], [a = b] (and I probably forgot half), the many way you can do initialization in C++ became a meme, but it really permeates the whole language whose design is driven by enthusiasts and people who have an interest to make it more complex (trainers, book authors, consultants, etc.)

          For Rust, I think it is a bit of a different story and it is harder to point to specific features. The language is clearly much better designed (because it was more designed and did not evolve so much) and because of its roots in functional programming. Just overall, the complexity is too high in my opinion and it a bit too idealistic and not pragmatic enough.

          • bfrog 5 hours ago

            The language is highly productive, it’s pragmatic enough. There’s no perfect language until our brains can directly describe what we want a machine to do without intermediary translation layers like C.

          • LoganDark 5 hours ago

            > it a bit too idealistic and not pragmatic enough

            This is an ideal programming language for certain types of people. It also gives the programming language certain properties that make it useful when provable correctness is a concern (see Ferrocene).

            • AlotOfReading 4 hours ago

              Last time I talked to the ferrocene people they had no plans to do anything with formal methods. Has their scope expanded?

        • anon-3988 2 hours ago

          For C++ specifically, constructors. Just have functions for gods sake.

  • jcranmer 3 hours ago

    Having developed a fair amount of expertise with both C++ and Rust, C++ is on a completely different level of complexity from Rust.

    In Rust, for most users, the main source of complexity is struggling with the borrow checker, especially because you're likely to go through a phase where you're yelling at the borrow checker for complaining that your code violates lifetime rules when it clearly doesn't (only to work it out yourself and realize that, in fact, the compiler was right and you were wrong) [1]. Beyond this, the main issues I run into are Rust's auto-Deref seeming to kick in somewhat at random making me unsure of where I need to be explicit (but at least the error messages basically always tell you what the right answer is when you get it wrong) and to a much lesser degree issues around getting dyn traits working correctly.

    By contrast C++ has just so much weird stuff. There's three or four subtly different kinds of initialization going on, and three or four subtly different kinds of type inference going on. You get things like `friend X;` and `friend class X;` having different meanings. Move semantics via rvalue references are clearly bolted on after the fact, and it's somewhat hard to reason about the right things to do. It has things like most-vexing parse. Understanding C++ better doesn't give you more confidence that things are correct; it gives you more trepidation as you know better how things can go awry.

    [1] And the commonality of people going through this phase makes me skeptical of people who argue that you don't need the compiler bonking you on the head because the rules are easy to follow.

    • tialaramex 32 minutes ago

      The C++ 11 move semantic is definitely an example of C++ programmers being sold a "pig in a poke"† The claim in the proposal document was that although this isn't the "destructive move" which programmers wanted (and which Rust had by 2015), the C++ 11 move feature can be realised with less disruption and is just as good. In reality it left significant performance on the table and you can't get it back without significant further language change.

      † This phrase would have been idiomatic many years ago but it is still used with the same intent today even though its meaning is no longer obvious, the idea is that a farmer at market told you this sack you can't see inside ("poke") has a piglet in it, so you purchase the item for a good price, but it turns out there was only a kitten in the bag, which (compared to the piglet) is worthless.

  • wisty 8 hours ago

    I'm not at all fluent at Rust, but I think c++ is not just complex, but every c++ project is complete in a different way.

  • Narew 2 hours ago

    It's funny because I have completely the opposite stance. When I code in rust (mainly algorithm), I always struggle to change what I want to do to what rust allow me to do. And all this complexity has nothing to do with the problem.

  • Aeolun 4 hours ago

    Rust is a lot better at producing helpful error messages than any C++ I’ve seen.

  • andrepd 7 hours ago

    It's really not even remotely the same. C++ has literally >50 pages of specification on the topic of initialising values. All of these are inconsistent, not subject to any overarching or unifying rule, and you have to keep it all in mind to not run into bugs or problematic performance.

    Rust is a dead simple language in comparison.

  • jplusequalt 13 hours ago

    >If there is a language that can rival C++ in terms of complexity

    Fair, but this relative. C++ has 50 years of baggage it needs to support--and IMO the real complexity of C++ isn't the language, it's the ecosystem around it.

  • dlachausse 13 hours ago

    Swift is a great C++ and Rust alternative that doesn’t get enough attention outside of Apple platforms. It’s a performant, statically typed, compiled language that feels almost like a scripting language to write code in. It’s memory safe, cross platform, has a fantastic standard library, and has excellent concurrency capabilities. Even the non-Xcode tooling is maturing rapidly.

    The big weak spot really is lack of community outside of Apple platforms.

    • cosmic_cheese 11 hours ago

      I would love to see a cross-platform desktop UI toolkit for Swift, preferably one that’s reactive and imperative-dominant with declaritivity sprinkled in where it makes sense (all-in declarative design like SwiftUI hits too many language weak points for the time being). Swift is really quite nice to write once you get a feel for it, and as long as one is judicious about advanced feature use, it looks more familiar and less intimidating than Rust does which is great for newcomers.

    • rapsey an hour ago

      Swift has IMO surpassed Rust in complexity. Rust changes very little but Swift has gone through multiple major changes and is accumulating cruft.

thasso 20 minutes ago

A nice thing about C is that you can be pretty confident that you know all major footguns (assuming you spent some time reading about it). With languages that are young or complex there is a much greater chance you’re making a terrible mistake because you’re not aware of it.

tux3 13 hours ago

>To paraphrase Norvig's Latency numbers a programmer should know, if we imagine a computer that executes 1 CPU instruction every second, it would take it days to read from RAM.

It's a detail, but this is a little bit off. RAM latency is roughly around ~100ns, CPUs average a couple instructions per cycle and a few cycles per ns.

Then in the analogy, a stall on RAM is about a 10 minute wait; not quite as bad as losing entire days.

  • jlokier 12 hours ago

    In current machines, that's way off depending on how you choose to count "1 CPU instruction" for the metaphor.

    Take Apple's latest laptops. They have 16 CPU cores, 12 of those clocking at 4.5 GHz and able to decode/dispath up to 10 instructions per cycle. 4 of those clocking at 2.6 GHz, I'm not sure about their decode/dispatch width but let's assume 10. Those decoder widths don't translate to that many instructions-per-cycle in practice, but let's roll with it because the order of magnitude is close enough.

    If the instructions are just right, that's 824 instructions per nanosecond. Or, roughly a million times faster than the 6502 in the Apple-II! Computers really have got faster, and we haven't even counted all the cores yet.

    Scaling those to one per second, a RAM fetch taking 100ns would scale to 82400 seconds, which 22.8 hours, just short of a day.

    Fine, but we forgot about the 40 GPU cores and the 16 ANE cores! More instructions per ns!

    Now we're definitely into "days".

    For the purpose of the metaphor, perhaps we should also count the multiple lanes of each vector instruction on the CPU, and lanes on the GPU cores, as if thery were separate processing instructions.

    One way to measure that, which seems fair and useful to me, is to look at TOPS instead - tera operations per second. How many floating-point calculations can the processor complex do per second? I wasn't able to find good figures for the Apple M4 Max as a whole, only the ANE component, for which 38 TOPS is claimed. For various reasons tt's reasonable to estimate the GPU is the same order of magnitude in TOPS on those chips.

    If you count 38 TOPS as equivalent to "CPU instructions" in the metaphor, then scale those to 1 per second, a RAM fetch taking 100ns scales to a whopping 43.9 days on a current laptop!

    • tux3 10 hours ago

      If you're counting all instruction executing in parallel with the maximum on-paper IPC on all CPUs, accelerators, and GPUs, the number your get has no clear relation to RAM latency. It really is comparing apples and oranges.

      This scenario where all your 16 cores are doing 10 instructions per clock assumes everything is running without waiting, at full instruction-level and CPU-level parallelism. It's a measure of the maximum paper throughput when you're not blocked waiting on memory.

      You could compare that to the maximum throughput of the RAM and the memory subsystem, and that would give you meaningful numbers (for instance, how many bytes/cycle can my cores handle? How many GB/s can my whole system process?).

      Trying to add up the combined throughput of everything you can on one side and the latency of a single fetch on the other side will give you a really big number, but as a metaphor it will be more confusing than anything.

    • renewiltord 8 hours ago

      This seems like the classic 9 women making a baby in 1 month. Even if the CPU can execute 824 instructions per nanosecond, it can't execute 1 instruction in 1/824 nanoseconds. You can't mix throughput and latency like that.

lmm 6 hours ago

If they're serious about their criteria they should go with OCaml (or maybe, like, Swift, or any of dozens of languages in that space).

(Of course they actually do want Haskell but they probably need to get there gradually)

  • zem 6 hours ago

    right! the table at the end just screamed "use ocaml and be happy"

    • legobmw99 6 hours ago

      nobody gives OCaml a thought in these discussions. It’s such a wonderful language!

  • tayo42 an hour ago

    Does ocaml have a mature ecosystem of libraries and dependencies? And easy way to manage them? Even rust with all its hype lacks in this area imo.

    • lmm an hour ago

      No, and that's part of why I use Scala instead. But this person is looking to make a binary to do a small thing on their desktop and does not list dependency management among their criteria.

jplusequalt 13 hours ago

>There is an apocryphal story about Euler in elementary school solving all the math problems that the teacher gave to the class in a jiffy, so the teacher tells him to sum up the numbers to a thousand to get him to stop pestering for more. The expectation was that Euler would go through the numbers "imperatively", like C, summing them up. Instead, what Euler did was discover the summation formula and solved it "declaratively" like Haskell, in one go, as an equation.

I've heard this story be accounted to Gauss, not Euler.

tines 13 hours ago

Very cool, I wish the author good luck! I've been writing a compiler in Rust for a few months now and I absolutely love it. The ways it solves most of the problems it addresses feel like the "right" way to do things.

There are some things that feel a little weird, like the fact that often when you want a more complex data structure you end up putting everything in a flat array/map and using indices as pointers. But I think I've gotten used to them, and I've come up with a few tricks to make it better (like creating a separate integer type for each "pointer" type I use, so that I can't accidentally index an object array with the wrong kind of index).

Rust is one of those languages that change how you think, like Haskell or Lisp or Forth. It won't be easy, but it's worth it.

  • lenkite 3 hours ago

    The best way to use Rust is to circumvent use of the borrow-checker and lifetimes and use indices everywhere! Suddenly, it becomes more pleasant and easy to refactor :).

genshii 13 hours ago

This hits close to home. TypeScript is also my language of choice for 90% of the software I write. I agree with the author that TypeScript is very close to the perfect level of abstraction, and I haven't seen another language with a type system that's nearly as enjoyable to use. Of course, TS (any by extension JS) obviously has its issues/complications. Bun solves a lot of the runtime-related issues/annoyances though.

For the other 10% software that is performance-sensitive or where I need to ship some binary, I haven't found a language that I'm "happy" with. Just like the author talks about, I basically bounce between Go and Rust depending on what it is. Go is too simple almost to a fault (give me type unions please). Rust is too expressive; I find myself debugging my knowledge of Rust rather than the program (also I think metaprogramming/macros are a mistake).

I think there's space in the programming language world for a slightly higher level Go-like language with more expressiveness.

  • daxfohl 13 hours ago

    I'm surprised ocaml doesn't have more market share here. Native, fast, robust type system, GC, less special syntax than rust, less obtuse than Haskell.

    • pragmatic 9 hours ago

      No libraries.

      All the niche languages have a chicken and egg problem.

      Only way around that is to be able to piggy back on C or JavaScript or Java.

      • daxfohl 8 hours ago

        Yeah, though when I say that I mean more like I'm surprised the ecosystem itself hasn't matured more. Rust and Go have both built solid ecosystems up from scratch, but it's a shame that OCaml hasn't since it's a nice middle ground between the two.

        • andrewflnr 8 hours ago

          I really considered one of the BuckleScript/js_of_ocaml languages for my current frontend project, but went with Typescript for basically conservatism/conventionalism reasons. I was already taking some arguably unnecessary technical risks on that project and wasn't sure I could afford more. I agree that I really wish OCaml or something close to it was mainstream.

    • lostmsu 3 hours ago

      Until recently it did not have multithreading

  • ashishb 13 hours ago

    TypeScript is good as a language. You can't generate static binaries out of it (except Docker images) and that itself is a deal breaker.

  • Sophistifunk 5 hours ago

    I very much enjoy reading and writing TS code. What I don't enjoy is the npm ecosystem (and accompanying mindset), and what I can't stand is trying to configure the damn thing. I've been doing this since TSC was first released, and just the other day I wasted hours trying to make a simple ts-node command line program work with file-extension-free imports and no weird disagreements between the ts-node runner and the language server used by the editor.

    And then gave up in disgust.

    Look, I'm no genius, not by a long shot. But I am both competent and experienced. If I can't make these things work just by messing with it and googling around, it's too damned hard.

  • renewiltord 8 hours ago

    I recently came to a production Typescript codebase and it took minutes to compile. Strangely, it could not behave correctly without a linter rule `no-floating-promises` but the linter also took minutes to lint the codebase. It was an astounding exercise in patience. Faster linters like oxlint exist but they don't have a notion of cross-file types so `no-floating-promises` is impossible on them.

    The worst part is that `no-floating-promises` is strange. Without it, Knex (some ORM toolkit in this codebase) can crash (segfault equivalent) the entire runtime on a codebase that compiles. With it, Knex's query builders will fail the lint.

    It was confusing. The type system was sophisticated enough that I could generate a CamelCaseToSnakeCase<T> type but somehow too weak to ensure object borrow semantics. Programmers on the codebase would frequently forget to use `await` on something causing a later hidden crash until I added the `no-floating-promises` lint, at which point they had to suppress it on all their query builders.

    One could argue that they should just have been writing SQL queries and I did, but it didn't take. So the entire experience was fairly nightmarish.

slowcache 13 hours ago

Odin has been really growing on me lately as a language that checks all of those boxes. String types, first class allocators, built in tests, a batteries included philosophy, and ease of use are some of the things that really drew me towards it.

I really wanted to like rust and I wrote a few different small toy projects in it. At some point knowledge of the language becomes a blocker rather than knowledge the problem space, but this is a skill issue that I'm sure would lessen the more I used it.

What really set me off was how every project turned into a grocery list of crates that you need to pull in in order to do anything. It started to feel embarrassing to say that I was doing systems programming when any topic I would google in rust would lead me to a stack overflow saying to install a crate and use that. There seemed to be an anti-DIY approach in the community that finally drew me away.

  • deathanatos 15 minutes ago

    > String types

    It's a byte string.

    > rune is the set of all Unicode code points.

    We copied the awful name from Go … and the docs are wrong.

    Five different boolean types?

    Zero values. (Every value has some default value, like in Go.)

    Odin also includes the Billion Dollar Mistake.

    > There seemed to be an anti-DIY approach in the community that finally drew me away.

    It's a "let a thousand flowers bloom" approach, at least until the community knows which design stands a good chance of not being a regretted addition to the standard library.

  • klntsky 13 hours ago

    What's the difference between anti-DIY and "batteries included"?

turboponyy a day ago

> While I can jump through hoops to compile JavaScript into a binary, such wouldn't feel "solid". And the very point of writing a native program in the first place is to make it feel solid

You can use Bun to compile to native binaries without jumping through hoops. It's not mature, but it works well enough that we use it at work.

  • genshii 13 hours ago

    It's definitely nice for certain use cases. I just wish the binaries weren't so huge (~60MB + your actual source code).

spooneybarger 5 hours ago

I've written a ton of C in my life and a C lot of Go and I was rofl at the "no spooky action at distance" lines.

This was brilliant performance art. Bless your heart Dear Author, I adore you.

  • blamestross 5 hours ago

    Everyone who has said the phrase "Spooky action at a distance" has been proven wrong :)

Sophistifunk 5 hours ago

Sounds like a Zig-shaped hole to me ;-)

  • Expurple 3 hours ago

    They complain that Go is too low-level for their needs. Zig, with its explicit allocators, is definitely even lower-level.

    Rust seems low-level too, but it isn't the same. It allows building powerful high-level interfaces that hide the complexity from you. E.g., RAII eliminates the need for explicit `defer` that can be forgotten

    • Sophistifunk an hour ago

      True, but I think the "low-level" complaint against Go in the article was just referring to all the stupid repetitive ceremony required for error handling, which Zig mostly skips over.

bee_rider 13 hours ago

I’m not sure what his abstraction column really means, nuts and bolts-wise. But, Fortran is native, you get to allocate your own memory, and it has object oriented features (maybe that’s abstraction).

j-krieger 13 hours ago

I write Gleam for this. A rust like language on the erlang VM. It's neat, but not widely used.

  • giancarlostoro 13 hours ago

    I have been wanting to use Gleam more, but I havent found the right project or time. I can prototype drastically easier using Python.

tptacek 13 hours ago

I don't think you need an elaborate process of elimination when one of your axioms is "must manage memory manually".

  • eviks an hour ago

    He's saying exactly the opposite

    > Rust... But it requires me to manage memory and lifetimes, which I think is something the compiler should do for me.

  • yccs27 13 hours ago

    > Memory management was indeed the sore sticking point, why Rust hadn't appealed to me earlier.

    The author doesn't want manual memory management, but still decides to go with Rust.

    • tptacek 13 hours ago

      Their rubric is literally just 3 items: "native compilation", "abstractions", and "manual memory management". Had they put that little table at the front of the article, there wouldn't even need to be an article: the table basically says "I'm going to use Rust". That's fine!

      • JoelMcCracken 12 hours ago

        The author really doesn't want to do manual memory management. The table is there to summarize things discussed, but he never says he wants to do manual memory management. I just went back and checked. If you do find something that indicates that, I'd appreciate you pointing it out to me, because idgi

        • tptacek 11 hours ago

          I read the article twice, but I could also just be wrong; it's not that big a deal.

          • Expurple 11 hours ago

            No problem, get some sleep in that case :D

        • ignoramous 8 hours ago

          > The table is there to summarize things discussed, but he never says he wants to do manual memory management.

            But it requires me to manage memory and lifetimes, which I think is something the compiler should do for me.
          
          The author wants the compiler to do memory management? How does Rust achieve this?
          • adastra22 2 hours ago

            It is better to think of Rust having explicit memory management, rather than manual memory management. C/C++ has manual memory management: the burden is on you to do it correctly. If you fuck up, your program will have bugs. Rust requires that your code be explicit about memory issues, but the compiler works with you to achieve that. if your code compiles it is correct; if it doesn't compile, the error points out what needs to be fixed. When I write Rust it rarely feels like I am taking on that burden myself.

          • steveklabnik 8 hours ago

            The compiler works with you to ensure that you’re not misusing memory. If you make a mistake, it will let you know.

            You’re not often manually managing memory in the same sense as other low-level languages. You’re not invoking malloc and free directly. Thanks to ownership, when you do allocate, Rust will call free for you.

            It’s somewhere in between fully manual and fully automatic. It can feel more like one or the other based on what you’re doing, but most of the time, in average, it feels like automatic.

  • Expurple 12 hours ago

    I don't think you need an elaborate comment when one of your axioms is "must not read the post that you're responding to".

nektro 5 hours ago

> If someone posts a patch or submits a PR to a codebase written in C, it is easier to review than any other mainstream language.

short-circuited reading this

pron 12 hours ago

> And the very point of writing a native program in the first place is to make it feel solid.

What does that mean, and what is it about native programs (i.e. programs AOT-compiled to machine code) that makes them feel solid? BTW, such programs are often more, not less, sensitive to OS changes.

> realizing that I was just spawning complexity that is unrelated to the problem at hand

Wait till you use Rust for a while, then (you should try, though, if the language interests you).

For me, the benefit of languages with manual memory management is the significantly lower memory footprint (speed is no longer an issue; if you think Haskell and Go are good enough, try Java, which is faster). But this comes at a price. Manual memory management means, by necessity, a lower level of abstraction (i.e. the same abstraction can cover fewer implementations). The price is usually paid not when writing the first version, but when evolving the codebase over years. Sometimes this price is worth it, but it's there, and it's not small. That's why I only reach for low level languages when I absolutely must.

  • Expurple 12 hours ago

    > such programs are often more, not less, sensitive to OS changes.

    You may be technically correct that they are more sensitive to the kernel interface changes. But the point is that native, static binaries depend only on the kernel interface, while the other programs also depend on the language runtime that's installed on that OS. Typical Python programs even depend on the libraries being installed separately (in source form!)

    • pron 12 hours ago

      > But the point is that native, static binaries depend only on the kernel interface

      Many binaries also depend on shared libraries.

      > while the other programs also depend on the language runtime that's installed on that OS

      You can (and probably should) embed the runtime and all dependencies in the program (as is easily done in Java). The runtime then makes responding to OS selection/changes easier (e.g. musl vs glibc), or avoids less stable OS APIs to begin with.

      • Expurple 11 hours ago

        > Many binaries also depend on shared libraries.

        Yeah, and those are also the opposite of "solid" :) That's why I qualified with "static". I'm so glad that Go and Rust promote static linking as the default (ignoring glibc).

        > You can (and probably should) embed the runtime and all dependencies in the program (as is easily done in Java).

        Congrats to the Java team and users, then. That makes it similar to the Go approach to binaries and the runtime, which I approve

        • pron 11 hours ago

          > Yeah, and those are also the opposite of "solid" :)

          So if that's what the author meant by "solid", i.e. few environmental dependencies, then it's not really about "native" or not, but about how the language/runtime is designed. Languages that started out as "scripting" languages often do rely on the environment a lot, but that's not how, say, Java or .NET work.

          > I'm so glad that Go and Rust promote static linking as the default (ignoring glibc).

          That doesn't work so well (and so usually not done) once you have a GUI, although I guess you consider the GUI to be part of the kernel.

  • Mawr 8 hours ago

    > For me, the benefit of languages with manual memory management is the significantly lower memory footprint (speed is no longer an issue; if you think Haskell and Go are good enough, try Java, which is faster).

    ... what? Speed is no longer an issue? Haskell and Go? ??? How'd we go from manual memory management languages to Haskell and Go and then somehow to Java? Gotta plug that <my favorite language> somehow I guess...

    It seems to me you have a deep misunderstanding of performance. If one program is 5% faster than another but at 100x memory cost, that program is not actually more performant. It just traded all possible memory for any and all speed gain. What a horrible tradeoff.

    This thinking is typical in Java land [1]. You see: 8% better performance. I see: 28x the memory usage. In other words, had the Rust program been designed with the same insane memory allowance in mind as the Java program, it'd wipe the floor with it.

    [1]: https://old.reddit.com/r/java/comments/n75pa0/java_beats_out...

  • ignoramous 8 hours ago

    > What does that mean, and what is it about native programs (i.e. programs AOT-compiled to machine code) that makes them feel solid? BTW, such programs are often more, not less, sensitive to OS changes

    TFA also concludes

      Since I want native code ...
    
    I think by "solid" they mean as close to metal as possible, because, as you suggest, one can go "native" with AOT. With JS/TS (languages TFA prefers), I'm not sure how far WASM's AOT will take you ... Go (the other language TFA prefers) even has PGO now on top of "AOT".
DarkNova6 13 hours ago

The table in the end sums it up nicely.

Rust allows low level programming and static compilation, while still providing abstraction and safety. A good ecosystem and stable build tools help massively as well.

It is one of the few languages which managed to address a real life need in novel ways, rather than incrementing on existing solutions and introducing new trade offs.

blashyrk 2 days ago

Someone really needs to show Nim to the author :). It checks all of their boxes and then some

  • Symmetry 13 hours ago

    I was thinking that too. There are many cases where you do want to manage memory yourself, and in that case you should likely use Rust or maybe Zig if you can choose your own tool. But if you don't want to manage your own memory Nim works nicely, though IMO it requires adherence to a style guide more than most languages.

    • timeon 19 minutes ago

      Depends what you do but most of the time you do not need to do anything special about memory management in Rust. That is why people try to use it for other things then just system programming.

  • elcritch 13 hours ago

    Yep it’s ideal for this sort of application without the headache of Rust. Plus it’s helpful it can compile to C,C++, or JavaScript. So take your pick.

outside1234 2 hours ago

Rust is an amazing language once you get over the initial mental hurdle. An important thing to go in with: 99% of programs should not require you to manage lifetimes (‘a notation) If you find yourself doing this and aren’t writing a inner loop high performance library, back up and find another way. Usually this entails using a Mutex or Arc (or other alternatives based on the scenario) to provide interior mutability or multiple references. This statement might not make sense now but write it down for when it will.

I use Rust now for everything from CLIs to APIs and feel more productive in it end to end than python even.

TOGoS 6 hours ago

I'm interested in the long piecewise elimination section. Presumably that's where they explain why not use Ocaml/Nim/yaddah yaddah.

If I were to write such a list, the answer would probably come down to "because I wanted to pick ONE and be able to stick with it, and Rust seems solid and not going anywhere." As much as Clojure and Ocaml are, from what I've heard, right up my alley, learning all these different languages has definitely taken time away from getting crap done, like I used to be able to do perfectly well with Java 2 or PHP 5, even though those are horrible languages.

JoelMcCracken 13 hours ago

Wow, a lot of stuff in here surprises me. C definitely can/does have spooky at a distance. Just share a pointer to a resource with something else and enjoy the spooky modifications. Changes are local as long as you program that way, but sometimes it can be a bit not-obvious that this is happening.

regarding redefining functions, what could the author mean? using global function pointers that get redefined? otherwise redefining a function wouldn't effect other modules that are compiled into separate object files. confusing.

C is simple in that it does not have a lot of features to learn, but because of e.g. undefined behavior, I find its very hard to call it a simple language. When a simple bug can cause your entire function to be UB'd out of existence, C doesn't feel very simple.

In haskell, side effects actually _happen_ when the pile of function applications evaluate to IO data type values, but, you can think about it very locally; that's what makes it so great. You could get those nice properties with a simpler model (i.e. don't make the langague lazy, but still have explicit effects), but, yeah.

The main thing that makes Haskell not simple IMO is that it just has such a vast set of things to learn. Normal language feature stuff (types, typeclasses/etc, functions, libraries), but then you also have a ton of other special haskell suff: more advanced type system tomfoolery, various language extensions, some of which are deprecated now, or perhaps just there are better things to use nowadays (like type families vs functional dependencies), hierarchies of unfamiliar math terms that are essentially required to actually do anything, etc, and then laziness/call-by-name/non-strict eval, which is its own set of problems (space leaks!). And yes, unfamiliar syntax is another stumbling block.

IME, Rust is actually more difficult than Haskell in a lot of ways. I imagine that once you learn all of the things you need to learn it is different. The way I've heard to make it "easier" is to just clone/copy data any time you have a need for it, but, what's the point of using Rust, then?

I wonder if the author considered OCaml or its kin, I haven't kept track of whats all available, but I've heard that better tooling is available and better/more familiar syntax. OCaml is a good language and a good gateway into many other areas.

There are some other langs that might fit, like I see nim as an example, or zig, or swift. I'd still like to do more with swift, the language is interesting.

  • tines 12 hours ago

    > Wow, a lot of stuff in here surprises me. C definitely can/does have spooky at a distance. Just share a pointer to a resource with something else and enjoy the spooky modifications. Changes are local as long as you program that way, but sometimes it can be a bit not-obvious that this is happening.

    I think the author means that the language constructs themselves have well-defined meanings, not that the semantics don't allow surprising things to happen at runtime. Small changes don't affect the meaning of the entire program. (I'm not sure I agree that this isn't the case for e.g. Haskell as well, I'm just commenting on what I think the author means.)

    > IME, Rust is actually more difficult than Haskell in a lot of ways. I imagine that once you learn all of the things you need to learn it is different.

    Having written code in both, Rust is quite a lot easier than Haskell for a programmer familiar with the "normal" languages like C, C++, Python, whatever. The pure functionality of Haskell is quite a big deal that ends up contorting my programs into weird poses, e.g. once you run into the need to compose Monads the complexity ramps way up.

    > The way I've heard to make it "easier" is to just clone/copy data any time you have a need for it, but, what's the point of using Rust, then?

    Memory safety. And the fact that this is the example of Rust complexity just goes to show what a higher level Haskell's difficulty is.

    • JoelMcCracken 12 hours ago

      Thanks for explaining what you think the author meant; i meant to reiterate that I was trying to actually understand as opposed to argue, but I forgot; I think sometimes "hey I don't get what you're saying because of reasons XYZ..." comes across as "I think you're wrong".

      Composing monads is another one of those painful parts of haskell. I remember being so frustrated while learning Haskell that there was all of this "stuff" to learn to "use monads" but it seemd to not have anything to _do_ with `Monad`, and people told me what I needed to know was `Monad`. Someday I wanna write all that advice I wish I had received when learning Haskell. A _lot_ of it will be about dealing with general monad "stuff".

      The thing that frustrated me in Rust coming from something like Ruby was how frequently I could not _do_ a very straightforward thing, because, for example, some function is a fnOnce instead of fnMulti, or the other way around, or whatever. Here's some of the experience from that time https://joelmccracken.github.io/entries/a-simple-web-app-in-.... It became clear to me eventually that some very minor changes in requirements could necessitate massive changes in how the whole data model is structured. Maybe eventually I'd get good enough at rust that this wouldn't be a huge issue, but I had no way of seeing how to get to that point from where I was.

      In contrast, I can generally predict when some requirement is going to necessitate a big change in haskell: does it require a new side effect? if so, it may need a big change. If not, then it probably doesn't. But, I've found it surprisingly easy to make big changes from the nice type system.

      I really don't get when rust folks claim "memory safety" like this; we've had garbage collection since 1959. Rust gives you memory safety with tight control over resource usage; memory safety is an advantage that Rust has over C or C++, but not over basically every other language people still talk about.

      If you just clone/copy every data structure left and right, then you're at a _worse_ spot than with garbage collection/reference counting when it comes to memory usage. I _guess_ you are getting the ability to avoid GC pauses, but, why not use a reference counted language if that's the problem? copy/clone data all of the time can't be faster than the overhead from a reference counting, can it??

      In haskell, I did find that once I understood the various pieces I needed to work with, actually solving problems (e.g. composing monads) is much easier. I don't generally have a hard time actually programming Haskell. All that effort is front-loaded though, and it can be hard to know exactly what you need to learn in order to understand some new unfamiliar thing.

      Your preferring Rust over Haskell is totally fine BTW, I'm just trying to draw a distinction between something that's hard to _use_ vs something that's hard to _learn_. Many common languages are much harder to use IME; I feel like I have to think so hard all of the time about every line of code to make sure I'm not missing something, some important side effect that I don't know about that is happening at some function call. With Haskell, I can generally skim the code and find what's important quite quickly because of the type system.

      I do plan to learn Rust at some point still whenever the planets align and I need to know something like it. Until then, there are so many other things that interest me, and not enough hours in the day. I still wonder if I have really missed out on some benefit from learning to think more about data ownership in programs.

      • Expurple 11 hours ago

        > frequently I could not _do_ a very straightforward thing, because, for example, some function is a fnOnce instead of fnMulti, or the other way around [..] It became clear to me eventually that some very minor changes in requirements could necessitate massive changes in how the whole data model is structured. Maybe eventually I'd get good enough at rust that this wouldn't be a huge issue, but I had no way of seeing how to get to that point from where I was.

        I understand your frustration, and Rust does get too low-level sometimes (see https://without.boats/blog/notes-on-a-smaller-rust/). But the semantic difference between FnOnce and Fn is actually important. Fn consumes its environment and makes it unavailable later. This is an important property. When you don't want that, "just" use Fn, wrap everything in an Arc and clone everything (I understand that this is more ceremony than in other languages, and that can be unjustified).

        > I really don't get when rust folks claim "memory safety" like this; we've had garbage collection since 1959.

        Agree 100%! What Rust actually gives you is predictability, reliability and compile time checks, while still allowing to write relatively ergonomic imperative and "impure" code. And a sane ecosystem of tools that are designed to be reliable and helpful. I'm currently writing a post about this.

        It also gives compile-time data race protection, which is still missing from some other memory-safe languages.

        > I still wonder if I have really missed out on some benefit from learning to think more about data ownership in programs.

        Yeah :) Affine types + RAII (ownership) allow you to express some really cool things, such as "Mutex<T> forces you to lock the mutex before accessing the T and automatically unlocks it when you're done", or "commits and rollbacks destroy the DatabaseTransaction and make it statically impossible to interact with", or "you'll never forget to run cleanup code for objects from external C libraries" (https://www.reddit.com/r/programming/comments/1l1nhwz/why_us...)

        • JoelMcCracken 11 hours ago

          I asked a bad question; what I meant was specifically thinking about ownership in terms of being required everywhere and to manage memory. But there are for sure real benefits I can see for _other_ properties in certain situations, like avoiding data races that might occur in Haskell. GHC added a linear type extension, for that matter (though AFAIK its still not very great to use). But some of that seems distinct from the question "do I benefit in some data modeling way from thinking about the ownership of memory in my program" as opposed to creating/sharing references whenever convenient.

          • Expurple 11 hours ago

            The deal with strict Rust-style references is that they solve mutable aliasing bugs (data races, iterator invalidation, all kinds of unexpected mutation really). That's the deal with Rust.

            The whole "memory management" thing is mostly a historical accident. We could have a "smaller Rust" that auto-boxes values, has a runtime that handles reference cycles for you, and doesn't guarantee anything about the stack vs heap: https://without.boats/blog/notes-on-a-smaller-rust/

      • tines 12 hours ago

        Yep I feel you. Thanks!

B4uler5 13 hours ago

If anyone reads this and like me fears the difficulty and complexity of rust, but still wants a language that is competitive in performance, works for system level programming as well as something more general purpose definitely give Swift a go.

Over the last year I’ve started to write every new project using it. On windows, on linux and mac.

It is honestly a wonderful language to work with. Its mature, well designed, has a lot of similarities to rust. Has incredible interop with C, C++, Objective-C and even Java as of this year which feels fairly insane. It also is ergonomic as hell and well understood by LLM’s so is easy to get into from a 0 starting point.

  • freeone3000 12 hours ago

    My major problem with Swift was its error messaging. “Error: -2.” Okay, well, I suppose I’ll attach a debugger and… it’s of absolutely no help, since it crashes internally to some function and I can’t get out a stacktrace.

    Function calling is irksome, with implicit parameters and mandatory parameters vaguely mixing? And the typing is appalling - there are multiple bottom types with implicit narrowing casts, one of them being NSObject, so if you’re doing any work with the Apple APIs you end up with a mess.

    We got it right with Java and Rust; C++ does a passable job; why Swift had to be as incomprehensible as typescript, I cannot fathom.

  • tines 13 hours ago

    How tied to the Mac ecosystem is Swift these days?

    Also, how is its type system and metaprogramming? Does it have type polymorphism, typeclasses, macros, etc?

    • B4uler5 12 hours ago

      Some of the functionalities in the core/foundation library don’t quite match between operating systems so sometimes you do need to put an “#if os(macos) do” etc, but for the most part its super straight forward working on one or another platform.

      In terms of its language features it has all of those and more, sometimes too many in my opinion.

      I personally favour languages that are clear in their vision. However at its core Swift is a highly performant language that is designed really well, has beautiful syntax, and in the last 5 or so years I have been impressed with its direction. It is being developed by a good team who listen to their community but not at the expense of the languages vision.

      My favourite aspect of using it though is its versatility. Wether you’re working on embedded systems, a game, a web server, or even a static site generator, it always feels like the language is there to support your vision, but still give you the fine grained control you need to optimise for performance.

      I also collaborate with a friend who is a Rust developer and he’s always super happy to work on a Swift project with me so I feel like that’s enough praise when you can pull a rest dev away from their beloved.

    • dlachausse 12 hours ago

      It supports Linux, Windows, and even Android. It has all those things and more.

  • dlachausse 12 hours ago

    Swift is the most underrated language in this space.