Threads for adaszko

  1. 4

    I love Rust (at least the parts of it that I know and use) and I’ve had a blast over the years building small CLI tools with it.

    However, I haven’t yet found any joy in working on larger projects with it. There’s a deep satisfaction in knowing you’re safe from memory issues, but there’s also a deep anxiety that one day I’ll come across a feature or qwerk that I just can’t wrap my head around and it blocks my progress indefinitely.

    I have a feeling that I’d immediately find the joy with a memory safe Zig.

    1. 3

      but there’s also a deep anxiety that one day I’ll come across a feature or qwerk that I just can’t wrap my head around and it blocks my progress indefinitely.

      I share the sentiment of feeling blocked indefinitely sometimes. Rust’s type checking (and not only Rust’s) lacks incrementalism in the sense that with when you get a runtime error, you have all sorts of tools to help you narrow down the issue: print(), debuggers, deleting parts of the program and seeing if the issue persists, etc. With type checking OTOH, you just get the nice, terse No ;) with some not always helpful error message. One way to address this issue would be of course to make the compiler explain its reasoning step-by-step so that you can see where your reasoning diverges from the compiler’s but that’s a tall order. If it comes to lifetimes, I think there should be a way to dump the code with all the lifetimes inferred by the compiler so that we can compare them with our expectations (that would have been really helpful for me at the learning stage, back when I hadn’t memorized all the lifetime elision rules). Another way to make debugging type checking errors easier would be to get more incrementalism by way of making it possible to break down any code into a series of easy-to-comprehend partial expressions of the form let var_introduced_purely_for_comprehension_purposes: ComprehensibleType = short_expression that later combine into the final, desired expression that’s trivially of the right type. The reason we can’t always do that today is that some types are so big that are better left untold, like a combination of iterators (impl Trait types do not always work or they don’t constraint the type enough).

      Another major complicating factor is the return type polymorphism (along with the turbofish). Ideally, method dispatch would only work one way but it’s obviously too late for that (that’s something Zig has as a design goal BTW). The next best thing I think is to add support for debugging it in the IDE: something like a “get the type of the selected expression” functionality in rust_analyzer. Type ascriptions could help too as they’re more flexible than turbofish.

      1. 2

        I understand your worry, but I don’t think it’s a big deal. There’s a helpful Rust forum that can get you unstuck if you’re missing some obscure case syntax or a trait trick. If you run into something that the compiler just can’t understand, you always have the unsafe hammer to push though.

        Also note that people usually get stuck in Rust when they are pushing the limits of zero-cost abstractions and static safety guarantees. You can back off a bit and write Rust in a “dumber” way the way you’d write C.

        1. 3

          A particular scare I had was a codebase that relied quite heavily on a third party library implemented as a macro on all their fundamental types. I had to add something that I thought would be trivial and would interact in a very minor way with what that library was doing and quickly hit that “nuh uh” fighting the compiler stage.

          I remember seeing a full screens worth of cryptic type information and trying to dig into the underlying library to get an understanding of what was happening. It was extremely clever stuff, but I think that’s the first and only time in my career that a multiple day effort resulted in no progress at all.

          I reached the conclusion that I had to choose between:

          1. Ripping out that library, which was potentially a multiple week effort
          2. Digging in further and hoping that I’d just missed an easy solution
          3. Abandoning that feature, and having a little cry

          Unsafe would’ve meant pretty much the entirety of the codebase was unsafe, I don’t think that would’ve even helped if memory serves (it was a long time ago). I think the major disappointment was realising that after building so much work on top of a third party library we were completely oblivious as to how that was going to restrict our future work, is it realistic to expect a full audit of macro-based code before adopting it? It probably should be if you’re trusting months or even years of effort building on top of it.

          I ended up being instructed to go with improvised option 4 which was a half-baked and ugly work around version of the feature, and had a little cry.

      1. 1

        Neat stuff. Is that how the deadlock detection in e.g. Go works?

        1. 4

          It looks very similar to the WITNESS subsystem in FreeBSD. If you’ve ever run a non-release kernel and see ‘Lock order reversal in…’ messages show up in dmesg or on the system console, this is where they come from: the system tracks which mutexes are held when another is acquired and then dumps a stack trace any time they are acquired in the reverse order (e.g. if the first acquisition of B has A locked and later code tries to acquire A while holding B locked then you’ll get a report).

          There are a few situations in which this can provide false positives. In particular, a couple of things have (for legacy reasons that are avoided in new code) different lock order rules during initialisation and normal operation. In these cases, the first lock order is A then B, the later one is B then A, but all code that acquires them in the A then B order finishes running before the code that acquires them in the B then A order. In general, it’s easier to ‘fix’ these cases than try to validate that the inversion is safe.

          There was a GSoC project a long time ago to port WITNESS to the userspace pthreads implementation. I don’t know what happened to it, I don’t think it was ever merged.

        1. 2

          I really wish someone would port Hypothesis’ stateful testing to Rust’s proptest or quickcheck (https://hypothesis.works/articles/rule-based-stateful-testing/).

          Consider a map/dictionary.

          With the style of testing presented here in the linked article (https://medium.com/@tylerneely/reliable-systems-series-model-based-property-testing-e89a433b360), you basically say “here’s a random series of inserts and deletes; hopefully some deletes line up with the inserts”. With Hypothesis style testing, you can generate deletes knowing what entries the map has.

          1. 5

            In the article I point out that there is a PR open on proptest (https://github.com/AltSysrq/proptest/pull/257) that adds something very like the EQC Statem, stateful property based testing. This means you can infact generate deletes that are in the model.

            In the case in the article, testing sets, we instead use low cardinality that ensures collisions over time. Not included in the article is that the test also gathers statistics about which operations were run, what percentage of removes were meaningless etc.

            I have since ported the test to use proptest stateful testing, and it was equally effective, and much nicer to write (especially for someone who still prefers EQC Statem!)

            1. 2

              Oh cool I missed that. I’m excited!

            2. 1

              It took me a while to realize that what you and the articles are talking about is called Simulation Testing in other circles. Agreed, those are very worthwhile tests to have.

            1. 5

              when is python losing the GIL?

              1. 4

                Until somebody comes up with a way to somehow get C extensions to work without the GIL or make the old C extension interface that requires the GIL a second-class citizen, probably not any time soon. Sam Gross’s nogil branch looks very promising, but it’s not there yet.

                I’m looking forward to it, but it’s going to break a lot of people’s code, because far too many people implicitly depend on the GIL in their pure Python code. A good example is all the people who mistakenly use collections.deque for interthread communication, who are going to get a rude awakening when their code on a GIL-less Python interpreter starts having lots of race conditions.

                1. 1

                  There was a relatively promising update on nogil efforts built on Python 3.9. I wonder what happened since–if core devs are interested in the pieces being brought up to date with current Python and landed.

                  1. 4

                    It looks like at least some ideas from the nogil effort are making their way into CPython. There’s been a PEP recently targetting 3.11 about introducing immortal objects. That’s one of the ideas from the nogil fork.

                    Personally, I think multiple interpreters combined with per-interpreter GIL is a more feasible path forward as it doesn’t entail breaking (as many) C-extensions.

                  1. 2

                    Or from a different angle, tree-sitter:text-editors::llvm:compilers

                    1. 2

                      There is also zed which is still in early development. Written in Rust by the creators of Atom, the Real-Time collaboration looks pretty interesting!

                      1. 2

                        Ah, looks like the zed team also built tree-sitter?

                        1. 2

                          Not only the name is almost the same, in fact zee’s feature list ends with: “a pragmatic editor, not a research endeavour into CRDTs” which -I suspect- is a clear reference to zed.

                          1. 2

                            I took that as more of a dig at xi-editor, but I’ve known about that longer than I’ve known about zed.

                        2. 1

                          IMO emacs is about the introspection and customization more than the keybindings (which is why I use evil-mode :) ).

                          It’s definitely interesting that both helix and zee are terminal editors. I think that prevents you from doing a lot of ‘cool’ things like widgets with arbitrary graphics or embedding a markdown preview, but I think the only ‘reasonable’ choices for cross-platform are either Qt or Electron. And if you want people to be able to make widgets easily without having to learn a bespoke scripting system, you’re basically stuck with Electron. :/

                        1. 12

                          Using a TV as monitor has other pitfalls. You’ll want to verify that it does full resolution, decent refresh rate and no chroma subsampling all at the same time. I bought a 4k TV a few years back with the intention of using it just as a monitor, and had to fiddle around with the configuration to get it to 4k, 60 Hz and 4:4:4. Probably unrelated, but it also had some really bad single-frame bugs (like flashes of white/red lightning), and I ended up returning it.

                          1. 5

                            For me, resolution is very important — “retina” displays are like SSDs in that after using one I couldn’t go back. I use a 27” 4K monitor, but I wouldn’t want one much larger than that, at least not as my primary display.

                            1. 2

                              Don’t forget that ‘retina’ is a property of two things: the pixel size of the screen and the viewing distance. A retina phone screen needs a much higher pixel density than a retina desktop display because you hold it a lot closer to your face. I have a 43” 4K monitor at work and the main advantage is that I need to sit further back from it to be able to fit it in my field of view than I would with a 27” display, which reduces eye strain (it’s also great for video calls, since I can sit back a bit and the other person is about the same size that they’d be if they were sitting across the table from me).

                              1. 1

                                Out of curiosity: what’s the smaller of the two main dimensions of your desktop? I’d go for a some huge screen but that’s my limiting factor.

                                1. 2

                                  I don’t have a sensible measuring thing to hand, but using a piece of A4 paper I’d estimate it as 50-60cm (less than double the height). It’s sufficiently small that it fits in my field of view if I’m sitting a good distance back, it’s too big if I sit too close, which helps me to sit more sensibly.

                              2. 1

                                Agree, I have a 32” (actual) 4k display, which is really pushing it and I mainly use it because the panel is fantastic with a really big gamut.

                            1. 9

                              I’m not into keeping up, but I do like to make stuff occasionally, and stay away from web because it’s too painful finding out how to do anything without being immersed in it day to day.

                              I’ve used Phoenix LiveView (for Elixir) recently, however, to make a Wordl-alike. It was a great experience and I didn’t have to write any JavaScript! I still had to write some CSS but it has variables now (you can see how little I keep up) and that made life a lot easier.

                              1. 5

                                I just want to highlight how great the LiveView concept is. As a gist: HTML is generated on the server and synced with the browser via diffing. The server is the authority, and all events are sent to the server for processing.

                                That approach solves a great deal of issues: No need to write REST API endpoint for every feature, no need to serialise data structures, no need to introduce a separate build for the frontend, easy testing (no need to spawn browsers), …

                                1. 2

                                  Those are indeed substantial advantages. What about offline SPAs? Is there any way to keep the app running if there’s no network connection?

                                  1. 2

                                    No because it’s all requests to the server. I’ve seen some attempts to get a BEAM implementation to run in the browser, however (I think via web assembly) and if this becomes possible and feasible (small download, fast startup, low memory overhead) then I look forward to server side rendering from client side code!

                                  2. 1

                                    I have definitely found that it’s not working as snappily as I’d hoped when accessed by mobile phone. Not being a web developer I have no idea how to diagnose this. I thought I might see lag due to latency, but all is well on a laptop (on the same network) so I’m guessing it could be grunt required in the browser, which is a little disappointing.

                                    Game here: https://fivelettrs.fly.dev/

                                    1. 4

                                      It’s pretty snappy on my iPhone, but it’s a recent model.

                                      Looking at the client-server communication it seems like your template sends all “dynamic” strings on every model change. Clicking on a character gets this response from the server:

                                      ["4","6","lv:phx-FtAFezJXYPUlsRqR","phx_reply",{"response":{"diff":{"1":{"d":[["guess-row ",{"d":[["guess pending","r"],["guess pending current"," "],["guess pending"," "],["guess pending"," "],["guess pending"," "]],"s":0}],["guess-row",{"d":[["guess pending"," "],["guess pending"," "],["guess pending"," "],["guess pending"," "],["guess pending"," "]],"s":0}],["guess-row",{"d":[["guess pending"," "],["guess pending"," "],["guess pending"," "],["guess pending"," "],["guess pending"," "]],"s":0}],["guess-row",{"d":[["guess pending"," "],["guess pending"," "],["guess pending"," "],["guess pending"," "],["guess pending"," "]],"s":0}],["guess-row",{"d":[["guess pending"," "],["guess pending"," "],["guess pending"," "],["guess pending"," "],["guess pending"," "]],"s":0}],["guess-row",{"d":[["guess pending"," "],["guess pending"," "],["guess pending"," "],["guess pending"," "],["guess pending"," "]],"s":0}]],"p":{"0":["\n            <span class=\"","\">","</span>\n"]}},"2":{"0":{"d":[[" id=\"q\"","key pending"," phx-value-key=\"q\"","q"],[" id=\"w\"","key pending"," phx-value-key=\"w\"","w"],[" id=\"e\"","key pending"," phx-value-key=\"e\"","e"],[" id=\"r\"","key pending"," phx-value-key=\"r\"","r"],[" id=\"t\"","key pending"," phx-value-key=\"t\"","t"],[" id=\"y\"","key pending"," phx-value-key=\"y\"","y"],[" id=\"u\"","key pending"," phx-value-key=\"u\"","u"],[" id=\"i\"","key pending"," phx-value-key=\"i\"","i"],[" id=\"o\"","key pending"," phx-value-key=\"o\"","o"],[" id=\"p\"","key pending"," phx-value-key=\"p\"","p"]]},"1":{"d":[[" id=\"a\"","key pending"," phx-value-key=\"a\"","a"],[" id=\"s\"","key pending"," phx-value-key=\"s\"","s"],[" id=\"d\"","key pending"," phx-value-key=\"d\"","d"],[" id=\"f\"","key pending"," phx-value-key=\"f\"","f"],[" id=\"g\"","key pending"," phx-value-key=\"g\"","g"],[" id=\"h\"","key pending"," phx-value-key=\"h\"","h"],[" id=\"j\"","key pending"," phx-value-key=\"j\"","j"],[" id=\"k\"","key pending"," phx-value-key=\"k\"","k"],[" id=\"l\"","key pending"," phx-value-key=\"l\"","l"]]},"2":"key backspace","3":{"d":[[" id=\"z\"","key pending"," phx-value-key=\"z\"","z"],[" id=\"x\"","key pending"," phx-value-key=\"x\"","x"],[" id=\"c\"","key pending"," phx-value-key=\"c\"","c"],[" id=\"v\"","key pending"," phx-value-key=\"v\"","v"],[" id=\"b\"","key pending"," phx-value-key=\"b\"","b"],[" id=\"n\"","key pending"," phx-value-key=\"n\"","n"],[" id=\"m\"","key pending"," phx-value-key=\"m\"","m"]]},"4":"key enter disabled"}}},"status":"ok"}]	1643819914.7385468
                                      

                                      …which is way too much data for a single-cell change. I’m guessing that slower JS engines may feel sluggish when consolidating the Virtual-DOM in the browser with the changes from the server (LiveView doesn’t directly updates the Dom but uses a virtual-dom library internally as an optimization).

                                      Thankfully, that shouldn’t be too hard to fix (as all these Dom patches are unnecessary). My guess is that your #guesses div is re-rendered fully for every change because the change-tracking of the HEEx template isn’t working correctly. Without the code one can only guess why. A first start is looking at Change Tracking Pitfalls.

                                      1. 1

                                        I’ll have a look, thanks. It improved somewhat after I took some advice on how to render the (dynamic) grid and keyboard in a way that was HEEx-friendly, but looking at the size of that diff I don’t think it’s quite right yet!

                                        In case you’re interested in seeing my rather fumbling first attempt at liveview: here’s the repo and here’s a direct link to the the heex, liveview and the game struct/code

                                        1. 1

                                          After some pain, I’ve concluded that LiveView can’t do the change tracking needed if I am using a single ‘game’ struct, and therefore I’m doing what I feared necessary: an ‘assign’ for every visible grid ‘tile’ and keyboard ‘key’.

                                          Small diffs but nasty HEEx and code!

                                          1. 1

                                            If I remember correctly HEEx can track inside structs. So the culprit may be the calls to functions that get passed the whole @game assign. I think you can rewrite your Game struct to contain the required data without having to go through struct members you should get good performance too. You could also introduce a GameStateView (or similar) struct which contains the data in a format ready for use in the template.

                                  1. 5

                                    IMO git pull is not a very good command, and you should just fetch/rebase manually.

                                    1. 2

                                      What if you don’t actually have any patches and you just want to update the local master branch to what’s on the remote?

                                      1. 3

                                        You just do git fetch origin followed by git merge --ff-only origin/master then, where origin is the assumed remote of course.

                                        1. 2

                                          fetch + reset

                                          1. 1

                                            To clarify, do you mean git fetch and then git reset --hard origin/main (or whatever is the right remote)? I don’t think I’ve seen git reset used this way, though I think it should work. (Though if you have uncommitted changes in the working directory, you can do a lot of damage with git reset --hard, no?)

                                            If so, what’s the advantage of git reset --hard over git merge --ff-only? If not, what exact command do you mean?

                                            1. 2

                                              You can do a hard reset if you want, but I tend to just rebase. merge –ff-only is also fine.

                                        2. 2

                                          Yeah, I realized while reading this that that was the solution I’d sort of subconsciously arrived at years ago. I can’t remember the last time I actually ran git pull.

                                          1. 1

                                            Thanks. I’ve heard this suggestion before, and I’ll consider it again. (Muscle memory can be a terrible thing.)

                                            1. 2

                                              What’s the point? It’s an additional command and the default will be fixed as you write in this article. Just set merge.ff=only and pull.ff=only and you have safe pulls.

                                              1. 1

                                                Maybe better to ask Forty-Bot, but (from what I can tell) many people dislike the way that pull fuses fetch and merge. They prefer to keep those steps separate. This is the argument I’ve heard, though I haven’t done anything about it yet.

                                                As far as “an additional command” goes, that may be why some people recommend this git up alias:

                                                git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'
                                                

                                                Also, I didn’t write the linked article. I just submitted it because I thought it was interesting.

                                          1. 10

                                            I’m baffled why people prefer going through the pains of setting up Prometheus + Grafana over adding few lines of code in their app to send metrics to InfluxDB 2.0 and have all the charting and alerting capabilities in a smaller footprint and simpler services topology. Additionally, the push model is safer under heavy load as it’s usually a better choice to drop metrics on the floor rather than choke the app by means of repeated requests from Prometheus (the “push gateway” isn’t not recommended as the primary reporting method as per docs). InfluxDB has a snappy UI, a pretty clean query language, retention policies with downsampling, HTTP API, dashboards with configuration storable in version control systems. It doesn’t have counters/gauges/histograms — there’s just measurements. The only thing I miss is metrics reporting over UDP.

                                            1. 17

                                              I’m baffled why people prefer going through the pains of setting up Prometheus + Grafana over adding few lines of code in their app to send metrics to InfluxDB 2.0 […]

                                              The community around Prometheus and Grafana is huge. The kube-prometheus project alone can drive a kubernetes cluster observability from zero-to-hero quickly. I admit that there is a learning curve there, but the value proposition is amazing. The community is amazing. There many online collections of ready-made rules teams can use.

                                              Even SaaS famous monitoring SaaS solutions offer poorer defaults than kube-prometheu’s 80+ strong k8s alerts collection. Prometheus is cheap, you just throw RAM and GBs and does all the heavy lifting. Good luck enabling OpenMetrics integration to drive billions of metrics to SaaS monitoring systems.

                                              Assuming I install influxDB, how do I drive metrics for cert-manager to influxDB? Cert-manager is an operator that mostly just works but needs monitoring in case the SSL cert issuance fails for whatever reason. For most solution the infra team will have to build monitoring. But cert-manager (like many others) exposes prometheus metrics (open-metrics compatible) and as a bonus there’s a good community grafana dashboard ready to be used.

                                              Additionally, the push model is safer under heavy load as it’s usually a better choice to drop metrics on the floor rather than choke the app by means of repeated requests from Prometheus […]

                                              To my experience it’s the other way around: If the exporter is built as it should, non-blocking, then it’s as light as serving a static page with few kb of text. I’ve seen applications slowed down by third party push libraries or the agent misbehaving (e.g. not being able to push metrics) leading to small but visible application performance degradation. Again one could talk about implementation but the footprint is visibly bigger in all respects.

                                              A pull-based metrics comes into play when you have hundreds if not thousands of services running and you don’t need detailed metrics (e.g. I don’t wanna get metrics every second). The default scrape interval is 30s. You can use buckets to collect detailed metrics falling to predefined percentiles, but sub-30s spikes cannot be debugged. Prom works like this for a reason. Modern monitoring systems are metric data-lakes and prometheus strikes a perfect balance between cost (in terms of storage, processing, etc.) and visibility.

                                              InfluxDB has a snappy UI, a pretty clean query language, retention policies with downsampling, HTTP API, dashboards with configuration storable in version control systems.

                                              There are ready-made prometheus rules and Grafana dashboards for about anything. Granted that many are poorly constructed or report the wrong thing (had poor experience with several community dashboards), usually community dashboards work out of the box for most people.

                                              1. 2

                                                In my experience, the times when a system is lagging or unresponsive are exactly the times when I want to capture as many metrics as possible. In a push model I will still get metrics leading up to the malfunction; in a pull model I may miss my window.

                                                As for push slowing down an application I agree that can happen, but it can also happen with pull (consider a naive application that serves metrics over http and does blocking writes to the socket). We have chosen to push metrics using shared memory so the cost of writing is at most a few hundred nanoseconds. A separate process can then transfer the metrics out of the server via push or pull, whichever is appropriate.

                                                1. 2

                                                  In a push model I will still get metrics leading up to the malfunction;

                                                  In modern observability infrastructures, the idea is to combine tracing, metrics and logs. What you’re describing is done by an APM/tracer a lot better compared to metrics.

                                                  I’ve seen metrics being used to measure time spent in function’s but that’s abusing the pattern I think. Of course if there’s no tracer/APM then, it’s fine.

                                                  Pushing metrics for every call leading up to the malfunction is usually dismissed because it’s a high cost, low value proposition.

                                                  1. 1

                                                    I didn’t say anything about metrics for every call; as you point out, that would be done better with tracing or logging. We do that too, but it serves a different purpose. A single process might handle hundreds of thousands of messages in a single second, and that granularity is too fine for humans to handle. Aggregating data is crucial, either into uniform time series (e.g. 30 second buckets so all the timestamps line up) or non-uniform time series (e.g. emitting a metric when a threshold). We keep counters in shared memory and scrape them with a separate process, resulting in a uniform time series. This is no different than what you would get scraping /proc on Linux and sending it to a tsdb, but it is all done in userspace.

                                                    As for push vs. pull, consider this case: a machine becomes unresponsive because it is swapping to disk. In a push model with a short interval you might see metrics showing which process was allocating memory before metrics are simply cut off. In a pull model the machine might become unresponsive before the metrics are collected, and at that point it is too late.

                                                    If you have a lot of machines to monitor but limited resources for collecting metrics, a pull model makes sense. In our case we have relatively few machines so the push model is a better tradeoff to avoid losing metrics.

                                                    In my ideal world metrics would be pushed on coarse-grained timer, or earlier when a threshold is reached. I think Brendan Gregg has written about doing something like this though I do not have the link handy.

                                              2. 14

                                                Years ago at GitLab we started with InfluxDB. It was nothing short of a nightmare, and we eventually migrated to Prometheus. I distinctively remember two issues:

                                                One: each row (I forgot the exact terminology used by Influx) is essentially uniquely identifier by a pair of (tags + values, timestamp). If the tags and their values are the same, and so is the timestamp, InfluxDB just straight up (and silently) overwrites the data. This resulted in us having far less data than we’d expect, until we: 1) recorded timestamps in microseconds/nanoseconds (don’t remember which) 2) added a slight random value to it. Even then it was still guess work as to how much data would be overwritten. You can find some old discussions on this here: https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/86

                                                Two: IIRC at some point they removed clustering support from the FOSS offering and made it enterprise only. This left a bad taste in our mouth, and IIRC a lot of people were unhappy with it at the time.

                                                Setting that aside, we had a lot of trouble scaling InfluxDB. It seemed that at the time it simply couldn’t handle the workload we were throwing at it. I’m not sure if it has improved since, but frankly I would stay away from push based models for large environments.

                                                1. 7

                                                  I honestly don’t know whether push behaves better than pull during incidents; I prefer to drop application traffic before dropping metrics. But either way, I think a better argument for pull-based metrics is that they are more composable. With Prometheus as a concrete example, it’s possible to forward metrics from one service to another, from one scraper to another, and from one database to another, by only changing one scraper and not the target service/scraper/database.

                                                  1. 4

                                                    I’m using Influx myself, but the advantage of Prometheus is that you don’t have to write your own dashboards for common services. The Prometheus metric naming scheme allows folks to write and share dashboards that are actually reusable. I haven’t had that experience with InfluxDB (or Graphite before it).

                                                    (Plus, you know, licensing.)

                                                    1. 2

                                                      One reason to use grafana is if you have data that you do not want to bring into influxdb. Both influx and prometheus have their own graphing capabilities and can be used on their own or with grafana. We use influxdb with grafana as a frontend so we visualize both the metrics stored in influx and other sources (such as kudu) on the same dashboard.

                                                      1. 2

                                                        I tend to agree but after using influxDB for quite some time I find its semantics to be rather awful and the company to be extremely difficult to deal with.

                                                        I’m currently sizing up a migration to Prometheus. Despite disliking strongly the polling semantics and the fact that it intentionally doesn’t scale, so you have many sources of truth for data.

                                                        Oh well.

                                                        1. 2

                                                          I’ve had circular arguments on handling some monitoring but still complying with the strongly discouraged push gateway method. I think pull makes sense when you start using alertmanager and alert when a metric fails. I like the whole setup except grafana’s log solution. Loki and fluent have been a long source of frustration and there seem to be very limited resources on the internet about them. It sounds good, but is extremely hard to tame compared to prometheus. The real win over previous solutions with prometheus and grafana were alerting rules. I find it easier to express complex metrics for monitoring quickly.

                                                        1. 10

                                                          I think on this site, everyone knows what RSS is and why people want to use it, so it’s a bit preaching to the choir. It would be more instructive if it theorized why most people don’t really use RSS anymore.

                                                          1. 5

                                                            I don’t think the article was aimed only at the lobste.rs crowd. Outside tech nerdery, I don’t think many people have even heard of RSS.

                                                            1. 2

                                                              One problem I have with RSS is that whenever a feed entry doesn’t have the <guid isPermaLink="true"> attribute set, the reader (yarr in my case) is going to display old entries as unread from time to time. Of course strictly speaking, that’s not a fault of the RSS reader, but rather of the server generating the feed but I find that many, many feeds have this problem.

                                                            1. 4

                                                              It’s cool, but contrary to TypeSense, doesn’t support HA, although it’s high on the “under consideration” list.

                                                              The Rust SDK is also a bit surprising. For instance, the fn to add documents to the index, add_documents() is async and therefore “returns” a future, but the future is itself a data structure representing “progress”, which seems redundant and error-prone. So in order to wait for completion of add_documents(), a wait_for_pending_update() (which has some arbitrary defaults, BTW) loop is needed instead of simply doing add_documents(...).await?.

                                                              I also don’t see any support of atomic transactions in contrast to e.g. Tantivy.

                                                              1. 5

                                                                If you want to be even closer to the metal, there’s Ceph’s Bluestore backend which eschews the file system and writes blocks directly. I don’t have experience with Ceph, but that’s how S3 actually writes to disk.

                                                                1. 1

                                                                  Those results are interesting! Has anyone gone a level deeper though? I’d love to see someone writing a driver for an SSD drive that bypasses not only the File System but also the Flash Translation Layer and exposes a low level interface that deals in immutable NAND flash pages. The FTL is, after all, only there to serve the mutability needed by the File System sitting layer above it. I think many people realize at this point how mutability complicates everything, so thanks to ditching it, not only we could speed things up, but also simplify them. For instance, LMDB is based on copy-on-write (i.e. immutable) memory pages because it’s the most reliable way. If it could deal in NAND pages directly instead, that’s losing two layers of abstraction that slow down and complicate things, with no loss of functionality. The downside is of course being tied to a specific SSD firmware, but the interface can be generalized to other SSD manufacturers.

                                                                1. 6

                                                                  Cool, but would be even cooler if it offered an Atom or RSS feed

                                                                    1. 1

                                                                      Not great, but at least it’s a way to get notified when there’s a change. Thanks!

                                                                  1. 2

                                                                    What’s the story with regards to delivery to major providers and self hosting email these days?

                                                                    1. 3

                                                                      I can’t speak for others, but I’ve been self hosting my email for a few months now, with no delivery problems whatsoever. Configuring the server to get messages to actually send and not end up in spam was difficult, but once it was done, things basically worked without issue.

                                                                      1. 3

                                                                        you don’t always know when mails you sent end up in the recipient’s spam folder.

                                                                        1. 2

                                                                          Or when Gmail silently throws them away with no error or warning. Don’t even show in spam. Just <poof> and … gone.

                                                                        2. 2

                                                                          I’ve had the same experience running Maddy on Hetzner.

                                                                          1. 1

                                                                            Considering how much of my life depends on a working email address and seeing all the horror stories of Gmail blocking accounts apparently for no good reason and with limited ability to appeal, I’m seriously considering hosting email myself too.

                                                                            Could you elaborate what were steps you needed to do for outgoing messages not to end up in the receiver’s spam folder?

                                                                            1. 2

                                                                              It mostly came down to properly setting up the DNS records for authentication. The Arch Wiki page for setting up a mail server is a very useful resource for this. For the most part, setting things up was just trial and error, troubleshooting until things worked. In my experience, hosting email actually isn’t that difficult. It’s definitely not easy, but if you know what you’re doing, it’s definitely doable, and it’s far from the hardest thing I’ve hosted in the past. Of course, this is just my experience, and obviously others have had experiences that differ a lot from mine, so definitely do your research before committing. Especially if you’re extremely dependent on having a working email address, it may be safer to either keep your current email, or just migrate it to a service other than Gmail if you feel uncomfortable with them.

                                                                              One other thing to note is that if you’re using a VPS, make sure your VPS provider actually allows you to self host email. Many VPS providers block crucial ports such as port 25. The process for getting these port(s) unblocked for your server differs from host to host; some don’t let you unblock it at all, for others you just have to open a support ticket and request it.

                                                                              1. 2

                                                                                Using some form of domain authentication is crucial. https://explained-from-first-principles.com/email/#domain-authentication

                                                                          1. 2

                                                                            Here’s the transcript. The best part is the students adding to the project. It showed a combination of low effort to learn with good results achieved.

                                                                            “ Finally, the software was sent to the NSA’s independent assurance team (SPRE) in Albuquerque, New Mexico, who reported that the software contained zero defects… The NSA were startled by the productivity figures because they were much higher than had ever been achieved on an NSA project despite having gone beyond the minimum requirements for Evaluation Assurance Level EAL5; Altran’s C by C development process includes activities from the much more rigorous EAL6 and EAL7 levels.”

                                                                            “The students were given 3-4 days training on reading and writing Z, 3 days training on the Tokeneer ID Station, 2 days training on Ada and 4-5 days training on SPARK and the SPARK tools. Beyond that they only had Z and SPARK textbooks and manuals, and email support from Altran/Praxis if they needed it.”

                                                                            “In the following years, a small number of errors have been found in the Tokeneer software by academic researchers[xxxv]. The authors of the cited paper say It is a remarkable achievement that this complete redevelopment of an actual system by 3 people over 9 months (part-time) achieved such a level of quality that only a few problems can be found several years later using so many new approaches not available at the time.”

                                                                            1. 2

                                                                              It is remarkable indeed. If you interested in learning more about SPARK, there’s a good book out there which I can easily recommend: Building High Integrity Applications with SPARK.

                                                                              1. 2
                                                                                1. 48

                                                                                  FWIW, this is how you express conditional arguments / struct fields in Rust. The condition has to encompass the name as well as the type, not just the type as was first attempted.

                                                                                  I feel like Rust has definitely obliterated its complexity budget in unfortunate ways. Every day somebody comes to the sled discord chat with confusion over some async interaction. The fixation on async-await, despite it slowing down almost every real-world workload it is applied to, and despite it adding additional bug classes and compiler errors that simply don’t exist unless you start using it, has been particularly detrimental to the ecosystem. Sure, the async ecosystem is a “thriving subcommunity” but it’s thriving in the Kuhnian sense where a field must be sufficiently problematic to warrant collaboration. There’s no if-statement community anymore because they tend to work and be reasonably well understood. With async-await, I observed that the problematic space of the overall community shifted a bit from addressing external issues through memory safety bug class mitigation to generally coping with internal issues encountered due to some future not executing as assumed.

                                                                                  The problematic space of a community is a nice lens to think about in general. It is always in-flux with the shifting userbase and their goals. What do people talk about? What are people using it for? As the problematic space shifts, at best it can introduce the community to new ideas, but there’s also always an aspect of it that causes mourning over what it once represented. Most of my friends who I’ve met through Rust have taken steps to cut interactions with “the Rust community” down to an absolute minimum due to it tending to produce a feeling of alienation over time. I think this is pretty normal.

                                                                                  I’m going to keep using Rust to build my database in and other things that need to go very fast, but I see communities like Zig’s as being in some ways more aligned with the problematic spaces I enjoy geeking out with in conversations. I’m also thinking about getting a lot more involved in Erlang since I realized I haven’t felt that kind of problem space overlap in any language community since I stopped using it.

                                                                                  1. 31

                                                                                    I was surprised to see the Rust community jump on the async-await bandwagon, because it was clear from the beginning it’s a bandwagon. When building a stable platform (e.g. a language) you wait for the current fashion to crest and fall, so you can hear the other side of the story – the people who used it in anger, and discovered what the strengths and weaknesses really are. Rust unwisely didn’t do that.

                                                                                    I will note though that the weaknesses of the async-await model were apparent right from the beginning, and yet here we are. A lesson for future languages.

                                                                                    1. 29

                                                                                      This hits me particularly hard because I had experienced a lot of nearly-identical pain around async when using various flavors of Scala futures for a few years before picking up Rust in 2014. I went to the very first Rust conference, Rust Camp in 2015 at Berkeley, and described a lot of the pain points that had caused significant issues in the Scala community to several of the people directly working on the core async functionality in Rust. Over the years I’ve had lots of personal conversations with many of the people involved, hoping that sharing my experiences would somehow encourage others to avoid well-known painful paths. This overall experience has caused me to learn a lot about human psychology - especially our inability to avoid problems when there are positive social feedback loops that lead to those problems. It makes me really pessimistic about climate apocalypse and rising authoritarianism leading us to war and genocides, and the importance of taking months and years away from work to enjoy life for as long as it is possible to do so.

                                                                                      The content of ideas does not matter very much compared to the incredibly powerful drive to exist in a tribe. Later on when I read Kuhn’s Structure of Scientific Revolutions, Feyerabend’s Against Method, and Ian Hacking’s Representing and Intervening, which are ostensibly about the social aspects of science, I was blown away by how strongly their explanations of how science often moves in strange directions that may not actually cause “progress” mapped directly to the experiences I’ve had while watching Rust grow and fail to avoid obvious traps due to the naysayers being drowned out by eager participants in the social process of Making Rust.

                                                                                      1. 7

                                                                                        Reminds me of the theory that Haskell and Scala appeal because they’re a way for the programmer to needsnipe themselves

                                                                                        1. 5

                                                                                          Thanks for fighting the good fight. Just say “no” to complexity.

                                                                                          Which of those three books you mentioned do you think is most worthwhile?

                                                                                          1. 10

                                                                                            I think that Kuhn’s Structure of Scientific Revolutions has the broadest appeal and I think that nearly anyone who has any interaction with open source software will find a tremendous number of connections to their own work. Science’s progressions are described in a way that applies equally to social-technical communities of all kinds. Kuhn is also the most heavily cited thinker in later books on the subject, so by reading his book, you gain deeper access to much of the content of the others, as it is often assumed that you have some familiarity with Kuhn.

                                                                                            You can more or less replace any mention of “paper citation” with “software dependency” without much loss in generality while reading Kuhn. Hacking and Feyerabend are more challenging, but I would recommend them both highly. Feyerabend is a bit more radical and critical, and Hacking zooms out a bit more and talks about a variety of viewpoints, including many perspectives on Kuhn and Feyerabend. Hacking’s writing style is really worth experiencing, even by just skimming something random by him, by anyone who writes about deep subjects. I find his writing to be enviably clear, although sometimes he leans a bit into sarcasm in a way that I’ve been put off by.

                                                                                          2. 4

                                                                                            If you don’t mind, what’s an example of async/await pain that’s common among languages and not to do with how Rust uniquely works? I ask because I’ve had a good time with async/await, but in plainer, application-level languages.

                                                                                            (Ed: thanks for the thoughtful replies)

                                                                                            1. 12

                                                                                              The classic “what color is your function” blog post describes what is, I think, such a pain? You have to choose in your API whether a function can block or not, and it doesn’t compose well.

                                                                                              1. 3

                                                                                                I read that one, and I took their point. All this tends to make me wonder if Swift (roughly, Rust minus borrow checker plus Apple backing) is doing the right thing by working on async/await now.

                                                                                                But so far I don’t mind function coloring as I use it daily in TypeScript. In my experience, functions that need to be async tend to be the most major steps of work. The incoming network request is async, the API call it makes is async, and then all subsequent parsing and page rendering aren’t async, but can be if I like.

                                                                                                Maybe, like another commenter said, whether async/await is a net positive has more to do with adapting the language to a domain that isn’t otherwise its strong suit.

                                                                                                1. 16

                                                                                                  You might be interested in knowing that Zig has async/await but there is no function coloring problem.

                                                                                                  https://kristoff.it/blog/zig-colorblind-async-await/

                                                                                                  1. 3

                                                                                                    Indeed this is an interesting difference at least in presentation. Usually, async/await provides sugar for an existing concurrency type like Promise or Task. It doesn’t provide the concurrency in the first place. Function colors are then a tradeoff for hiding the type, letting you think about the task and read it just like plain synchronous code. You retain the option to call without await, such that colors are not totally restrictive, and sometimes you want to use the type by hand; think Promise.all([…]).

                                                                                                    Zig seems like it might provide all these same benefits by another method, but it’s hard to tell without trying it. I also can’t tell yet if the async frame type is sugared in by the call, or by the function definition. It seems like it’s a sort of generic, where the nature of the call will specialize it all the way down. If so, neat!

                                                                                                    1. 7

                                                                                                      It seems like it’s a sort of generic, where the nature of the call will specialize it all the way down. If so, neat!

                                                                                                      That’s precisely it!

                                                                                                      1. 2

                                                                                                        I’ve been poking at Zig a bit since this thread; thank you for stirring my interest. :)

                                                                                              2. 6

                                                                                                Well, I think that async/await was a great thing for javascript, and generally it seems to work well in languages that have poor threading support. But Rust has great threading support, and Rust’s future-based strategy aimed from the beginning at copying Scala’s approach. A few loud research-oriented voices in the Rust community said “we think Scala’s approach looks great” and it drowned out the chorus of non-academic users of Scala who had spent years of dealing with frustrating compilation issues and issues where different future implementations were incompatible with each other and overall lots of tribalism that ended up repeating in a similar way in the Rust async/await timeline.

                                                                                                1. 5

                                                                                                  I am somewhat surprised that you say Rust’s futures are modeled after Scala’s. I assume the ones that ended up in the standard library. As for commonalities: They also offer combinators on top of a common futures trait and you need explicit support in libraries - that’s pretty much all that is similar to Rust’s.

                                                                                                  In Scala, futures were annoying because exceptions and meaningless stacktraces. In Rust, you get the right stacktraces and error propagation.

                                                                                                  In Rust, Futures sucked for me due to error conversions and borrowing being basically unsupported until async await. Now they are still annoying because of ecosystem split (sync vs various partially compatible async).

                                                                                                  The mentioned problem of competing libraries is basically unpreventable in fields without wide consensus and would have happened with ANY future alternative. If you get humans to agree on sensible solutions and not fight about irrelevant details, you are a wizard.

                                                                                                  Where I agree is that it was super risky to spend language complexity budget on async/await, even though solving the underlying generator/state machine problem felt like a good priority. While async await feels a bit to special-cased and hacky to be part of the language… It could be worse. If we find a better solution for async in Rust, we wouldn’t have to teach the current way anymore.

                                                                                                  Other solutions would just have different pros and cons. E.g. go’s or zig’s approach seemed the solution even deeper into the language with the pro of setting a somewhat universal standard for the language.

                                                                                                  1. 3

                                                                                                    It was emulating Finagle from the beginning: https://medium.com/@carllerche/announcing-tokio-df6bb4ddb34 but then the decision to push so much additional complexity into the language itself so that people could have an easier time writing strictly worse systems was just baffling.

                                                                                                    Having worked in Finagle for a few years before that, I tried to encourage some of the folks to aim for something lighter weight since the subset of Finagle users who felt happy about its high complexity seemed to be the ones who went to work at Twitter where the complexity was justified, but it seemed like most of the community was pretty relieved to switch to Akka which didn’t cause so much type noise once it was available.

                                                                                                    I don’t expect humans not to fragment now, but over time I’ve learned that it’s a much more irrational process than I had maybe believed in 2014. Mostly I’ve been disappointed about being unable to communicate with what was a tiny community about something that I felt like I had a lot of experience with and could help other people avoid pain around, but nevertheless watch it bloom into a huge crappy thing that now comes back into my life every day even when I try to ignore it by just using a different feature set in my own stuff.

                                                                                                2. 3

                                                                                                  I hope you will get a reply by someone with more Rust experience than me, but I imagine that the primary problem is that even if you don’t have to manually free memory in Rust, you still have to think about where the memory comes from, which tends to make lifetime management more complicated, requiring to occasionally forcefully move things unto the heap (Box) and also use identity semantics (Pin), and so all of this contributes to having to deal with a lot of additional complexity to bake into the application the extra dynamicism that async/await enables, while still maintaining the safety assurances of the borrow checker.

                                                                                                  Normally, in higher level languages you don’t ever get to decide where the memory comes from, so this is a design dimension that you never get to explore.

                                                                                                3. 2

                                                                                                  I’m curious if you stuck around in Scala or pay attention to what’s going on now because I think it has one of the best stories when it comes to managing concurrency. Zio, Cats Effect, Monix, fs2 and Akka all have different goals and trade offs but the old problem of Future is easily avoided

                                                                                                4. 6

                                                                                                  I was surprised to see the Rust community jump on the async-await bandwagon, because it was clear from the beginning it’s a bandwagon.

                                                                                                  I’m not surprised. I don’t know how async/await works exactly, but it definitely has a clear use case. I once implemented a 3-way handshake in C. There was some crypto underneath, but the idea was, from the server’s point of view was to receive a first message, respond, then wait for the reply. Once the reply comes and is validated the handshake is complete. (The crypto details were handled by a library.)

                                                                                                  Even that simple handshake was a pain in the butt to handle in C. Every time the server gets a new message, it needs to either spawn a new state machine, or dispatch it to an existing one. Then the state machine can do something, suspend, and wait for a new message. Note that it can go on after the handshake, as part of normal network communication.

                                                                                                  That state machine business is cumbersome and error prone, I don’t want to deal with it. The programming model I want is blocking I/O with threads. The efficiency model I want is async I/O. So having a language construct that easily lets me suspend & resume execution at will is very enticing, and I would jump to anything that gives me that —at least until I know better, which I currently don’t.

                                                                                                  I’d even go further: given the performance of our machines (high latencies and high throughputs), I believe non-blocking I/O at every level is the only reasonable way forward. Not just for networking, but for disk I/O, filling graphics card buffers, everything. Language support for this is becoming as critical as generics themselves. We laughed “lol no generics” at Go, but now I do believe it is time to start laughing “lol no async I/O” as well. The problem now is to figure out how to do it. Current solutions don’t seem to be perfect (though there may be one I’m not aware of).

                                                                                                  1. 2

                                                                                                    The whole thing with async I/O is that process creation is too slow, and then thread creation was too slow, and some might even consider coroutine creation too slow [1]. It appears that concerns that formerly were of the kernel (managing I/O among tasks; scheduling tasks) are now being pushed out to userland. Is this the direction we really want to go?

                                                                                                    [1] I use coroutines in Lua to manage async I/O and I think it’s fine. It makes the code look like non-blocking, but it’s not.

                                                                                                    1. 2

                                                                                                      I don’t think it’s unreasonable to think that the kernel should have as few concerns as possible. It’s a singleton, it doesn’t run with the benefit of memory protection, and its internal APIs aren’t as stable as the ones it provides to userland.

                                                                                                      … and, yes, I think a lot of async/await work is LARPing. But that’s because a lot of benchmark-oriented development is LARPing, and probably isn’t special to async/await specifically.

                                                                                                      1. 1

                                                                                                        I’m not sure what you’re getting at. I want async I/O to avoid process creation and thread creation and context switches, and even scheduling to some extent. What I want is one thread per core, and short tasks being sent to them. No process creation, no thread creation, no context switching. Just jump the instruction pointer to the relevant task, and return to the caller when it’s done.

                                                                                                        And when the task needs to, say, read from the disk, then it should do so asynchronously: suspend execution, return to the caller, wait for the response to come back, and when it does resume execution. It can be done explicitly with message passing, but that’s excruciating. A programming model where I can kinda pretend the call is blocking (but in fact we’re yielding to the caller) is much nicer, and I believe very fast.

                                                                                                    2. 5

                                                                                                      Agreed. I always told people that async/await will be just as popular as Java’s synchronized a few years down the road. Some were surprised, some were offended, but sometimes reality is uncomfortable.

                                                                                                    3. 29

                                                                                                      Thank you for sharing, Zig has been much more conservative than Rust in terms of complexity but we too have splurged a good chunk of the budget on async/await. Based on my experience producing introductory materials for Zig, async/await is by far the hardest thing to explain and probably is going to be my biggest challenge to tackle for 2021. (that said, it’s continuations, these things are confusing by nature)

                                                                                                      On the upside

                                                                                                      despite it slowing down almost every real-world workload it is applied to

                                                                                                      This is hopefully not going to be a problem in our case. Part of the complexity of async/await in Zig is that a single library implementation can be used in both blocking and evented mode, so in the end it should never be the case that you can only find an async version of a client library, assuming authors are willing to do the work, but even if not, support can be added incrementally by contributors interested in having their use case supported.

                                                                                                      1. 17

                                                                                                        I feel like Rust has definitely obliterated its complexity budget in unfortunate ways.

                                                                                                        I remember the time I asked one of the more well-known Rust proponents “so you think adding features improves a language” and he said “yes”. So it was pretty clear to me early on that Rust would join the feature death march of C++, C#, …

                                                                                                        Rust has many language features and they’re all largely disjoint from each other, so knowing some doesn’t help me guess the others.

                                                                                                        That’s so painfully true.

                                                                                                        For instance, it has different syntax for struct creation and function calls, their poor syntax choices also mean that structs/functions won’t get default values any time soon.

                                                                                                        ; is mandatory (what is this, 1980?), but you can leave out , at the end.

                                                                                                        The severe design mistake of using <> for generics also means you have to learn 4 different syntax variations, and when to use them.

                                                                                                        The whole module stuff is way too complex and only makes sense if you programmed in C before. I have basically given up on getting to know the intricacies, and just let IntelliJ handle uses.

                                                                                                        Super weird that both if and switch exist.

                                                                                                        Most of my friends who I’ve met through Rust have taken steps to cut interactions with “the Rust community” down to an absolute minimum due to it tending to produce a feeling of alienation over time.

                                                                                                        Yes, that’s my experience too. I have some (rather popular) projects on GitHub that I archive from time to time to not having to deal with Rust people. There are some incredibly toxic ones, which seem to be – for whatever reason – close to some “core” Rust people, so they can do whatever the hell they like.

                                                                                                        1. 6

                                                                                                          For instance, it has different syntax for struct creation and function calls

                                                                                                          Perhaps they are trying to avoid the C++ thing where you can’t tell whether foo(bar) is struct creation or a function call without knowing what foo is?

                                                                                                          The whole module stuff is way too complex and only makes sense if you programmed in C before. I have basically given up on getting to know the intricacies, and just let IntelliJ handle uses.

                                                                                                          It only makes sense to someone who has programmed in C++. C’s “module” system is far simpler and easier to grok.

                                                                                                          Super weird that both if and switch exist.

                                                                                                          Would you have preferred

                                                                                                          match condition() {
                                                                                                              true => {
                                                                                                              
                                                                                                              },
                                                                                                              false => {
                                                                                                          
                                                                                                              },
                                                                                                          }
                                                                                                          

                                                                                                          I think that syntax is clunky when you start needing else if.

                                                                                                          1. 1

                                                                                                            Perhaps they are trying to avoid the C++ thing where you can’t tell whether foo(bar) is struct creation or a function call without knowing what foo is?

                                                                                                            Why wouldn’t you be able to tell?

                                                                                                            Even if that was the issue (it isn’t), that’s not the problem C++ has – it’s that foo also could be 3 dozen other things.

                                                                                                            Would you have preferred […]

                                                                                                            No, I prefer having one unified construct that can deal with both usecases reasonably well.

                                                                                                            1. 2

                                                                                                              Why wouldn’t you be able to tell?

                                                                                                              struct M { };
                                                                                                              void L(M m);
                                                                                                              
                                                                                                              void f() {
                                                                                                                  M(m); // e.g. M m;
                                                                                                                  L(m); // function call
                                                                                                              }
                                                                                                              

                                                                                                              The only way to tell what is going on is if you already know the types of all the symbols.

                                                                                                              No, I prefer having one unified construct that can deal with both usecases reasonably well.

                                                                                                              Ok, do you have an example from another language which you think handles this reasonably well?

                                                                                                              1. 2

                                                                                                                The only way to tell what is going on is if you already know the types of all the symbols.

                                                                                                                Let the IDE color things accordingly. Solved problem.

                                                                                                                Ok, do you have an example from another language which you think handles this reasonably well?

                                                                                                                I’m currently in the process of implementing it, but I think this is a good intro to my plans.

                                                                                                                1. 1

                                                                                                                  Let the IDE color things accordingly. Solved problem.

                                                                                                                  The problem of course is for the writer of the IDE :)

                                                                                                                  Constructs like these in C++ make it not only harder for humans to parse the code, but for compilers as well. This turns into real-world performance decreases which are avoided in other languages.

                                                                                                                  I’m currently in the process of implementing it, but I think this is a good intro to my plans.

                                                                                                                  That’s interesting, but I think there’s a conflict with Rust’s goal of being a systems-level programming language. Part of that is having primitives which map reasonably well onto things that the compiler can translate into machine code. Part of the reason that languages like C have both if and switch is because switch statements of the correct form may be translated into an indirect jump instead of repeated branches. Of course, a Sufficiently Smart Compiler could optimize this even in the if case, but it is very easy to write code which is not optimizable in such a way. I think there is value to both humans and computers in having separate constructs for arbitrary conditionals and for equality. It helps separate intent and provides some good optimization hints.

                                                                                                                  Another reason why this exists is for exhaustiveness checks. Languages with switch can check that you handle all cases of an enum.

                                                                                                                  The other half of this is that Rust is the bastard child of ML and C++. ML and C++ both have match/switch, so Rust has one too.


                                                                                                                  I think you will have a lot of trouble producing good error messages with such a syntax. For example, say someone forgets an = or even both ==s. If your language does false-y and truth-y coercion, then there may be no error at all here. And to the parser, it is not clear at all where the error is. Further, this sort of extension cannot be generalized to one-liners. That is, you cannot unambiguously parse if a == b then c == d then e without line-breaks.

                                                                                                                  On the subject, in terms of prior-art, verilog allows expressions in its case labels. This allows for some similar syntax constructions (though more limited since functions are not as useful as in regular programming languages).

                                                                                                          2. 3

                                                                                                            For instance, it has different syntax for struct creation and function calls, their poor syntax choices also mean that structs/functions won’t get default values any time soon.

                                                                                                            This is a good thing. Creating a struct is a meaningfully different operation from calling a function, and there’s no problem with having there be separate syntax for these two separate things.

                                                                                                            The Rust standard library provides a Default trait, with examples of how to use it and customize it. I don’t find it at all difficult to work with structs with default values in Rust.

                                                                                                            The whole module stuff is way too complex and only makes sense if you programmed in C before. I have basically given up on getting to know the intricacies, and just let IntelliJ handle uses.

                                                                                                            I don’t understand this comment at all. Rust’s module system seems fairly similar to module systems in some other languages I’ve used, although I’m having trouble thinking of other languages that allow you to create a module hierarchy within a single file, like you can do with the mod { } keyword (C++ allows nested namespaces I think, but that’s it). I don’t see how knowing C has anything to do with understand Rust modules better. C has no module system at all.

                                                                                                            1. 2

                                                                                                              I’m having trouble thinking of other languages that allow you to create a module hierarchy within a single file

                                                                                                              Lua can do this, although it’s not common.

                                                                                                              1. 1

                                                                                                                This is a good thing.

                                                                                                                I guess that’s why many Rust devs – immediately after writing a struct – also define a fun to wrap their struct creation? :-)

                                                                                                                Creating a struct is a meaningfully different operation from calling a function

                                                                                                                It really isn’t.

                                                                                                                The Rust standard library provides a Default trait, with examples of how to use it and customize it. I don’t find it at all difficult to work with structs with default values in Rust.

                                                                                                                That’s clearly not what I alluded to.

                                                                                                                I don’t see how knowing C has anything to do with understand Rust modules better. C has no module system at all.

                                                                                                                Rust’s module system only makes sense if you keep in mind that it’s main goal is to produce one big ball of compiled code in the end. In that sense, Rust’s module system is a round-about way to describe which parts of the code end up being part of that big ball.

                                                                                                                1. 3

                                                                                                                  Putting a OCaml hat on:

                                                                                                                  • Struct creation and function calls are quite different. In particular it’s good to have structure syntax that can be mirrored in pattern matching, whereas function call has no equivalent in match.
                                                                                                                  • Multiple modules in one file is also possible in ML/OCaml. Maybe in some Wirth language, though I’m not sure on that one.

                                                                                                                  it’s main goal is to produce one big ball of compiled code in the end.

                                                                                                                  What other goal would there be? That’s what 100% of compiled languages aim at… Comparing rust to C which has 0 notion of module is just weird.

                                                                                                                  1. 1

                                                                                                                    Struct creation and function calls are quite different. In particular it’s good to have structure syntax that can be mirrored in pattern matching, whereas function call has no equivalent in match.

                                                                                                                    In what sense would this be an obstacle? I would expect that a modern language let’s you match on anything that provides the required method/has the right signature. “This is a struct, so you can match on it” feels rather antiquated.

                                                                                                                    What other goal would there be? That’s what 100% of compiled languages aim at… Comparing rust to C which has 0 notion of module is just weird.

                                                                                                                    It feels like it was built by someone who never used anything but C in his life, and then went “wouldn’t it be nice if it was clearer than in C which parts of the code contribute to the result?”.

                                                                                                                    The whole aliasing, reexporting etc. functionality feels like it exists as a replacement for some convenience C macros, and not something one actually would want. I prefer that there is a direct relationship between placing a file somewhere and it ending up in a specific place, without having to wire up everything again with the module system.

                                                                                                                    1. 1

                                                                                                                      There is documented inspiration from OCaml from the rust original creator. The first compiler was even in OCaml, and a lot of names stuck (like Some/None rather than the Haskell Just/Nothing). It also has obvious C++ influences, notably the namespace syntax being :: and <> for generics. The module system most closely reminds me of a mix of OCaml and… python, with special file names (mod.rs, like __init__.py or something like that?), even though it’s much much simpler than OCaml. Again not just “auto wiring” files in is a net benefit (another lesson from OCaml I’d guess, where the build system has to clarify what’s in or out a specific library). It makes build more declarative.

                                                                                                                      As for the matching: rust doesn’t have active patterns or the scala-style deconstruction. In this context (match against values you can pre-compile pattern-matching very efficiently to decision trees and constant time access to fields by offset. This would be harder to do efficiently with “just call this deconstuct method”. This is more speculation on my side, but it squares with rust’s efficiency concerns.

                                                                                                                      1. 1

                                                                                                                        I see your point, but in that case Rust would need to disallow match guards too (because what else are guards, but less reusable unapply methods?).

                                                                                                                    2. 1

                                                                                                                      Comparing rust to C which has 0 notion of module is just weird.

                                                                                                                      Well there are translation units :) (though you can only import using the linker)

                                                                                                                  2. 1

                                                                                                                    I’m having trouble thinking of other languages that allow you to create a module hierarchy within a single file,

                                                                                                                    Perl can do this.

                                                                                                                    1. 3

                                                                                                                      Elixir also allows you to create a module hierarchy within a single file.

                                                                                                                      1. 2

                                                                                                                        And Julia. Maybe this isn’t so rare.

                                                                                                                  3. 1

                                                                                                                    ; is mandatory (what is this, 1980?), but you can leave out , at the end.

                                                                                                                    Ugh this one gets me every time. Why Rust, why.

                                                                                                                    1. 2

                                                                                                                      Same in Zig? Curious to know Zig rationale for this.

                                                                                                                      1. 10

                                                                                                                        In almost all languages with mandatory semicolons, they exist to prevent multi-line syntax ambiguities. The designers of Go and Lua both went to great pains to avoid such problems in their language grammars. Unlike, for example, JavaScript. This article about semicolon insertion rules causing ambiguity and unexpected results should help illustrate some of these problems.

                                                                                                                        1. 3

                                                                                                                          Pointing out Javascript isn’t a valid excuse.

                                                                                                                          Javascript’s problems are solely Javascript’s. If we discarded every concept that was implemented poorly in Javascript, we wouldn’t have many concepts left to program with.

                                                                                                                          I want semicolon inference done right, simple as that.

                                                                                                                          1. 4

                                                                                                                            That’s not what I’m saying. JavaScript is merely an easy example of some syntax problems that can occur. I merely assume that Rust, which has many more features than Go or Lua, decided not to maintain an unambiguous grammar without using semicolons.

                                                                                                                            1. 2

                                                                                                                              Why would the grammar be ambiguous? Are you sure that you don’t keep arguing from a JavaScript POV?

                                                                                                                              Not needing ; doesn’t mean the grammar is ambiguous.

                                                                                                                              1. 4

                                                                                                                                ~_~

                                                                                                                                Semicolons are an easy way to eliminate grammar ambiguity for multi-line syntax. For any language. C++ for example would have numerous similar problems without semicolons.

                                                                                                                                Not needing ; doesn’t mean the grammar is ambiguous.

                                                                                                                                Of course. Go and Lua are examples of languages designed specifically to avoid ambiguity without semicolons. JavaScript, C++, and Rust were not designed that way. JavaScript happens to be an easy way to illustrate possible problems because it has janky automatic semicolon insertion, whereas C++ and Rust do not.

                                                                                                                                1. 0

                                                                                                                                  I’m completely unsure what you are trying to argue – it doesn’t make much sense. Has your triple negation above perhaps confused you a bit?

                                                                                                                                  The main point is that a language created after 2000 simply shouldn’t need ;.

                                                                                                                                  1. 5

                                                                                                                                    ; is mandatory (what is this, 1980?), but you can leave out , at the end.

                                                                                                                                    Same in Zig? Curious to know Zig rationale for this.

                                                                                                                                    The rationale for semicolons. They make parsing simpler, particularly for multi-line syntax constructs. I have been extremely clear about this the entire time. I have rephrased my thesis multiple times:

                                                                                                                                    In almost all languages with mandatory semicolons, they exist to prevent multi-line syntax ambiguities.

                                                                                                                                    Semicolons are an easy way to eliminate grammar ambiguity for multi-line syntax.

                                                                                                                                    Many underestimate the difficulty of creating a language without semicolons. Go has done so with substantial effort, and maintaining that property has by no means been effortless for them when adding new syntax to the language.

                                                                                                                                    1. 0

                                                                                                                                      Yeah, you know, maybe we should stop building languages that are so complex that they need explicitly inserted tokens to mark “previous thing ends here”? That’s the point I’m making.

                                                                                                                                      when adding new syntax to the language

                                                                                                                                      Cry me a river. Adding features does not improve a language.

                                                                                                                                      1. 1

                                                                                                                                        Having a clear syntax where errors don’t occur 15 lines below the missing ) or } (as would unavoidably happen without some separator — trust me, it’s one of OCaml’s big syntax problems for toplevel statements) is a net plus and not bloat.

                                                                                                                                        What language has no semicolon (or another separator, or parenthesis, like lisp) and still has a simple syntax? Even python has ; for same-line statements. Using vertical whitespace as a heuristic for automatic insertion isn’t a win in my book.

                                                                                                                                        1. 2

                                                                                                                                          Both Kotlin and Swift have managed to make a working , unambiguous C-like syntax without semicolons.

                                                                                                                                          1. 2

                                                                                                                                            I didn’t know. That involves no whitespace/lexer trick at all? I mean, if you flatten a whole file into one line, does it still work? Is it still in LALR(1)/LR(1)/some nice fragment?

                                                                                                                                            The typical problem in this kind of grammar is that, while binding constructs are easy to delimit (var/val/let…), pure sequencing is not. If you have a = 1 b = 2 + 3 c = 4 d = f(a) semicolons make things just simpler for the parser.

                                                                                                                                            1. 1

                                                                                                                                              Why are line breaks not allowed to be significant? I don’t think I care if I can write an arbitrarily long program on one line…

                                                                                                                                          2. 0

                                                                                                                                            Using vertical whitespace as a heuristic for automatic insertion isn’t a win in my book.

                                                                                                                                            I agree completely. I love Lua in particular. You can have zero newlines yet it requires no semicolons, due to its extreme simplicity. Lua has only one ambiguous case: when a line begins with a ( and the previous line ends with a value.

                                                                                                                                            a = b
                                                                                                                                            (f or g)() -- call f, or g when f is nil
                                                                                                                                            

                                                                                                                                            Since Lua has no semantic newlines, this is exactly equivalent to:

                                                                                                                                            a = b(f or g)()
                                                                                                                                            

                                                                                                                                            The Lua manual thus recommends inserting a ; before any line starting with (.

                                                                                                                                            a = b
                                                                                                                                            ;(f or g)()
                                                                                                                                            

                                                                                                                                            But I have never needed to do this. And if I did, I would probably write this instead:

                                                                                                                                            a = b
                                                                                                                                            local helpful_explanatory_name = f or g
                                                                                                                                            helpful_explanatory_name()
                                                                                                                                            
                                                                                                                          2. 3

                                                                                                                            Also curious, as well as why Zig uses parentheses in ifs etc. I know what I’ll say is lame, but those two things frustrate me when looking at Zig’s code. If I could learn the rationale, it might hopefully at least make those a bit easier for me to accept and get over.

                                                                                                                            1. 3

                                                                                                                              One reason for this choice is to remove the need for a ternary operator without greatly harming ergonomics. Having the parentheses means that the blocks may be made optional which allows for example:

                                                                                                                              const foo = if (bar) a else b;
                                                                                                                              
                                                                                                                              1. 9

                                                                                                                                There’s a blog post by Graydon Hoare that I can’t find at the moment, where he enumerates features of Rust he thinks are clear improvements over C/C++ that have nothing to do with the borrow checker. Forcing if statements to always use braces is one of the items on his list; which I completely agree with. It’s annoying that in C/C++, if you want to add an additional line to a block of a brace-less if statement, you have to remember to go back and add the braces; and there have been major security vulnerabilities caused by people forgetting to do this.

                                                                                                                                1. 6
                                                                                                                                2. 6

                                                                                                                                  The following would work just as well:

                                                                                                                                  const foo = if bar { a } else { b };
                                                                                                                                  

                                                                                                                                  I’ve written an expression oriented language, where the parenthesis were optional, and the braces mandatory. I could use the exact same syntactic construct in regular code and in the ternary operator situation.

                                                                                                                                  Another solution is inserting another keyword between the condition and the first branch, as many ML languages do:

                                                                                                                                  const foo = if bar then a else b;
                                                                                                                                  
                                                                                                                                  1. 2

                                                                                                                                    I don’t get how that’s worth making everything else ugly. I imagine there’s some larger reason. The parens on ifs really do feel terrible after using go and rust for so long.

                                                                                                                                    1. 1

                                                                                                                                      For what values of a, b, c would this be ambiguous?

                                                                                                                                      const x = if a b else c
                                                                                                                                      

                                                                                                                                      I guess it looks a little ugly?

                                                                                                                                      1. 5

                                                                                                                                        If b is actually a parenthesised expression like (2+2), then the whole thing looks like a function call:

                                                                                                                                        const x = if a (2+2) else c
                                                                                                                                        

                                                                                                                                        Parsing is no longer enough, you need to notice that a is not a function. Lua has a similar problem with optional semicolon, and chose to interpret such situations as function calls. (Basically, a Lua instruction stops as soon as not doing so would cause a parse error).

                                                                                                                                        Your syntax would make sense in a world of optional semicolons, with a parser (and programmers) ready to handle this ambiguity. With mandatory semicolons however, I would tend to have mandatory curly braces as well:

                                                                                                                                        const x = if a { b } else { c };
                                                                                                                                        
                                                                                                                                        1. 4

                                                                                                                                          Ah, Julia gets around this by banning whitespace between the function name and the opening parenthesis, but I know some people would miss that extra spacing.

                                                                                                                                        2. 3
                                                                                                                                          abs() { x = if a < 0 - a else a }
                                                                                                                                          
                                                                                                                                          1. 1

                                                                                                                                            Thanks for the example!

                                                                                                                                            I think this is another case where banning bad whitespace makes this unambiguous.

                                                                                                                                            a - b => binary
                                                                                                                                            -a => unary
                                                                                                                                            a-b => binary
                                                                                                                                            a -b => error
                                                                                                                                            a- b => error
                                                                                                                                            - a => error
                                                                                                                                            

                                                                                                                                            You can summarise these rules as “infix operators must have balanced whitespace” and “unary operators must not be followed by whitespace”.

                                                                                                                                            Following these rules, your expression is unambiguously a syntax error, but if you remove the whitespace between - and a it works.

                                                                                                                                            1. 1

                                                                                                                                              Or you simply ban unary operators.

                                                                                                                                              1. 1

                                                                                                                                                Sure, seems a bit drastic, tho. I like unary logical not, and negation is useful sometimes too.

                                                                                                                                                1. 1

                                                                                                                                                  Not sure how some cryptic operator without working jump-to-declaration is better than some bog-standard method …

                                                                                                                                                  1. 1

                                                                                                                                                    A minus sign before a number to indicate a negative number is probably recognizable as a negative number to most people in my country. I imagine most would recognise -x as “negative x”, too. Generalising that to other identifiers is not difficult.

                                                                                                                                                    An exclamation mark for boolean negation is less well known, but it’s not very difficult to learn. I don’t see why jump-to should fail if you’re using a language server, either.

                                                                                                                                                    More generally, people have been using specialist notations for centuries. Some mathematicians get a lot of credit for simply inventing a new way to write an older concept. Maybe we’d be better off with only named function calls, maybe our existing notations are made obsolete by auto-complete, but I am not convinced.

                                                                                                                              2. 9

                                                                                                                                My current feeling is that async/await is the worst way to express concurrency … except for all the other ways.

                                                                                                                                I have only minor experience with it (in Nim), but a good amount of experience with concurrency. Doing it with explicit threads sends you into a world of pain with mutexes everywhere and deadlocks and race conditions aplenty. For my current C++ project I built an Actor library atop thread pools (or dispatch queues), which works pretty well except that all calls to other actors are one-way so you now need callbacks, which become painful. I’m looking forward to C++ coroutines.

                                                                                                                                1. 3

                                                                                                                                  except for all the other ways

                                                                                                                                  I think people are complaining about the current trend to just always use async for everything. Which ends up complaining about rust having async at all.

                                                                                                                                2. 8

                                                                                                                                  This is amazing. I had similar feelings (looking previously at JS/Scala futures) when the the plans for async/await were floating around but decided to suspend my disbelief because of how good previous design decisions in the language were. Do you think there’s some other approach to concurrency fit for a runtime-less language that would have worked better?

                                                                                                                                  1. 17

                                                                                                                                    My belief is generally that threads as they exist today (not as they existed in 2001 when the C10K problem was written, but nevertheless keeps existing as zombie perf canon that no longer refers to living characteristics) are the nicest choice for the vast majority of use cases, and that Rust-style executor-backed tasks are inappropriate even in the rare cases where M:N pays off in languages like Go or Erlang (pretty much just a small subset of latency-bound load balancers that don’t perform very much CPU work per socket). When you start caring about millions of concurrent tasks, having all of the sources of accidental implicit state and interactions of async tasks is a massive liability.

                                                                                                                                    I think The ADA Ravenscar profile (see chapter 2 for “motivation” which starts at pdf page 7 / marked page 3) and its successful application to safety critical hard real time systems is worth looking at for inspiration. It can be broken down to this set of specific features if you want to dig deeper. ADA has a runtime but I’m kind of ignoring that part of your question since it is suitable for hard real-time. In some ways it reminds me of an attempt to get the program to look like a pretty simple petri net.

                                                                                                                                    I think that message passing and STM are not utilized enough, and when used judiciously they can reduce a lot of risk in concurrent systems. STM can additionally be made wait-free and thus suitable for use in some hard real-time systems.

                                                                                                                                    I think that Send and Sync are amazing primitives, and I only wish I could prove more properties at compile time. The research on session types is cool to look at, and you can get a lot of inspiration about how to encode various interactions safely in the type system from the papers coming out around this. But it can get cumbersome and thus create more risks to the overall engineering effort than it solves if you’re not careful.

                                                                                                                                    A lot of the hard parts of concurrency become a bit easier when we’re able to establish maximum bounds on how concurrent we’re going to be. Threads have a little bit more of a forcing function to keep this complexity minimized due to the fact that spawning is fallible due to often under-configured system thread limits. Having fixed concurrency avoids many sources of bugs and performance issues, and enables a lot of relatively unexplored wait-free algorithmic design space that gets bounded worst-case performance (while still usually being able to attempt a lock-free fast path and only falling back to wait-free when contention picks up). Structured concurrency often leans into this for getting more determinism, and I think this is an area with a lot of great techniques for containing risk.

                                                                                                                                    In the end we just have code and data and risk. It’s best to have a language with forcing functions that pressure us to minimize all of these over time. Languages that let you forget about accruing data and code and risk tend to keep people very busy over time. Friction in some places can be a good thing if it encourages less code, less data, and less risk.

                                                                                                                                    1. 17

                                                                                                                                      I like rust and I like threads, and do indeed regret that most libraries have been switching to async-only. It’s a lot more complex and almost a new sub-language to learn.

                                                                                                                                      That being said, I don’t see a better technical solution for rust (i.e. no mandatory runtime, no implicit allocations, no compromise on performance) for people who want to manage millions of connections. Sadly a lot of language design is driven by the use case of giant internet companies in the cloud and that’s a problem they have; not sure why anyone else cares. But if you want to do that, threads start getting in the way at 10k threads-ish? Maybe 100k if you tune linux well, but even then the memory overhead and latency are not insignificant, whereas a future can be very tiny.

                                                                                                                                      Ada’s tasks seem awesome but to the best of my knowledge they’re for very limited concurrency (i.e the number of tasks is small, or even fixed beforehand), so it’s not a solution to this particular problem.

                                                                                                                                      Of course async/await in other languages with runtimes is just a bad choice. Python in particular could have gone with “goroutines” (for lack of a better word) like stackless python already had, and avoid a lot of complexity. (How do people still say python is simple?!). At least java’s Loom project is heading in the right direction.

                                                                                                                                      1. 12

                                                                                                                                        Just like some teenagers enjoy making their slow cars super loud to emulate people who they look up to who drive fast cars, we all make similar aesthetic statements when we program. I think I may write on the internet in a way that attempts to emulate a grumpy grey-beard for similarly aesthetic socially motivated reasons. The actual effect of a program or its maintenance is only a part of our expression while coding. Without thinking about it, we also code as an expression of our social status among other coders. I find myself testing random things with quickcheck, even if they don’t actually matter for anything, because I think of myself as the kind of person who tests more than others. Maybe it’s kind of chicken-and-egg, but I think maybe we all do these things as statements of values - even to ourselves even when nobody else is looking.

                                                                                                                                        Sometimes these costumes tend to work out in terms of the effects they grant us. But the overhead of Rust executors is just perf theater that comes with nasty correctness hazards, and it’s not a good choice beyond prototyping if you’re actually trying to build a system that handles millions of concurrent in-flight bits of work. It locks you into a bunch of default decisions around QoS, low level epoll behavior etc… that will always be suboptimal unless you rewrite a big chunk of the stack yourself, and at that point, the abstraction has lost its value and just adds cycles and cognitive complexity on top of the stack that you’ve already fully tweaked.

                                                                                                                                        1. 3

                                                                                                                                          The green process abstraction seems to work well enough in Erlang to serve tens of thousands of concurrent connections. Why do you think the async/await abstraction won’t work for Rust? (I understand they are very different solutions to a similar problem.)

                                                                                                                                          1. 4

                                                                                                                                            Not who you’re asking, but the reason why rust can’t have green threads (as it used to have pre-1.0, and it was scraped), as far as I undertand:

                                                                                                                                            Rust is shooting for C or C++-like levels of performance, with the ability to go pretty close to the metal (or close to whatever C does). This adds some constraints, such as the necessity to support some calling conventions (esp. for C interop), and precludes the use of a GC. I’m also pretty sure the overhead of the probes inserted in Erlang’s bytecode to check for reduction counts in recursive calls would contradict that (in rust they’d also have to be in loops, btw); afaik that’s how Erlang implements its preemptive scheduling of processes. I think Go has split stacks (so that each goroutine takes less stack space) and some probes for preemption, but the costs are real and in particular the C FFI is slower as a result. (saying that as a total non-expert on the topic).

                                                                                                                                            I don’t see why async/await wouldn’t work… since it does; the biggest issues are additional complexity (a very real problem), fragmentation (the ecosystem hasn’t converged yet on a common event loop), and the lack of real preemption which can sometimes cause unfairness. I think Tokio hit some problems on the unfairness side.

                                                                                                                                            1. 4

                                                                                                                                              The biggest problem with green threads is literally C interop. If you have tiny call stacks, then whenever you call into C you have to make sure there’s enough stack space for it, because the C code you’re calling into doesn’t know how to grow your tiny stack. If you do a lot of C FFI, then you either lose the ability to use small stacks in practice (because every “green” thread winds up making an FFI call and growing its stack) or implementing some complex “stack switching” machinery (where you have a dedicated FFI stack that’s shared between multiple green threads).

                                                                                                                                              Stack probes themselves aren’t that big of a deal. Rust already inserts them sometimes anyway, to avoid stack smashing attacks.

                                                                                                                                              In both cases, you don’t really have zero-overhead C FFI any more, and Rust really wants zero-overhead FFI.

                                                                                                                                              I think Go has split stacks (so that each goroutine takes less stack space)

                                                                                                                                              No they don’t any more. Split Stacks have some really annoying performance cliffs. They instead use movable stacks: when they run out of stack space, they copy it to a larger allocation, a lot like how Vec works, with all the nice “amortized linear” performance patterns that result.

                                                                                                                                            2. 3

                                                                                                                                              Two huge differences:

                                                                                                                                              • Erlang’s data structures are immutable (and it has much slower single threaded speed).
                                                                                                                                              • Erlang doesn’t have threads like Rust does.

                                                                                                                                              That changes everything with regard to concurrency, so you can’t really compare the two. A comparison to Python makes more sense, and Python async has many of the same problems (mutable state, and the need to compose with code and libraries written with other concurrency models)

                                                                                                                                        2. 4

                                                                                                                                          I’d like to see a good STM implementation in a library in Rust.

                                                                                                                                      2. 6

                                                                                                                                        The fixation on async-await, despite it slowing down almost every real-world workload it is applied to, and despite it adding additional bug classes and compiler errors that simply don’t exist unless you start using it, has been particularly detrimental to the ecosystem.

                                                                                                                                        I’m curious about this perspective. The number of individual threads available on most commodity machines even today is quite low, and if you’re doing anything involving external requests on an incoming-request basis (serializing external APIs, rewriting HTML served by another site, reading from slow disk, etc) and these external requests take anything longer than a few milliseconds (which is mostly anything assuming you have a commodity connection in most parts of the world, or on slower disks), then you are better off with a some form of “async” (or otherwise lightweight concurrent model of execution.) I understand that badly-used synchronization can absolutely tank performance with this many “tasks”, but in situations where synchronization is low (e.g. making remote calls, storing state in a db or separate in-memory cache), performance should be better than threaded execution.

                                                                                                                                        Also, if I reach for Rust I’m deliberately avoiding GC. Go, Python, and Haskell are the languages I tend to reach for if I just want to write code and not think too hard about who owns which portion of data or how exactly the runtime schedules my code. With Rust I’m in it specifically to think about these details and think hard about them. That means I’m more prone to write complicated solutions in Rust, because I wouldn’t reach for Rust if I wanted to write something “simple and obvious”. I suspect a lot of other Rust authors are the same.

                                                                                                                                        1. 5

                                                                                                                                          The number of individual threads available on most commodity machines even today is quite low

                                                                                                                                          I don’t agree with the premise here. It depends more on the kernel, not the “machine”, and Linux in particular has very good threading performance. You can have 10,000 simultaneous threads on vanilla Linux on a vanilla machine. async may be better for certain specific problems, but that’s not the claim.

                                                                                                                                          Also a pure async model doesn’t let you use all your cores, whereas a pure threading model does. If you really care about performance and utilization, your system will need threads or process level concurrency in some form.

                                                                                                                                          1. 4

                                                                                                                                            I don’t agree with the premise here. It depends more on the kernel, not the “machine”, and Linux in particular has very good threading performance. You can have 10,000 simultaneous threads on vanilla Linux on a vanilla machine. async may be better for certain specific problems, but that’s not the claim.

                                                                                                                                            I wasn’t rigorous enough in my reply, apologies.

                                                                                                                                            What I meant to say was, the number of cores available on a commodity machine is quite low. Even if you spawn thousands of threads, your actual thread-level parallelism is limited to the # of cores available. If you’re at the point where you need to spawn more kernel threads than there are available cores, then you need to put engineering into determining how many threads to create and when. For IO bound workloads (which I described in my previous post), the typical strategy is to create a thread pool, and to allocate threads from this pool. Thread pools themselves are a solution so that applications don’t saturate available memory with threads and so you don’t overwhelm the kernel with time spent switching threads. At this point, your most granular “unit of concurrency” is each thread in this thread pool. If most of your workload is IO bound, you end up having to play around with your thread pool sizes to ensure that your workload is processed without thread contention on the one hand (too few threads) or up against resource limits (too many threads). You could of course build a more granular scheduler atop these threads, to put threads “to sleep” once they begin to wait on IO, but that is essentially what most async implementations are, just optimizations on “thread-grained” applications. Given that you’re already putting in the work to create thread pools and all of the fiddly logic with locking the pool, pulling out a thread, then locking and putting threads back, it’s not a huge lift to deal with async tasks. Of course if your workload is CPU bound, then these are all silly, as your main limiting resource is not IO but is CPU, so performing work beyond the amount of available CPU you have necessitates queuing.

                                                                                                                                            Moreover the context with which I was saying this is that most Rust async libraries I’ve seen are async because they deal with IO and not CPU, which is what async models are good at.

                                                                                                                                          2. 3

                                                                                                                                            Various downsides are elaborated at length in this thread.

                                                                                                                                            1. 2

                                                                                                                                              Thanks for the listed points. What it’s made me realize is that there isn’t really a detailed model which allows us to demonstrate tradeoffs that come with selecting an async model vs a threaded model. Thanks for some food for thought.

                                                                                                                                              My main concern with Rust async is mostly just its immaturity. Forget the code semantics; I have very little actual visibility into Tokio’s (for example) scheduler without reading the code. How does it treat many small jobs? Is starvation a problem, and under what conditions? If I wanted to write a high reliability web service with IO bound logic, I would not want my event loop to starve a long running request that may have to wait longer on IO than a short running request and cause long running requests to timeout and fail. With a threaded model and an understanding of my IO processing latency, I can ensure that I have the correct # of threads available with some simple math and not be afraid of things like starvation because I trust the Linux kernel thread scheduler much more than Tokio’s async scheduler.

                                                                                                                                          3. 3

                                                                                                                                            There’s no if-statement community

                                                                                                                                            That had me laughing out loud!

                                                                                                                                            1. 2

                                                                                                                                              probably because it’s if-expressions 🙃

                                                                                                                                            2. 2

                                                                                                                                              I hope I’m not opening any wounds or whacking a bee-hive for asking but… what sort of problematic interactions occur with the Rust community? I follow Rust mostly as an intellectual curiosity and therefor aren’t in deep enough to see the more annoying aspects. When I think of unprofessional language community behavior my mind mostly goes to Rails during the aughts when it was just straight-up hostile towards Java and PHP stuff. Is Rust doing a similar thing to C/C++?

                                                                                                                                            1. 27

                                                                                                                                              An approximated summary based on titles of video segments [?], for people who don’t want/can’t watch the video now:

                                                                                                                                              • “Language spec”
                                                                                                                                              • “Self-hosted compiler”
                                                                                                                                              • “[Merging & discussing PRs and helping people get unstuck - good PRs can also influence our priorities]”
                                                                                                                                              • “LLVM [got a new] release[, we need to rebase/update]”
                                                                                                                                              • “Official package manager”
                                                                                                                                              • “Zig (Bug) Stability Program”

                                                                                                                                              then the video proceeds to a Q&A apparently taking ~30min of the ~50min video, so there might be some notable content there too.

                                                                                                                                              1. 7

                                                                                                                                                I guess one of the coolest features of Zig promised for 0.7.0 falls under the “Self-hosted compiler” point.

                                                                                                                                                1. 5

                                                                                                                                                  Given that LLVM 12 is coming out soon and that Zig ties the latest stable release to latest stable LLVM, 0.8.0 will come out without enough progress on self-hosted to make it viable for everyday use (so it will continue to stay behind a flag). Nothing changes in terms of actual timeline, just that it will have to be a different release number compared to the original prediction, the ETA is still sometime in 2021.

                                                                                                                                              1. 1

                                                                                                                                                I’m going to go out on a limb here and recommend something much more concrete then the standard theory books (Schneier and others): Zero to Monero.

                                                                                                                                                Concrete stuff has a much better chance to stick than abstract and people learn from the concrete to the abstract, not the other way round (standard books tend to be organized like that).

                                                                                                                                                The book is also structured very gradually which eases understanding.

                                                                                                                                                The downside is you get only a slice of only some of the crypto algorithms, but you can widen that knowledge by reading other sources after it.