Threads for kornel

    1. 4

      This is just an unfortunate consequence about the complexity of software. Some things will inherently be much harder due to how they’re written and it’s not a good use of developer time to try to predict every possible use case that users will possibly have, where some customizations may involve rewriting the entire software differently from the ground up.

      The fact that the author was able to patch Qt at all and modify the behavior of their software is the success of free software. Shortcuts on Apple doesn’t make implementing vim keybinds for text boxes any easier. The claim just seems incredibly exaggerated to vent frustration but fundamentally the system is working as intended

      1. 1

        as intended by whom? RMS certainly isn’t happy with the direction software is moving.

        1. 3

          There are lots of different software trends RMS can rightfully be unhappy about, but that doesn’t mean this particular thing isn’t working as intended. Qt is available under the GNU license, and the OS OP was complaining about was most likely also under a GNU license. The difficulty of making modifications wasn’t from anybody trying to deny licensing rights, but rather overall complexity of the system, and others exercising their right to modify and distribute the software.

          1. 2

            they said “the system is working as intended” so I don’t think they were talking about just Qt; maybe they will clarify

    2. 37

      This is just so fundamentally confused about what rights and obligations come with the freedoms, and why they’re important.

      1. 25

        But the point seems valid to me — what good is the freedom to modify the software on your computer if doing so is brain-fryingly difficult?

        The underlying problem is that developing for a very, very deep and complex hierarchy of components is hard. It usually requires big teams with well-organized development and testing processes. (I’ve worked on such a team.) When an individual opens up some component in the middle, like a UI framework, and starts making changes, it’s pretty likely stuff will break, or at least the resulting build & integration work will be challenging.

        I still recognize the value of such freedom, but I think many descriptions are being disingenuous in how they describe it as being the same as tinkering with a bike or a guitar, which are approachable to nearly anyone.

        1. 14

          The legal freedom is the required element, but it’s not sufficient alone. Whether any modification is easy is somewhat orthogonal to the software license (I bet Microsoft couldn’t easily modify keybindings either across eleven different bug-compatible UI toolkits and the Office Multiverse).

          I get that difficulty of actually modifying software is a problem, but Free Software is a specific term, so reframing it to encompass usability of patching libraries across a whole desktop app ecosystem is stretching it beyond its meaning.

          BTW: this situation is an analog of the “Non-programmers can’t modify Free Software” problem. FSF has addressed this, that even if you don’t have the skills, you still have the rights, so you can get help from the community or hire someone to make modifications for you.

          1. 9

            Yeah, I see it as less “Free software that you can’t customize is not truly Free Software” and more “Free software that you can’t customize is not good software”.

            Whether the software is free or not only matters if it’s worth using in the first place.


            I think autohotkey can do it to some extent. Example -

            But this is not built in to Windows.

            I agree with the rest of the comment.

        2. 8

          If what you’re looking for is good or user-friendly or user-customizable software, I’m afraid you’re just going to have to exercise one of the freedoms of open source and fork it. I too want good software, but the idea of Free Software and “truly” (whatever that means) free software has nothing to do with the software being good.


          You’re absolutely right. More often than not, that “complex hierarchy” is quite deliberate and serves a business interest rather than user’s interest. Very relevant piece on this topic by the Rails inventor: They’re rebuilding the Death Star of complexity

      2. 7

        I think that would be a different article. This one seems more to be concerned with using “actually-existing free software” than with formal philosophical concerns.


          I think the issue there is that there is no well defined skill level or stubborness required to enjoy FOSS.

          If someone is okay with recompiling a new Qt version every week because they just want that one change, would that make the article invalid?

          Conversely, if we are thinking about non-experts, how low does the ceiling have to be? Is it okay to deny someone who does not have the ability to program at all the ability to modify their FOSS software as they wish? And from the other view, is it okay that only a select number of people from the population have the ability to modify the software? Even if that just means “people who can edit a config file in [fileformat]”?

          I don’t think the blog makes a great point because of that; the author has a skill level and a sort of pain threshold of what they are willing to do to make their computer work the way they want. Everyone else has different thresholds and levels, even if they might be similar.

          The above points definitely fall into “actually using free software”, just not from the authors point of view but some random person of the street. Not everyone can patch software. Not everyone is comfortable editing a config file. Some people do not feel safe to configure things unless it very plainly and in a single sentence says what it’ll do. All of them have a right to free software modification, they just lack the ability. If we follow the author’s argument, then no software is truly free.

    3. 1

      The talk makes sense in its context of Google making a mess and needing to clean it up. It’s just the title that sounds incredibly native.

    4. 5

      Software produced when there’s nothing at stake and developers get to do whatever they want.

      While I can get behind most of the article, I very vehemently disagree that this is - in itself - a red flag. There is absolutely nothing wrong with developers doing whatever they want, to experiment, or learn, or just to have fun. All of these are valid and desirable goals and purposes.

      Not all software exist to serve a business, or anything at all, really. It is fine to not have a purpose for a piece of code after it has been developed - for its development is the reason.

      1. 8

        I didn’t get that as a red flag from the article. It’s listed under smells because you absolutely don’t want this to be the driving goal in a commercial environment, but I’d expect most developers to have at least one or two projects like this. The key is the ‘low stakes’ bit. Don’t do this for something that other people depend on.

        1. 2

          I disagree, even in a commercial environment. Letting developers loose to do whatever the heck they want is a very good way to allow them to grow, and learn, and experiment. You don’t have to ship that code to customers, but it is absolutely valuable to have it internally, to talk it through with other teams, perhaps.

          We should have more software that people write for fun, for reasons other than commercial interest. Not less, not just one or two per developer.

          1. 8

            If you are in a position to let developers write code and not use it then that is, by definition, a low stakes activity, where I said this is a good thing.

            1. 3

              I strongly disagree. Encouraging - not letting! they’re not children - developers to write experimental throwaway code can be a great way of de-risking high stakes activities.

              1. 2

                Developers can get sucked into rabbit-holes of technically interesting and challenging problems that have no value for the business. There really needs to be care taken to guide experimentation in productive directions. You might get a new product or a creative solution, but you may as well end up with another framework to end all frameworks, which will somehow become the foundation of all your software, and nobody except the original author will understand or even want to use.

                1. 1

                  There really needs to be care taken to guide experimentation in productive directions.

                  Yes, absolutely! IMO it’s vital that the goals of experimental stories / spikes / whatever are understood beforehand, and ideally that they’re timeboxed up-front as well.

    5. 3

      “Finally, securing that private key is vital. Password protect that private key.”

      Isn’t this providing two forms of authentication? Something you have (the private key) and something you know (the password for the key).

      1. 7

        Not exactly, because the server, which checks your authentication, can’t check if the key is password protected. Even with a user provided FIDO2 key a server can’t know if the private key is on some extra device and the owner of this device has pressed a button to authorise the key usage.

        1. 2

          Even with a user provided FIDO2 key a server can’t know if the private key is on some extra device and the owner of this device has pressed a button to authorise the key usage

          It can be fairly certain though with some extra conditions, for example from checking the key’s attestation information(not usually done not except in enterprise usecases) it can get the vendor, and some vendors give guarantees that the keys they produce satisfy some guarantees, like if the response comes out with user authorization, the user had to actually authorize it or that it’s a separate hardware device.

          But that’s somewhat privacy invasive, and for example Firefox asks if you really want to give out that information. It makes sense for cases where the keys are handed out by work, but as much for private persons.

          1. 1

            The server still can only validate that the client has access to the private key (matching the presented cert/pubkey). So it is still a single factor. That the vendor of the device somehow enforces the button press (or some sort of auth) is trusting the vendor.

            Don’t get me wrong, this might be the right choice (depending on your system), but theoretical speaking it’s not a second factor.

        2. 1

          I thought my SSH server config had a flag to only accept password-protected keys last time I looked, but I could be misremembering or this could be trivially bypassed.

          1. 2

            I would say you misremember something. When you think about how pubkey-auth works this is quite obvious:

            To authenticate you send the server your public key. The server accept the key and send you the algorithm you use to prove that you own the private key. You (or more precise your client) sign now a struct out of session-id, username, some details and your public key. This signature is send to the server. The server can’t distinguish between a client with loads the unencrypted key from the disk, decrypt the private key, using some sort of agent, or uses a external hardware to sign this message.

            You can also think about what the password protection actual is. To protect your private key with a password this password is give to a key derivation function. There you get a symmetric key and you encrypt your private key with this symmetric key. To use the private key you generate the same symmetric key out of your password and decrypt your private key with it.

            1. 2

              I was thinking of the verify_required option in sshd, which only applies to FIDO algorithms:

        3. 0

          Without the password, the password-protected key is useless, so both factors are required and checked.

          Enforcing that users set up password-protected keys is an orthogonal issue. There are many sites that support MFA, but don’t require users to set it up.

    6. 15

      Excellent, clear, to the point. Well done.

      One minor confounding factor that might be worth exploring: users in aggregate versus users in specific, ditto for other quantities.

      I’ve seen codebases and businesses contort themselves into pretzels trying to cater to one user or use-case and ignore the larger or more relevant masses.

      1. 9

        and conversely software becoming a space shuttle cockpit by trying to have all features for all users, instead of focusing on a specific group.

        1. 9

          This was Steve Jobs’ superpower. He had fairly good taste and a set of requirements that overlapped a fair chunk of users and insisted that products were developed for him, not for any aggregate model of users. Sometime this went badly (the NeXT computers required someone as rich as him to afford them and the iPod had volume settings for someone as deaf as him and could cause ear damage), but when your requirements were close to his they were amazing products.

          1. 3

            NeXT computers required someone as rich as him to afford them

            That’s pretty unfair!

            The original NeXT was $6500, and the 68040 NeXTStation was $4995.

            In comparison, the Mac II was $5498 and the IIfx was $8969. The first computer I ever owned personally, a Mac IIcx purchased because I’d just bought a cheap Chinese 2400 bps modem at MacWorld SF and a local BBS was providing uucp-based mail and “news”, was $5369, which would have been about 2-3 months salary.

            Intel 80386 and 80486 PCs from name brand manufacturers were similar prices.

            1. 14

              The original NeXT was $6500, and the 68040 NeXTStation was $4995.

              In today’s money, $6,500 works out at $16,905.00. That’s pretty close to the annual salary (before tax) of a minimum-wage worker, and not exactly cheap for anyone else. And that was the base model, you needed a RAM upgrade and a hard disk (it came only with MO) for it to be a useful machine.

              Intel 80386 and 80486 PCs from name brand manufacturers were similar prices.

              And most of these manufacturers also sold 286 and 8086 machines because the 386s were too expensive for most customers. For most of the ’90s, you could buy a fairly decent (not top of the range, but quite usable) PC for a bit over £1000. That was a big expense for most households. The cheapest NeXT machine was several times more expensive.

              NeXT sold a total of 50,000 computers, over 11 years of operation. They received glowing reviews throughout that time and so there were a lot of people that wanted them, but very few that could afford them.

              To put that number in perspective, Compaq sold 53,000 computers in their first year of operation, in 1982. They sold the first 386 systems and their sales numbers for those were only a bit higher than the NeXT Computer, but they sold almost exclusively to businesses that had existing x86 software that they needed to go faster. They made a ton of money from this, but the 286 massively outsold them in volume for several years. The 386s were high-margin machines (twice as fast as competitors, running the same software).

              The Apple II, which aimed at the mass market, was $1,298 in 1977. Inflation adjusted, that’s $2,533.88 in 1988 dollars, slightly more than a third of the price of the NeXT. The Commodore PET (released the same year) was $795. The PET sold 219,000 units - more than four times as many computers as NeXT sold in total. The PET was not even close to Commodore’s most successful machine, the C64 sold over 12 M units, in a similar time to NeXT.

              1. 1

                And most of these manufacturers also sold 286 and 8086 machines because the 386s were too expensive for most customers.

                Which were not at all comparable to a NeXT, Mac II, or 386/486 PC except in the trivial Turing equivalence sense.

                Yes, 32 bit computers were very expensive in the late 80s. Most people couldn’t afford them. That doesn’t make NeXT overpriced in comparison to comparable machines, or require you to be “rich” to afford one. It simply required planning and prioritising in the same way as buying a new car.

                I sure wasn’t rich in 1989, just a couple of years out of university, but I found the money for that Mac IIcx, partly by buying a 0/0 machine (no RAM, no hard disk, I installed cheaper 3rd party parts) and greyscale 640x480 monitor and video card rather than a larger or colour setup.

                If you were using the computer to earn a living, not just as a toy, then it was well worth spending the money for a ’386 or ’030 machine. Some people with undemanding needs would get by with a ’286 or 68000, but there is no way that a C64 or Apple ][ cut it for work in the late 80s. As a toy for games, sure.

              2. 1

                I bought my first computer in 1988, it was a 286 laptop with 1Mb of ram and cost £999, a year later I upgraded to 2Mb for an extra £400. The only reason I didn’t buy a standard PC is that a laptop suited by itinerant lifestyle, and Apple and NeXT were out of my price range as a student…

      2. 2

        I’ve seen codebases and businesses contort themselves into pretzels trying to cater to one user or use-case and ignore the larger or more relevant masses.

        I’ve seen this happen mostly when the company wasn’t doing too well and they needed that one user’s business too badly. A company that’s doing well can (typically, not always) take a wider view and realize that they shouldn’t destroy a good product just to reel in that one customer that wants a weird-ass feature that doesn’t integrate well.

        It can also happen when the company has non-technical leadership which doesn’t listen to the tech folks regarding what a change will do to the stability or quality of a codebase.

        Of course, when both the above come together it’s a recipe for disaster.

    7. 23

      TL;DR: If you call the get method to extract a raw pointer from a smart pointer, you can introduce all of the pain and suffering that raw pointers imply. This is why C++ guidelines all say some variation of: Do this only when you fully understand the ownership semantics of the API that you’re passing the raw pointer to, every call to get is a red flag in code review and should be subject to additional scrutiny.

      Smart pointers don’t prevent bad code (and, before Rust evangelists jump in, all of these cases in Rust require unsafe and have the same problems if you commit them without thinking or properly reviewing the code), they make bad code easier to spot. The examples in the article are very obvious things that stand out and would not pass half competent code review. At the very least, every call to get should be accompanied by a comment explaining why it’s say (I personally wish this method had been called unsafe_get: that’s what I call it in my own smart pointers so that it stands out).

      1. 3

        As a Rust evangelist, I completely agree.

        1. 9

          Yup, in Rust it’s possible to accidentally cast Box<T> to &T to a C raw pointer. This doesn’t even require unsafe on the Rust side, apart from the C call that will take the blame.

          One thing I wish more people knew (and used) is that Rust’s Box<T> and &mut T types are ABI-compatible with C pointers. You can define C functions as taking or returning Box or &T where appropriate, effectively adding machine-checked documentation for how the C functions handle ownership.

      2. 2

        The examples in the article are very obvious things that stand out and would not pass half competent code review.

        The article mentions having observed this error in a medical device… so I guess we can assume this medical device did not undergo such a half competent code review?

        No wonder the EU came up with something like the CRA.

        1. 9

          I reviewed the certification requirements for medical devices a year or so ago to see if CHERIoT would be a good fit. They all revolve around process, but are light on specifics. Requiring code review and sign-off may be a requirement but, for example, adhering to the C++ Core Guidelines and documenting every exception probably would not be. They are not even close to being fit for purpose and often failures are just blamed on nurses.

    8. 2

      I think the improved ergonomics of async fn next() is irrelevant. This is the shape of the Iterator trait, and people already dislike writing iterators this way. A for loop is much simpler to write than a stateful fn next(). So I think even if the async fn next() interface was available, people would skip it anyway, and go for a generator syntax (like this one).

      So when the easiest and most preferred syntax can be implemented on top of either abstraction, then ergonomics for simple cases isn’t the differentiator. Instead, it’s better to choose the lowest-level interface for maximum flexibility and minimum overhead, and that’s poll_next.

    9. 7

      This pattern is everywhere, from design of frameworks to bug trackers. They start out simple, then accommodate everyone’s edge cases, until users are annoyed with the complexity of everyone elses’ edge cases, and restart the process by writing their simple one again.

    10. 29

      While it is in total hype these days, despite 20 years of programming and having had my first steps in C and C++, I cannot look at a piece of Rust code and say with certainty that I understand what is going on there.

      I beg that people stop making this useless argument. Of course a paradigm shift will be hard for any range of exp.

      In the same line of arguing it could be said that prolog is even more complex than rust because of unfamiliarity. Which is just factually untrue.

      1. 19

        Familiarity with Rust’s syntax hasn’t made it any less of a cognitive load to read for me. It’s not Perl but viewed from outside the bubble it’s a mess of a language.

        (Modern C++ is also a mess. Rust’s only advantage is three decades less cruft.)

        1. 12

          I don’t understand how people can’t understand how hairy Rust’s syntax is.

          1. 8

            Rust’s syntax gets more hairy the closer you look at it. Even its lexical syntax is deeply complicated! I like Rust but I also like perl…

          2. 7

            You say hairy, I say explicit :) Probably just different strokes for different folks. Rust is designed heavily around exposing the decisions to the programmer, and is a major part of why the standard library and much of the ecosystem works the way it does.

            Oftentimes hairy code in older languages is due to legacy features and cruft, but Rust mainly has sheer density of information (turbo-fish operator being the main cruft)

            1. 2

              Exactly! It’s not “syntax”, it’s semantics, of which rust specifies more explicitly. There was even an article showcasing Rust semantics with different syntaxes, and it wouldn’t be better with the same amount of explicitness in JS/Python/whatever-notation either.

              (Can’t find the article now)

              1. 4

                I believe the article you’re referring to is:

                Written by a fellow lobster!

        2. 9

          Perl is more readable, imo

          my @temperatures = (
              { city => 'City1', temp => 19 },
              { city => 'City2', temp => 22 },
              { city => 'City3', temp => 21 },
          my @filtered_temps = grep { $_->{temp} > 20 } @temperatures;
          1. 10

            Whatever language you’re used to reading and/or writing will be the “more readable” language to you. I find Perl to be mostly unreadable, including the example you provided here, but I also acknowledge that if I worked with Perl on a regular basis, that your example would be obvious and pleasant to read. But for me, at this point, that isn’t the case.

          2. 2

            that is fairly obvious. Weird to see grep as a function call (as a non-perler)

            1. 2

              It’s in spirit of grep(1), not exactly the same.

              Perl has a bunch of functions operating on lists which takes an expression or a block and acts on each element in the list in turn. map is the most general, and the List::Utils module offers syntactic sugar for a bunch of others, such as selecting the sum, max, first etc from a list.

              1. 1

                I don’t find it too strange in a Perl context, but it’s an atypical choice because filter is already a well-established name for this particular higher-order function. Though Perl does at least keep the standard name for map.

                1. 2

                  In Perl, “filter” is for changing the source code itself:

                2. 2

                  Maybe it’s an age thing? Perl has had a grep since at least 1999 afaict.

                  1. 3

                    grep (but not map I think) goes back to perl4 if not earlier.

      2. 16

        I’m also worried about anyone who claims that they can tell with certainty what’s going on in any line of C++ code.

        (I write, or rather delete, C++ code every day)

        1. 5

          Well, do you accept “that’s undefined behavior” as “telling with certainty”? 🙃

        2. 3

          Well you can only delete C++ code if it was allocated with new. If it’s on the stack or allocated with malloc things are different.

          1. 2

            Even though it seems like it’s hard to delete C++ code that’s deep in your tech stack, it’s very easy with Nix!

      3. 15

        The Rust version could look like this:

        let temperatures = [
            ("City1", 19),
            ("City2", 22),
            ("City3", 21),
        let filtered_temps: Vec<_> = {
            temperatures.into_iter().filter(|(city, temp)| temp > 20).collect()

        People unfamiliar with Rust’s syntax could be confused by closures or type annotations (especially turbofish), but apart from a few sigils it doesn’t look that much worse than Python.

        1. 13

          The into_iter() and collect() stuff aren’t super obvious either, and on top of that when things can error iterator combinators get complicated in Rust (Python uses exceptions which short-circuit by default). I’m not a Python fan for many reasons, but I think Go is more straightforward than both most of the time. I often find for loops more readable than iterator combinators (of course, Rust has for loops too, but they’re not very idiomatic and there’s a surprising amount of benefit in “everyone does things the same way” IMHO). I still like Rust, but it’s not my get-shit-done goto language.

          1. 1

            I’m not saying Rust is easy, but also it’s not “I can’t tell what’s going on” bad. Writing Rust requires understanding of a bunch of concepts that don’t exist in Python or Go, and that is a barrier. However, when reading it’s not that much worse than Python.

            1. 4

              I don’t really agree. Things like turbofish, clone, Rc<>, RefCell<>, collect, lifetimes, and so on take a fair bit of time to understand, and you encounter these all over idiomatic programs (they’re not “advanced” like Python’s oddities).

          2. 1

            In both the Python & Go variants of the article, one can access fields by name (e.g. temperatures[0].city or temperatures[0]["city"]) and the final filtered result is also a hash with keys of city names. So, I think the Rust version over-simplifies in 2 ways (and is still complicated as you observe, even with the over-simplification). EDIT: Beats me if these are important properties in the larger context of the code he was thinking about, but he does very specifically say “dictionary comprehension”.

            1. 1

              All you need to output a hash is to replace the Vec<_> by HashMap<_, _>.

              The naming part is not complicated but is more verbose: Rust does not have anonymous struct literals, so as in Go you need to define a struct, but then you either need to replace every tuple by a struct instantiation:

              struct CityTemperature {
                  city: &’static str,
                  temperature: f64,
              let temperatures = vec![
                  CityTemperature { city: “City1”, temperature: 10.0 },

              Or you need to define a positional constructor to which you can migrate the tuples:

              impl CityTemperature {
                  fn new(city: &’static str, temperature: f64) -> Self {
                      Self { city, temperature }
              let temperatures = vec![
                  CityTemperature::new(“City1”, 10.0),

              Alternatively / additionally you could impl From <(&’static str, f64)> for CityTemperature, although in this specific case I doubt it’d be worth it.

              he does very specifically say “dictionary comprehension”.

              That is a misrepresentation. The essay says “such as a list of dictionary comprehension”, the intent is to contrast the convenient succinctness of such syntactic sugar to the procedural nature of the Go code.

              1. 1

                That is a misrepresentation. The essay says “such as a list of dictionary comprehension”[..]to contrast

                I disagree that dict builds are not part of the intended contrast. The essay also does map[string]float64 output in its Go. He seems to contrast both type defs & dict builds (& doing both is among the least problematic aspects of the essay).

                Anyway, thanks for the fixes! The HashMap fix, at least, seems easy enough.

                Also, of code posted here so far, @kornel’s Rust is not the only one skipping named fields. @jlarocco’s C++ & Lisp also do, as does @alexshroyer’s Lisp. The Perl, Ruby & Nim seem more directly analogous.

                1. 3

                  I interpreted the code in the article as “return full records where part of the original matches a condition”. But I suppose the original article could have used tuples or arrays, so point taken. Here’s a K variant with named fields (also K is more APL than lisp):

                   x:+`city`temp!(("City1";"City2";"City3");19 22 21)
                  1. 1

                    Ah, yes.. you even said ngn/K. Sorry. K dialects will often win concision battles. I don’t think it’s a very clear article making good points, but as long as people are sounding off with “Favorite PL” solutions, the playing ground should be level-ish. So, thank you!

                2. 2

                  I think for such a short snippet skipping the fields is sensible, that’s definitely how I would do it: there’s only two values in the row and the types are unambiguous, the field names are very temporary and they scope is so short they don’t really matter.

    11. 19

      Usually I’d say definitely not, it’s just bloating the repository, but:

      My background is C++ gamedev.

      in that context this seems more reasonable. C and C++ don’t have a well-known reliable repository for dependencies. Games especially may depend on a lot of custom tools to build.

    12. 2

      It is a remarkable display. I just wish it weren’t so expensive. Also, I found the mention of Al Gore’s legendary three displays rather amusing. We’ve had those since 1999! Most of the traders in the financial services have been doing this for years.

      1. 1

        I wanted something bigger when I upgraded to a M2 studio; it just so happens that Dell released their 6K display earlier this year.

        It was, as the article mentions, less than ideal with an Intel Mac: with a 2018 mini + eGPu the best I could get was 6k@30hz or 4k@60hz

        But with the Mac Studio it’s fantastic. Ridiculously crisp and clear, even compared to the 24” 4Ks (which themselves are crisper than the flood of 4K 27”+ displays that are popular) I previously used (and still use one of, beside the 6k).

        I honestly don’t understand the people who claim there’s no difference and that 4K is “enough”.

        Anyway: my point is if you want ~220ppi at 32” for less than the XDR I highly recommend the Dell 6K.

        1. 1

          I’ve put that extra bandwidth into 4K@120Hz, and now I’m not sure if I want higher resolution, or keep the higher refresh rate. 60Hz is not bad, but going back from 120Hz feels like a slower computer. I guess I’ll have to wait for another bandwidth doubling…

    13. 37

      Highly recommend this talk from Software You Can Love, Vancouver 2023, which dives deeper into this topic: ATTACK of the KILLER FEATURES - Martin Wickham.

      This is something I was hoping Carbon would have addressed in a satisfactory way, but, alas, they do the same thing Zig does, so Zig is once again the language which will have to innovate to solve this problem.

      1. 19

        The video explains that “Parameter Reference Optimization” is a serious design flaw in Zig. It’s a footgun that leads to subtle bugs that can’t be seen by grug-brained developers. It’s unsound. Other system languages like C, C++, Rust do not have this footgun, so people who come to Zig from those other languages will fall into the trap.

        It seems like the obvious fix (but not mentioned in the video?) is to use C semantics by default for passing large parameters (copy the value before making the function call, then pass a reference to the copy). Only use the “parameter reference optimization” when the optimizing compiler can prove that no aliasing will result if the copy is elided. This optimization strategy is sometimes called “copy elision”.

        At the 8 minute mark, a bug in List.add() from the Zig library is shown. The bug is caused by the “Parameter Reference Optimization” feature. To fix the bug requires the developer to add an extra copy of ‘item’ to the function body, which causes a performance hit. If, instead, Zig used copy elision as a sound compiler optimization, then you wouldn’t have this subtle bug, you wouldn’t take a performance hit to fix the bug, and the extra copy of the ‘item’ parameter would only happen in cases where it was needed (the compiler adds the copy at the caller site to prevent parameter aliasing).

        For the other buggy feature in the video, “Result Location Semantics”, I make the same comments. Fixing the bug in rotate (without changing the function signature) requires the developer to make an explicit copy of the parameter before using it, which again is a performance hit. Instead of making people write code like this, you should generate compiler temporaries by default, and elide the copies as an optimization only when it is sound to do so.

        You guys understand these issues better than I do, so what am I missing? Alias analysis is hard, so the standard approach I describe will have worse performance than what the Zig compiler does today (on par with C/C++), and you want better performance than C?

        1. 10

          I wouldn’t say that the issue is solved in other languages:

          • in C++, you can accidentally copy large heap objects. C++ semantics of implicit deep copy is not a useful one.
          • Rust solve correctness problems around parameter passing and aliasing via borrow checker, but that is complicated (at least, as a part of the whole package that is Rust), and doesn’t solve perf problems — Rust does a lot of stack-to-stack copies. It also lacks placement.
          • C doesn’t protect you from aliasing when passing arguments, and has useless copies where the caller has to defensively copy parameter.

          Then, there’s this annoying question of “should you pass small things by reference or by value? And what is small, by the way?”.

          And then there’s:

          My personal take here is that “fn f(param: Foo) passes parameter param by immutable reference” is the right high-level semantics:

          • not mutating is the common case, it shouldn’t require any sigils
          • some things can’t be copied (e.g., their address is meaningful), but you still want to be able to pass them as parameters without extra ceremony
          • whether “foo” is passed by value in registers or by pointer is a detail that should depend on target CPU. Language semantics should allow either.

          The question is rather how to implement that semantics without running afoul of aliasing, which is what Carbon, Zig, and Hylo (formerly Val) are trying to do.

          1. 3

            Rust has added fixes/optimizations to LLVM to eliminate redundant stack-to-stack copies.

      2. 7

        Thank you for the video suggestion, I am not a Zig programmer and the blog post was a bit too terse for me – I could not really follow. The video is super clear and got me very excited about Zig, but folks if you expect a solution besides a clear exposition of the problems you will be disappointed :D we’ll have to wait a while for that I guess.

      3. 3

        I’ve been getting in to Zig in the past few weeks and I really have to say: It’s a joy. Would forcing the compiler to always pass a struct by value with a flag ever be considered? Or does that go harder against the grain of other language principles?

        1. 11

          That kind of solution doesn’t really work because it doesn’t compose … Now libraries would have to add test coverage for being compiled with both flags.

          And if the libraries have dependencies, then they can’t rely on the semantics of those either. You end up with a 2^N test matrix for N libraries.

          The flags also don’t appear in the source code, which is a problem for readability.

          Compiler flags can be used for optimizations, which by definition are semantics-preserving. Pass by value vs. by reference isn’t a semantics-preserving change – it’s basically the opposite semantics!

        2. 1

          Adding a compiler flag to decide semantics as fundamental as “are variables passed by pointer or by copy” sounds like a terrible idea.

    14. 15

      I wouldn’t normally post video content featuring myself but this video was particularly well received.

      Since it was published, people pointed out two mistakes I made:

      1. Go has been able for a while now to export dynamic libraries. My knowledge was from before that time and I also got confused, thinking that you could not export C ABI functions at all, while in fact you can. That said, having a runtime still makes Go not a viable C replacement in the most direct sense of the expression.

      2. Zig used to only support pointer arithmetic by converting the pointer to an int, applying the operation, and then converting it back to a pointer. Since a few months ago, [*]T (and related) started supporting arithmetic. That’s a pointer type that you don’t touch directly often, as you normally would use a slice (ptr + len).

      1. 24

        having a runtime still makes Go not a viable C replacement

        What you mean is that Go has a garbage collector.

        C has a runtime. It is called “the C runtime” and is traditionally abbreviated as “crt”. On Linux systems with GCC installed, there are files named “crt*.o” somewhere under /usr/lib that are part of the C runtime. This is distinct from and in addition to the standard C library (libc). If I compile the C program “int main() { return 0; }” using GCC, then I get about 2K of code and data, even though I’m not calling any library functions. This 2K of stuff comes from the C runtime. [However, note that I’m producing a dynamically linked executable. If I try using ‘gcc -static’ then I get an executable with 780K of code (it looks like glibc), and I don’t know how to make that smaller.]

        Rust also has a runtime, even though the home page claims that it does not! If I compile the rust program “fn main() {}” (which references no library functions) then I get a static executable that is over 300K, and that’s due to the Rust runtime. Supposedly most of this is due to the standard panic handler. Here is some documentation about the Rust runtime:, which says that the panic handler is part of the Rust runtime.

        Zig seems like the best choice if you want to build static executables with a minimal runtime. I compiled “pub fn main() !void {}”, and got a static executable with 660K of code and data. Twice the size of the corresponding Rust executable. A lot of this runtime code seems to involve runtime safety checks and a panic handler. If I rebuild using ReleaseFast then I get 190K of code, which again includes a panic handler. If I rebuild with “zig build -Doptimize=ReleaseSmall” then I get a much smaller static executable with only 6K of code. I don’t know how to make C static executables this small (on Linux).

        1. 43

          The greatest trick Unix ever pulled was convincing its programmers C doesn’t have a runtime.

          1. 1

            I wasn’t sure you were THE Calvin, until now.

              1. 1

                There was a famous Calvin who spent a lot of time worrying about what tricks God was pulling.

                1. 1

                  I believe he was more interested in whatever tricks the Catholic Church was pulling to keep the Word of God from everyone. So quite apropos to the comment from our calvin.

            1. 2

              Wait, does that mean Hobbes is here too?!

        2. 6

          What you mean is that Go has a garbage collector.

          It also has green threads and a multiplexed IO library.

          C only really lacks a runtime when it is used in freestanding mode, when there’s no stdio nor stdlib: no allocator, no signals, no main(), no exit().

          1. 9

            yeah, I really don’t understand people that think it makes sense to downplay the fact that a language like Go can pause execution, realloc an entire green stack somewhere else and fixup all pointers, while being really fixated on crt.

        3. 6

          This answer is like “dry cleaning actually uses liquids”. You’re correct in the strict sense, but also ignoring everything people mean by “having a runtime” in the common-yet-imprecise sense.

          Runtimes of C and Rust (and probably Zig’s too, although I’m unsure about their async) are relatively small, non-invasive, and play nicely with other runtimes in the same process. These languages can produce static libraries that are easily usable in programs written in other languages. That’s generally not the case in languages that are said to “have a runtime”, in the sense that the runtime is substantially larger, more involved in execution of the program, and may cause problems if it’s not the only runtime in the process (e.g. if it needs to control all I/O, or track every pointer).

        4. 3

          Rust also has a runtime, even though the home page claims that it does not! If I compile the rust program “fn main() {}” (which references no library functions) then I get a static executable that is over 300K, and that’s due to the Rust runtime.

          That’s due to the std library, which is linked by default if you’re compiling for a hosted target. It’s not part of the Rust language, which is why people say Rust doesn’t have a runtime.

          A Rust program that just prints hello world is about 9K:

          $ ls -l hello
          -rwxrwxr-x 1 john john 8608 Nov 22 10:23 hello
          $ objdump -d -M intel hello
          hello:     file format elf64-x86-64
          Disassembly of section .text:
          0000000000401000 <.text>:
            401000:	50                   	push   rax
            401001:	48 8d 35 f8 0f 00 00 	lea    rsi,[rip+0xff8]        # 0x402000
            401008:	b8 01 00 00 00       	mov    eax,0x1
            40100d:	bf 01 00 00 00       	mov    edi,0x1
            401012:	ba 0e 00 00 00       	mov    edx,0xe
            401017:	0f 05                	syscall 
            401019:	b8 3c 00 00 00       	mov    eax,0x3c
            40101e:	31 ff                	xor    edi,edi
            401020:	0f 05                	syscall 
            401022:	58                   	pop    rax
            401023:	c3                   	ret    
        5. 2

          The Rust Runtime Environment is entirely optional. You can in-fact compile a Rust program that does not reference any of std, alloc or core. You will be stuck with a very restricted environment (similar to what happens if you do this in C).

          It should also be noted that when you simply compile a Rust program, the stdlib isn’t LTO optimized or otherwise shrunk down (loadbearing * here). You can disable that and only bring what you need. You can also disable the startup wrapper which handles some early init stuff, you can remove the default panic handler entirely and even disable the OOM handler.

          Additionally, running in no-core mode will require you to implement a few core constructs the compiler is looking for yourself, since you’ll be missing quite literally everything that holds rust together (such as operators).

        6. 1

          tcc is pretty good at producing small executables from C code

        7. 1

          C has a runtime. It is called “the C runtime” and is traditionally abbreviated as “crt”. On Linux systems with GCC installed, there are files named “crt*.o” somewhere under /usr/lib that are part of the C runtime. This is distinct from and in addition to the standard C library (libc). If I compile the C program “int main() { return 0; }” using GCC, then I get about 2K of code and data, even though I’m not calling any library functions. This 2K of stuff comes from the C runtime. [However, note that I’m producing a dynamically linked executable. If I try using ‘gcc -static’ then I get an executable with 780K of code (it looks like glibc), and I don’t know how to make that smaller.]

          It sounds like you are describing gcc, not C in general.

          1. 7

            Windows also has a C runtime – and worse, it was not distributed with the operating system!

            It was called msvcrt.dll as far as I remember – Microsoft Visual Studio C runtime. I remember you had to copy it around to get some programs to work.

            This was over 15 years ago – not sure what the situation is like today.

            edit: To clarify, C does have a runtime, but you don’t have to use it. Kernels and Windows user space don’t, but Linux user space does.

            • The Windows kernel doesn’t use the C runtime, as far as I know.
            • Many/most Windows user space apps don’t use the C runtime. They use C APIs provided by Windows.
              • But portable ANSI C applications often use the C Runtime DLL I mentioned.
            • The Linux kernel doesn’t use the C runtime.
              • For example, printf() is part of the C runtime, but the kernel doesn’t use it. It has its own string formatting routines.
            • Linux user space uses the C runtime – that’s what GNU is – most apps and CLI tools on Linux link with GNU libc, etc. Or musl libc.

            Another consideration is that modern malloc()s have a lot in common with garbage collectors. They have some unpredictable performance characteristics, similar to a GC runtime.

          2. 4

            That’s a fairly typical way for C compilers to link in the startup and exit code.

            1. 1

              Recognizing that this is a tangent, how many C compilers are in your dataset to judge what’s “typical”? Do clang, tcc, kenc, cproc, lacc, and scc do this too?

              1. 9

                I don’t know about hobbyist C compilers, but what do you think calls main? A crt0.o that contains things like _start is pretty standard for mainstream Unix C compilers.

                1. 1

                  Well there are only 2 mainsteam Unix C compilers. So I guess by “pretty standard” you mean “universal”?

                  1. 3

                    I’m not sure what you’re trying to say here. Are you implying that e.g. tcc, kenc, cproc don’t have any startup or exit code?

                    C programs expect to get argv, have stdout, and atexit working. These things are part of C and its standard library, and compilers need to insert code that makes them work.

                    1. 1

                      I’m asking if @calvin is referring to the two mainstream Unix C compilers, gcc and clang, in which case his statement could be strengthened from “pretty standard” to “universal.”

                1. 1

                  So what were you saying is fairly typical?

          3. 4

            I’m describing the situation when you use C to write programs that run under an operating system like DOS, Linux, Windows, MacOS, etc. If your C program has a function called int main(int argc, char **argv), then there is a C runtime that provides the operating system entry point and calls main. The ISO C standard calls this a “hosted execution environment”. The situation where C might not have a runtime is called a “freestanding environment”.

            1. 2

              Thanks for clarifying the meaning of “runtime;” I was not aware it included things like startup and malloc.

      2. 4

        Zig used to only support pointer arithmetic by converting the pointer to an int, applying the operation, and then converting it back to a pointer.

        Does zig not model pointer provenance than? There has been a lot of discussion in the rust community how about pointer/int cast break the compilers ability to reason about provenance. As I understand it, you can have either pointer/int casts or pointer provenance, but not both.

        1. 7

          As I understand it, you can have either pointer/int casts or pointer provenance, but not both.

          That is not quite the case. There are several proposed provenance models for Rust, C and C++, all of which have to have some way to deal with integer/pointer casts.

          This paper gives a good overview:

          tl;dr there are two categories of approaches: “PNVI” (provenance not via integers) and “PVI” (provenance via integers).

          In PVI, integers carry provenance. So if you cast a pointer to an int and then back, you retain the entire chain of custody and the resulting pointer works exactly as the original did. However you now run into some tricky questions like “What is the provenance of a + b? Or a ^ b?”. This is (from what I can tell, but I’m no expert on this) what CHERI C does: Their uintptr_t retains provenance, and they make some choices about what that means for integer math.

          In PNVI, integers do not carry provenance, so you need another way to make pointer->int->pointer casts work. The current favorite seems to be “PNVI-ae”, for “address exposed”, where casting a pointer to an int leaks its provenance to “the environment”. Casting back to a pointer will look for an exposed provenance for that address and if there is one, you get that (and an invalid pointer otherwise). This avoids the tricky questions of PVI and allows a bunch of patterns that PVI doesn’t, such as the infamous XOR-linked-list. However, it is also extremely painful and slow to implement on platforms that physically manifest provenance such as CHERI. For regular optimizing compilers however, it’s not a big problem: their aliasing/provenance analysis already has to be able to cope with pointers that “escape” their analysis horizon, be it due to FFI, inline asm or (and yes, this is literally called out as allowed by the C standard) round-tripping pointers through the filesystem via fprintf("%p") and fscanf("%p"). PNVI-ae at worst inhibits their ability to optimize code that does a bunch of int/pointer casts.

          Now for Zig, if they adopt these rules as-is, this might mean a more substantial pessimization if int/pointer casts are more idiomatic and used in many more places. If more or less every pointer’s address is escaped, you do lose most of the optimizations allowed by provenance.

          There is no “Zig Memory Model” yet from what I can tell, but a bunch of discussion:

          1. 3

            Their uintptr_t retains provenance, and they make some choices about what that means for integer math.

            Note for those unfamiliar with CHERI: this means uintptr_t is 128 bits large when holding a 64 bit address.

            Also, for languages that don’t need to be backward-compatible with code that casts freely between ints and pointers, there is the option to disallow int->ptr casts completely, and instead require supplying provenance explicitly when you want to construct a pointer from an integer. E.g. new_ptr_with_derived_provenance = old_ptr.new_from_address(some_int).

    15. 63

      I love this website. First thing I see? Code. Amazing. It is insane how some languages hide their code from me.

      And then I scroll down and… a repl? OMG. Yes.

      And then I scroll down more and there’s more code??????? Did an actual developer build this website????

      I remember when the Rust website was good and actually showed you code. Now it’s useless. versus

      I feel like you’ve really hit the best of “simple, stylistic, and shows me the fucking programming language”.

      1. 31

        They’re clearly marketing to different audiences. Rust doesn’t need to sell as a technology to engineers anymore, it needs to sell as a product to businesses. Most tech marketing sites undergo this transition the more they grow, in my experience

        1. 11

          I think that’s not true at all. What does it that even mean, selling as a product to businesses? The way you do that is by targeting their engineers, the people who make technology choices. Ground up is by far the best strategy for a language, top down feels ridiculous. If you are actually in a position where you’re talking to bunsinesses about funding you’ve already sold to their engineers and your website is irrelevant - at that point they’re going to want to hear about what the language needs in order to succeed because the org is bought in.

          Beyond that, this website contains all of the things that the Rust site does, but also code. It’s proof that you don’t need a tradeoff here - literally just put a damn snippet of the language on the site, that’s it.

          The reality is that language decisions are made almost exclusively when the company starts. By engineers. Companies only ever constrain their internal support languages in the future, and it’s engineers who push for language support when that happens. Selling a language to a business through the website is just a nonsensical concept on its face.

          Roc makes it clear, in text, without code, “here is why you would choose Roc” but also it shows code, very concisely right at the top.

          1. 20

            Cool! I think it’s true at all :)

            The idea is that rust has already captured he interest of engineers. Word of mouth, blog posting, conferences etc. “Everyone” knows about rust at this point. Engineers are building hobby projects and FOSS in jot already.

            What I mean by selling to a business is that ultimately management aka business needs to approve and buy into rust otherwise it will remain in hobby project obscurity.

            Some engineer or group of engineers says to their boss “we want to use rust for project X” and their boss loads up the landing page because while they’ve heard of Java and python they haven’t heard of rust. This is why the first words you see are ”reliable”, “efficient”, “safe”, and “productive”. They see big recognizable company logos and testimonials by well known or high-positioned leaders in the industry.

            For what it’s worth I also think it’s a good idea to put code on the homepage. You can do both, but this is why you see tech marketing sites evolve to become less and less technical over time.

            1. 5

              The idea is that rust has already captured he interest of engineers.

              The reality is that Rust is something engineers have heard of but largely know nothing about, other than some high level stuff like “fast”. A small portion of companies actually use Rust, a small portion of engineers have ever touched it.

              Some engineer or group of engineers says to their boss

              By far the easiest way to gain adoption is through startups or smaller companies where the engineers are already the ones who decide on language. After a few thousand employees companies always end up with someone locking down the decision making process and tiering the language support levels - at that point, again, it’s up to the engineers to work through the process to gain support. I’ve never heard of a situation where a top level manager had to go to a language website and make the call to use or not use a language, isn’t that sort of an absurd idea? But, again, Roc shows that this is not an “either, or” situation.

              This is why the first words you see are

              Roc shows “fast, friendly, functional” - I’m not saying you can’t have words. Roc shows that you can have these great, high level blurbs here.

              They see big recognizable company logos and testimonials by well known or high-positioned leaders in the industry.

              Roc also shows this. Again, I am not against the idea of content other than code on the site, I’m saying it’s ridiculous to have literally no code.

              For what it’s worth I also think it’s a good idea to put code on the homepage.

              Then we agree on the main issue here.

              1. 10

                I’m not debating your tastes here? Just explaining why sites follow this trend. Hope this helps

                1. 3

                  OK and I’m explaining why the reasoning isn’t sound and it’s a bad idea.

              2. 5

                I think the point is that Rust is at a stage where it’s marketing itself as an industry-ready language and ecosystem that you can build actual products on without worrying about running into a horrible bug that dooms your start up. At that stage, showing code doesn’t make too much difference, because the “industry-ready” aspect of it has so much more going for it than the surface language syntax you can showcase in a landing page.

                Roc, however, is at the stage where it needs to capture the interest of curious engineers that might be interested in the language itself, without worrying about the practical applicability of it too much. (BTW, I don’t want to imply that Roc can’t be used for anything practical at this point, I don’t know, but clearly I wouldn’t bet my start up on it and I doubt anyone expects that at this stage).

          2. 14

            What does it that even mean, selling as a product to businesses? The way you do that is by targeting their engineers, the people who make technology choices. Ground up is by far the best strategy for a language, top down feels ridiculous.

            Man, as a person who sells languages (mostly TLA+) professionally, I wish that was the case. Selling to engineers is way more fun and feels more comfortable. But I’ve had cases where an entire team wanted to hire me, and cases where the team never heard of TLA+ but a manager or CTO wanted to hire me, and the second case way more often leads to business.

            1. 3

              Which is the norm. Because our peers unfortunately mostly have very little power to make decisions.

            2. 2

              I’m not saying that it literally never happens, I’m saying that by far language adoption at companies is driven by engineers - either through external forces (engineers building tooling/libraries in their free time, engineers starting companies with that language, etc) or through direct forces (engineers advocating for the language at that company).

              I’m sure there are plenty of extreme cases like Oracle convincing schools to sell Java, or whatever, where this wasn’t the case.

              It’s all moot since Roc’s language clearly shows that a website can have code and high level information, and meld them together well.

              1. 1

                I’m not saying that it literally never happens, I’m saying that by far language adoption at companies is driven by engineers

                The guy literally said he sells languages professionally, and he’s telling us it’s driven by management, not engineers. Do you also sell languages? Or better yet, have some source of information which trumps his personal anecdotes?

                In what way is Oracle an extreme case?

                I don’t disagree that you can still include code, but even the high level blurbs will be different when selling to businesses, at which point, if the copy isn’t selling to developers, you might as well better use the space to more effectively sell to businesses where the code would have been (even literally empty space for a less cramped, more professional looking page).

                Engineers care more about “fast (runtime)” and certainly “friendly” than most businesses do, and businesses care a lot more about “safe” than a lot of programmers do; “fast (compile time)”, ”reliable”, “efficient”, and “productive” may be mixed. Engineers also care about technical details like “functional” a hell of a lot more than businesses do, because they can evaluate the pros and cons of that for themselves.

                I guess it’s much more effective to be very targeted in who you’re selling to, unless both profiles have equal decision making power.

                1. 1

                  The guy literally said he sells languages professionally, and he’s telling us it’s driven by management, not engineers.

                  And? I know almost nothing about them. They’ve apparently been pushing TLA+, an incredibly niche and domain specific language that the vast majority of developers never even hear of and that targets software that lives in an extreme domain of correctness requirements. It’s nothing at all like Rust. Maybe they also sell Python, or comparable languages? I don’t know, they didn’t say. Is it even evidence that selling TLA+ this way is the most effective way?

                  Or better yet, have some source of information which trumps his personal anecdotes?

                  Years of experience in this field as an engineer, advocating and getting a large company to include languages in their accepted policies, generally just being an engineer who has adopted languages.

                  unless both profiles have equal decision making power.

                  I do not buy, at all, that for a language like Rust (frankly, I doubt it’s the case for any language, but I can imagine languages like TLA+ going a different route due to their nature and lack of appeal to devs) that it’s not wildly favorable to target developers. I’ve justified this elsewhere.

          3. 5

            The current Rust website is not ideal, but I think the general principle of convincing devs vs businesses holds.

            Developers are picky about surface-level syntax. The urge to see the code is to judge how it fits their preferences. I see lots of comments how Rust is plain ugly and has all the wrong brackets.

            Business-level decision makers don’t care how the code looks like. It can look like COBOL, and be written backwards. They ask what will happen if they spend resources on rewriting their code, and that better improve their product’s performance/reliability/security or devs’ productivity, not just make a neat-looking quicksort one-liner.

            1. 2

              Of course the one who cares about code readability is the one who is going to read the code. What’s wrong with that?

              1. 3

                Readability is subjective. Syntax must be functional and clear, but devs will care about details beyond that, and die on hills of which brackets it should use, and whether significant whitespace is brilliant or terrible.

                Compare “The app is slower and uses more memory, but the devs think the code is beautiful” vs “The app is faster and more reliable, but the devs think the code’s syntax looks stupid”. If you want to convince product owners, the syntax is an irrelevant distraction, and you must demonstrate outcomes instead.

                In web development there’s also a point of friction in developer experience vs user experience. Frameworks and abstraction layers have a performance cost (tons of JS), but not using them is harder and more tedious.

                1. 2

                  While readability is subjective to a certain degree, it stems from human psychology, which is based on how the real world works.

                  In the real world, things that are inside a container are smaller than the container, so if delimiters don’t visually encompass the text they contain, it looks counter-intuitive.

                  And things which are physically closer together are perceived as being more strongly associated, so a::b.c looks like it should be a::(b.c), while in fact it’s (a::b).c.

                  Performance and safety has nothing to do with syntax. It would be entirely possible (and not difficult) to create a language exactly like Rust, but with better syntax.

                  1. 4

                    I think you’re confirming what I’m saying. You’re focusing on a tiny nitpick about a part of the syntax that doesn’t cause any problems.

                    And yet you’re prepared to bikeshed the syntax, as if it was objectively wrong, rather than a complex tradeoff. In this case the syntax and precedence rules were copied from C++ to feel familiar to Rust’s target audience. If you just “fix” the precedence, it will require parens in common usage. If you change the separator symbols, you’ll get complaints that they’re weird and non-standard, and/or create ambiguities in item resolution.

                    Showing code for a language invites such shallow subjective opinions, and distracts from actually important aspects of the language.

                    1. 1

                      You’re focusing on a tiny nitpick about a part of the syntax that doesn’t cause any problems.

                      Code readability is a problem, especially in a language that frequently features complex declarations.

                      And yet you’re prepared to bikeshed the syntax, as if it was objectively wrong

                      It is objectively wrong. < and > are comparison signs, not brackets. (And Rust does also use them as comparison signs, creating even more confusion.)

                      If you change the separator symbols, you’ll get complaints that they’re weird and non-standard

                      I think using \ instead of :: would be fine in terms of familiarity. Everyone has seen a Windows file path.

                      1. 5

                        This is literally bikeshedding. Don’t you see the pointlessness of this? If you changed Rust to use your preferred sigil, it would still produce byte for byte identical executables. It wouldn’t change anything for users of Rust programs, and it wouldn’t even change how Rust programs are written (the compiler catches the <> ambiguity, tells you to use turbofish, and you move on).

                        Rust has many issues that actually affect programs written in it, e.g. bloat from overused monomorphisation, lack of copy/move constructors for C++ interop and self-referential types, Send being too restrictive for data inside generators and futures, memory model that makes mmapped data unsafe, blunt OOM handling, lack of DerefMove and pure Deref, lack of placement new for Box, etc. But these are hard problems that require deep understanding of language’s semantics, while everyone can have an opinion on which ASCII character is the best.

                        Even Rust’s syntax has more pressing issues, like lack of control over lifetimes and marker traits of async fn, or lack of syntax for higher-ranked lifetimes spanning multiple where clauses.

                  2. 1

                    You’re thinking of physics, not psychology. Concrete syntax is one of the least important parts of a programming environment, and we only place so much weight onto it because of the history of computers in industry, particularly the discussions preceding ALGOL and COBOL.

      2. 5

        And they actually explain what they claim with “What does X mean here?”.

      3. 3

        There is also, linked (not prominently) from the bottom of It doesn’t have the “eating your laundry” footnote, but the code runner works, unlike in

    16. 1

      Very interesting, and I look forward to seeing the larger project you alluded to. Was the use of libc::write in the generated code just to avoid bringing in Rust’s formatting and panic machinery, or was there another reason behind that?

      1. 1

        I would assume that bit only gets called when std::mem::transmute returns null for some reason, so at that point the whole Rust runtime is not to be trusted. Not sure what would cause transmute to return null though.

        1. 1

          By definition transmute can’t change the value. It’s not a conversion function, it’s a type-system cheating function.

          Also, there’s cast() on pointers that you could use to remove all transmutes and casts except the one for the fn() type.

      2. 1

        Was the use of libc::write in the generated code just to avoid bringing in Rust’s formatting and panic machinery, or was there another reason behind that?

        Yep, exactly – if we somehow end up in a wrapper without an underlying handle to call via dlsym, then there’s a good chance either (1) the runtime is broken or (2) the user hooked something so intrinsic to the Rust runtime that their hook ran in “life before main.” In both cases we can’t rely on the panic or formatting machinery, so we have to abort directly.

    17. 3

      Track how the ecosystem adopts the 1.0 version: (probably bookmark this and check later :)

    18. 6

      I’m not sure about the fors, because that’s the internal iteration vs external iteration tradeoff. If you push fors down, you can’t “zip” two iterations into one. This should probably be decided on case-by-case basis if there’s something special to do before/after the loop (e.g. locking a lock once rather than per iteration).

      With ifs, I’m also wondering about cases where the else case needs to be handled frequently, and in a consistent way. If you have a Walrus without a name, you wouldn’t want else "Anon", else "(null)", else "???" ad-hoc fallbacks all over the place.

      1. 2

        Automatic inlining can also nullify the point about reducing the number of calls to the child function. I’d consider it simpler and more composable to locate the loop outside the child function—in many modern languages, a collection function ought to do the looping for you. Consider pushing the loop down when profiling identifies a slow spot.

    19. 12

      POSIX signals are a gift that keeps on giving.

      1. 2

        There has got to be a decent alternative. I hate to ask it, but what does Windows do?

        1. 8

          Windows uses structured exception handling (SEH) or vectored exception handling (VEH) for this. SEH was covered by a bunch of patents owned by Borland and so *NIX systems didn’t copy it (they expired about 10 years ago, so probably could). There are three bits to SEH:

          First, there are some C extensions so that languages that don’t have native support for exceptions can handle them. You use __try / __except / __finally in C to describe SEH blocks.

          Next, there’s an ABI and unwind library. Unlike the Itanium unwinder, SEH was designed to avoid needing any heap allocations. When an SEH exception is raised, the exception object is allocated on the stack and then passed down to the unwinder. The unwinder then walks up the stack[1] and, for each frame with cleanup (or catch) behaviour, it calls a funclet. This is a function that runs with a pointer to the stack frame for the function that it’s cleaning up. If the funclet is a catch, then it may move the exception object into the space reserved to hold it in the catching stack frame.

          Finally, there’s support in the kernel to push a call into the library routine that starts this process on top of the stack. For anything that would be a signal on *NIX, Windows will throw an SEH exception (if you want to see some terrifying code, take a look at what the Windows Terminal does to handle ^C).

          SEH is great for a lot of things, but some things are very hard. For example, libsegv on *NIX has some abstractions that let you catch segfaults and lazily provide a mapping, so that you can do things like userspace distributed shared memory and other fun hacks. This is not possible with SEH because code gets to handle exceptions from the leaf stack frames up, but you want to handle these things with some global code. VEH adds a more signal-like model, where you have a stack of handlers that are registered globally. These run before SEH handlers and have the option of either resuming execution or continuing to search for another handler. As far as I am aware (I’ve not looked at this bit of the code[2]), these are a pure userspace abstraction. The kernel just invokes the userspace unwinder and it checks for VEH handlers then SEH ones.

          The C++ exception mechanism is layered on top of SEH (the SEH handler that you register is provided by the C++ runtime and does exception type checks before deciding to continue unwinding or resume from a handler). For Objective-C, we delegated to this and made Objective-C exceptions look like C++ ones. This involved some fun on 64-bit Windows. The C++ exception is supposed to embed the set of things that it matches. In C++, that’s a static list, but in Objective-C reflection APIs may change it dynamically. That’s fine, because we can construct it in the throw function. Unfortunately, the 64-bit ABI decided to save space and so expects all of the types to be 32-bit displacements from the library base. We ended up using alloca to allocate them all on the stack and then storing displacements from an arbitrary point on our stack frame.

          The nice thing about SEH is that it gives a unified mechanism for handling kernel and userspace errors. It requires a bit of coupling between userspace and the kernel (the kernel expects userspace to have a valid stack pointer at all times and has to know the address of the SEH entry point - on *NIX the signal trampiline is injected by the kernel, via a VDSO or some more ad-hoc mechanism).

          The SEH unwinder is generally nicer than the Itanium / DWARF model because it doesn’t need any heap allocations. The Itanium spec recommends that the C++ runtime has a small pool of buffers to use for allocating out-of-memory exceptions.

          I don’t believe SEH can handle out-of-stack cases. I think the Windows ABI avoids this by doing a one-page-displacement stack probe on function entry and throwing an out-of-stack exception if it’s not possible to move the guard page down, so you always have at least one page for running exception handlers. This is probably cheaper than a signal stack, but means that you must be careful to limit stack usage in cleanups.

          [1] On 32-bit x86, this used an on-stack linked list, on 64-bit x86 and Arm, it uses a table-driven unwinder similar to the DWARF model. The x86 model is faster to throw exceptions, the other model is faster if you do not throw exceptions. Since exceptions are supposed to happen only in exceptions situations, it makes more sense to optimise for the exceptional case.

          [2] The bits of the code I did look at had some very interesting comments. There’s actually machinery hidden in the Windows userspace runtime bits for a full suite of Lisp-style exceptions (unwinding, resumable, and restartable). I’ve never seen anything use more than a tiny fraction of this.

          1. 1

            There’s a very deep delve into the x86-64 EH support in this series of blog posts, in case you’re interested:

          2. 1

            If the funclet is a catch, then it may move the exception object into the space reserved to hold it in the catching stack frame.

            How does that work when the exception object is a derived class of the catch declaration?

            1. 1

              I have no idea. That may involve promoting to the heap and is handled by the C++ part of the runtime. The generic SEH mechanism doesn’t know anything about subtyping.

    20. 3

      I’m curious what use case rust scripts will fall in to. I recently tried to use nu shell for anything slightly more involved than basic command aliases and it feels nice.

      Rust on the other hand is more mental overhead so not sure what task would fit being just a script VS a normal crate.

      1. 4

        Probably just a thing for Rust projects to avoid bringing another language. Rust projects usually support native Windows, so even relying on bash isn’t that simple.

      2. 2

        Yeah, it seems really handy to be able to have a whole rust script with its dependencies in a single file but at the same time I can see that file ending up quite large (or with a pretty major dependency graph) to accomplish a lot of “mundane” tasks. If the build artifact for a given script can be (or is) cached somewhere that would at least avoid the compile time of an easily achievable 100+ dependency graph.

        1. 2

          I believe cargo script already keeps a target dir around per script (path) right now with all the same caching of normal rust projects.

          So the first time you build a script (or when you change it’s name/location) it might take a minute to build the dependency graph, but after that it should be fast.

        2. 2

          I’d imagine once you’re at the 100+ dep graph you’re reaching beyond a simple script. I.e. just to see where it lands I added a few common deps for what the kind of work I’d normally do in a script, but taking advantage of some of Rust’s excellent deps (plus the standard library).

          • clap: argument parsing (+3 transitive deps)
          • dirs: XDG dir handling (+3 transitive deps)
          • indicatif: beautiful progress bars (+5 transitive deps)
          • walkdir: parallel directory walking (+1 transitive dep)

          I imagine for script like functionality you could do without the default features of these, which brings the total dep list to 15 (libc is used by two and thus deduplicated) and compiles on my machine in ~1.02s. Considering the capabilities those deps bring I’d say thats a worthy trade-off if you’re already in the realm of looking into using Rust for scripting.

          This isn’t to say you couldn’t reach a script with a massive dep list, just that I think doing so should be an anti-pattern most of the time?

          1. 4

            xshell and xflags are really nice for Rustic scripting.

            1. 1

              Those look dope! Those seem like a nice balance of still using rust but just getting something running pretty fast.

              Would be nice to have a helper crate that just re-exports those and anyhow so with one dep specified you can make nice scripts

          2. 1

            Sadly, it is just not complicated or difficult to add a lot of dependencies to any rust program, no matter if it is a single file or multiple files. I know that we programmers always say that large, complicated things should be split into smaller less complicated things, because it is an easy to understand ideal that I agree is self evident, but it rarely seems to me like the reality of software development is matching any of these kinds of ideals, truisms or best practices. in (at least, my a friend’s) reality single files regularly bloat to tens of thousands of lines and the average person is easily cruising up to and past the 100 dependency threshold without ever thinking once about the licensing requirements in a single crate in their layer cake supply chain.

            I personally will try to stick to this ideal of minimizing unnecessary dependencies (and not violate licensing requirements of freely shared software) but I do think parts of Rust are on to something in that they better equip people to do the right thing in their programs, it’s just that I think dependencies is a place where the design with Cargo and all can really seem to encourage some design antipatterns generally. Give a guy a hammer…