Hacker Newsnew | past | comments | ask | show | jobs | submit | hmry's commentslogin

I wouldn't say it's "to blame", but it is more susceptible to bad RNG.

If the RNG is bad, you'll get more benefit from adding non-random bits than you would from additional badly RNG'd bits.

The probability of future collisions also rises the more IDs you generate. If you incorporate non-random bits, you can alleviate that:

- timestamps make the collision probability not grow over time as you accumulate more existing UUIDs that could collide

- known-distinct machine IDs make the collision probability not grow as you add more machines


Great article! Love these types of deep dives into optimizations. Hope the project goal works out!

I've felt before that compilers often don't put much effort into optimizing the "trivial" cases.

Overly dramatic title for the content, though. I would have clicked "Async Rust Optimizations the Compiler Still Misses" too you know


So on the title, I picked this because it's simply the truth. Since async landed in 2019 or so, not much has changed.

Yes, we can have async in traits and closures now. But those are updates to the typesystem, not to the async machinery itself. Wakers are a little bit easier to work with, but that's an update to std/core.

As I understand it, the people who landed async Rust were quite burnt out and got less active and no one has picked up the torch. (Though there's 1 PR open from some google folk that will optimize how captured variables are laid out in memory, which is really nice to have) Since I and the people I work with are heavy async users, I think it's maybe up to me to do it or at least start it. Free as in puppy I guess.

So yeah, the title is a little baitey, but I do stand behind it.


Some of the burnout no doubt being due to the catastrophizing of every decision by the community and the extreme rhetoric used across the board.

Great to see people wanting to get involved with the project, though. That’s the beauty of open source: if it aggravates you, you can fix it.


As an example of this, i remember a huge debate at the time about `await foo()` vs `foo().await` syntax. The community was really divided on that one, and there was a lot of drama because that's the kind of design decision you can't really walk back from.

Retrospectively, i think everyone is satisfied with the adopted syntax.


It makes sense that there was a huge debate, because the postfix .await keyword was both novel (no other languages had done it that way before) and arguably the right call. Of course, one can argue that the ? operator set a relevant precedent.

> Retrospectively, i think everyone is satisfied with the adopted syntax.

Maybe it’s a case of agree and commit, since it can’t really be walked back.


Various prominent people have said years after that .await was the correct choice after all

I'm not prominent but I disagreed with it at the time and I was wrong.

I’m curious - why were you wrong? It still seems like a wart to me, all these years later. What am I missing?

Contrast it with async in JS/ES as an example... now combine it with the using statement for disposeAsync instances.

    await using db = await sqlite.connect(await ctx.getConfig("DB_CONN"));
It's not so bad when you have one `await foo` vs `foo.await`, it's when you have several of them on a line in different scopes/contexts.

Another one I've seen a lot is...

    const v = await (await fetch(...)).json();
    
Though that could also be...

    const v = await fetch(...).then(r => r.json());
In any case, it still gets ugly very quickly.

I’ve never in my life used JS, so I’ll have to take your word for it.

It's a language I'm familiar with that uses the `await foo` syntax and often will see more than one in a line, per the examples given. C# is the most prominent language that has similar semantics that I know well, but is usually less of an issue there.

I'll give my two cents here. I work with Dart daily, and it also uses the `await future` syntax. I can cite a number of ergonomic issues:

```dart (await taskA()).doSomething() (await taskB()) + 1 (await taskC()) as int ```

vs.

```rust taskA().await.doSomething() taskB().await + 1 taskC().await as i32 ```

It gets worse if you try to compose:

```dart (await taskA( (await taskB( (await taskC()) as int )) + 1) ).doSomething() ```

This often leads to trading the await syntax for `then`:

```dart await taskC() .then((r) => r as i32) .then(taskB) .then((r) => r + 1) .then(taskA) .then((r) => r.doSomething()) ```

But this is effectively trading the await structured syntax for a callback one. In Rust, we can write it as this:

```rust taskA(taskB(taskC().await as i32).await + 1).await.doSomething() ```


Two spaces before a line make it a code block literal

  This is a code block
HN has never used markdown so the triple-tick does nothing but create noise here.

Thnaks for heads up, I'll keep this in mind in the future.

I was a proponent of the postfix macro solution. `.await!` or `.await!()`, essentially. The idea was that this could be generalized, it was closer to existing syntax, etc.

I was worried about features that I still don't love like `.match` etc (I'm more open to these now).

Post-fix macros would have been very complex. Scoping alone is complex.

`.await` kinda just works. It does everything you want and the one cost is that it looks like a property access but it isn't. A trivial cost in retrospect that I was a huge baby about, and I'll always feel bad about that.


The postfix macro does sound like a better solution tbh. Did you write the static assertions crate? If so, thank you. I’m a daily user.

Ha, no, I did not write that crate. I think my use of this username probably predates rust, certainly that crate.

Postfix macros had some very tricky issues and it would have delayed things a lot to figure out the right resolution.


I was initially very against postfix await and I was wrong. It's great.

I think it's partially accurate, and partially a consequence of how async fractures the design space, so it will always feel like a somewhat separate thing, or at least until we figure out how to make APIs agnostic to async-ness.

I am a beginner to Rust but I've coded with gevent in Python for many years and later moved to Go. Goroutines and gevent greenlets work seamlessly with synchronous code, with no headache. I know there've been tons of blog posts and such saying they're actually far inferior and riskier but I've really never had any issues with them. I am not sure why more languages don't go with a green thread-like approach.

Because they have their own drawbacks. To make them really useful, you need a resizable stack. Something that's a no-go for a runtime-less language like Rust.

You may also need to setup a large stack frame for each C FFI call.


Rust originally came with a green thread library as part of its primary concurrency story but it was removed pre-1.0 because it imposed unacceptable constraints on code that didn’t use it (it’s very much not a zero cost abstraction).

As an Elixir + Erlang developer I agree it’s a great programming model for many applications, it just wasn’t right for the Rust stdlib.


One of Rust's central design goals is to allow zero cost abstractions. Unifying the async model by basically treating all code as being possibly async would make that very challenging, if not impossible. Could be an interesting idea, but not currently tenable.

One problem I have with systems like gevent is that it can make it much harder to look at some code and figure out what execution model it's going to run with. Early Rust actually did have a N:M threading model as part of its runtime, but it was dropped.

I think one thing Rust could do to make async feel less like an MVP is to ship a default executor, much like it has a default allocator.


They could still come in a step short of default executor and establish some standard traits/types that are typical across executors.

By providing a default, I think you're going to paint yourself into a corner. Maybe have one of two opt-in executors in the box... one that is higher resource like tokio and one that is meant for lower resource environments (like embedded).


Claim-1: the async versus sync distinction cannot be meaningfully dissolved at the programmer level.

Claim-2: async versus sync is a fundamental division in CS

Discuss amongst yourselves. I lean towards thinking both are probably true (P~70%, P~90%)

See: “What Color is Your Function?” by Bob Nystrom (2015). https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...


As an uninterested 3rd party, it’s a wild exaggeration

> So on the title, I picked this because it's simply the truth. Since async landed in 2019 or so, not much has changed.

Hi. The article calls Rust async an MVP. You should expect strong reactions when you frame it like that.

"MVP" has a generally understood meaning; distorting that is unhelpful and confusing. Rust's async was not an MVP when it was released in 2019. It was the result of a lot of earlier work.

Rust async: (a) works well for a lot of people and orgs in production settings and (b) is arguably better designed than most (all?) other async implementations. Calling it an MVP is far from "simply the truth". It is an opinion -- and frankly a pretty clickbaity one. I appreciate your article's attention to detail, but the title is straight up shameful sensationalism.

I strive to not reflexively defend the status quo, but I get really chafed when people conveniently blur the difference between fact and opinion.

Please argue on narrowest correct claims available. The current title overstates your claims and undermines its overall credibility. Your central claim (as I read it) is that for embedded software there are opportunities for async improvement in Rust. Yeah this might sound boring, but I think it's accurate.

My other main criticism of your article is when it claims Rust async breaks the "zero cost abstraction" principle. I don't buy this claim, because you do not show that hand rolling the code provides the same guarantees. A lot of people misunderstand what "zero cost" means; your article wouldn't be the first to give the wrong impression.

Writing is hard (different audiences bring different backgrounds), and I commend anyone who puts their ideas out into the world. Please take this as constructive feedback: please agree or disagree with me on the merits. Ask and engage where I'm unclear.


> Rust's async was not an MVP when it was released in 2019

The team literally described it as such.

One of the main architects of Rust’s async/await, withoutboats, left a comment on lobsters:

> It's just the truth. Neither in the language design nor in the compiler implementation has hardly any progress been made in the now 7 years since we shipped the MVP. The people primarily involved in delivering the MVP all become less active in the project around the same time and delivery since then has stalled out.

>

> I hope this person receives the support to do this work.

Rust’s async is great, and I feel you around some of the less informed criticisms. But it’s been called an MVP for a decade now, it’s not an insulting characterization. Just because it’s been an MVP does not mean it’s not good or useful.


Arguably the state it was left in is the state that was sufficient for the needs of all adopters. Don't fix something if it ain't broke. I know it's been sufficient for fuchsia which did a lot of the initial investment. The types of improvements described in the post tend to only matter at scale or in embedded use cases. No one from those realms has decided it was a sufficiently important problem to prioritize solving until now so it didn't get solved.

Even if MVP is the correct term for its current state, it has a connotation to it which less informed folks will take away the wrong meaning from, so perhaps it's not useful to continue to propagate it even if true.


> No one from those realms has decided it was a sufficiently important problem to prioritize solving until now so it didn't get solved.

This is both true and not true. It's no secret that the async ecosystem has had deep social rifts for a very long time, and that's made it very tough to actually make progress by anyone, regardless of the desire to.

It is true that async massively gave a boost to Rust's adoption, and it is good enough for many users. It is a monumental technical achievement. At the same time, that doesn't mean it's perfect.


Agree on title. Too dramatic.

The author seems to be obsessing about the overhead for trivial functions. He's bothered by overhead for states for "panicked" and "returned". That's not a big problem. Most useful async blocks are big enough that the overhead for the error cases disappears.

He may have a point about lack of inlining. But what tends to limit capacity for large numbers of activities is the state space required per activity.


> Most useful async blocks are big enough that the overhead for the error cases disappears.

Is it really though?

In my experience many Rust applications/libraries can be quite heavy on the indirection. One of the points from the article is that contrary to sync Rust, in async Rust each indirection has a runtime cost. Example from the article:

    async fn bar(blah: SomeType) -> OtherType {
       foo(blah).await
    }
I would naively expect the above to be a 'free' indirection, paying only a compile-time cost for the compiler to inline the code. But after reading the article I understand this is not true, and it has a runtime cost as well.

In my experience, it's not uncommon to have an async trait method for which many implementations are actually synchronous. For example, different tables in your DB need to perform some calculations, but only some tables reference other tables. In that case, the method needs to be async and take a handle to the DB as parameter, but many table entries can perform the calculation on their own without using the handle (or any async operation).

This may look like a case of over-optimization, but given how many times i've seen this pattern, i assume it builds up to a lot of unnecessary fluff in huge codebases. To be clear, in that case, the concern is not really about runtime speed (which is super fast), but rather about code bloat for compilation time and binary size.


> Most useful async blocks are big enough that the overhead for the error cases disappears.

Most useful async blocks are deeply nested, so the overhead compounds rapidly. Check the size of futures in a decently large Tokio codebase sometime


He's optimizing for embedded no-std situation. These things do matter in constrained environments.

He briefly mentions microcontrollers, but doesn't go into details. That might make sense, for microcontrollers which implement some kind of protocol talking to something.

> [...] That's not a big problem [...]

Depends somewhat on your expectations, I suppose. Compared to Python, Java, sure, but Rust off course strives to offer "zero-cost" high level concepts.

I think the critique is in the same realm of C++'s std::function. Convenience, sure, but far from zero-cost.


To the point it got replaced by std::function_ref() in C++26.

Exactly. And I guess that is also the gist of the article: async Rust needs additional TLC.

> Agree on title. Too dramatic.

not just too dramatic

given that all the things they list are

non essential optimizations,

and some fall under "micro optimizations I wouldn't be sure rust even wants",

and given how far the current async is away from it's old MVP state,

it's more like outright dishonest then overly dramatic

like the kind of click bait which is saying the author does cares neither about respecting the reader nor cares about honest communication, which for someone wanting to do open source contributions is kinda ... not so clever

through in general I agree rust should have more HIR/MIR optimizations, at least in release mode. E.g. its very common that a async function is not pub and in all places directly awaited (or other wise can be proven to only be called once), in that case neither `Returned` nor `Panicked` is needed, as it can't be called again after either. Similar `Unresumed` is not needed either as you can directly call the code up to the first await (and with such a transform their points about "inlining" and "asyncfns without await still having a state machine" would also "just go away"TM, at least in some places.). Similar the whole `.map_or(a,b)` family of functions is IMHO a anti-pattern, introducing more function with unclear operator ordering and removal of the signaling `unwrap_` and no benefits outside of minimal shortening a `.map(b).unwrap_or(a)` and some micro opt. is ... not productive on a already complicated language. Instead guaranteed optimizations for the kind of patterns a `.map(b).unwrap_or(a)` inline to would be much better.


"I'd just like to interject for a moment. What you're referring to as macOS, is in fact, macOS/Darwin, or as I've recently taken to calling it, macOS plus Darwin."

"What you're referring to as Darwin, is in fact, Darwin/XNU."

"What you're referring to as XNU, is in fact, BSD/Mach."

I seem to remember it being possible to run macOS-less Darwin several years ago, not sure if that's still possible or if Apple has modified it so much at this point that it's useless without at least some macOS components.


https://github.com/apple/darwin-xnu

Apple stopped updating this 5 years ago.

I remember getting it to boot once long ago but I didn't have anything to actually do with it.


Looks like it is still getting updates and has moved here: https://github.com/apple-oss-distributions/xnu

I now think of things in terms of token budget. I put my MacOS VM aspirations on the back burner because the effort was taking up 100 GB of space and I made poor choices when it came to laptop specs. Now I'm thinking why not rebuild XNU but I have other things I'd rather spend the tokens on. I don't want to delay other projects so I'm giving up something stupid and fun.

> several years ago

2024, maybe? needs some renewed interest perhaps:

https://www.puredarwin.org/


Needs someone to pick it up: its project leader passed away last year.

AFAIK Asahi development needs some hypervisor features for reverse engineering macOS drivers that only exist on M1-M3 and were removed on M4+. So yeah, it may be several years until they get support (or never, if nobody steps up to do it).


Is there any constant more misused in compsci than ieee epsilon? :)

It's defined as the difference between 1.0 and the smallest number larger than 1.0. More usefully, it's the spacing between adjacent representable float numbers in the range 1.0 to 2.0.

Because floats get less precise at every integer power of two, it's impossible for two numbers greater than or equal to 2.0 to be epsilon apart. The spacing between 2.0 and the next larger number is 2*epsilon.

That means `abs(a - b) <= epsilon` is equivalent to `a == b` for any a or b greater than or equal to 2.0. And if you use `<` then the limit will be 1.0 instead.

Epsilon is the wrong tool for the job in 99.9% of cases.


A (perhaps initially) counterintuitive part of the above more explicitly stated: The doubling/halving also means numbers between 0 and 1 actually have _more_ precision than the epsilon would suggest.


Considerably more in many cases. The point of floating point is to have as many distinct values in the range 2-4 as are in the range 1-2 as are between 1/2 and 1, 1/4 and 1/2, 1/8 and 1/4, etc. the smallest representable difference between consecutive floating point numbers down around the size of 1/64 is on the order of epsilon/64

Multiplying epsilon by the largest number you are dealing with is a strategy that makes using epsilons at least somewhat logical.


The term I've seen a lot is https://en.wikipedia.org/wiki/Unit_in_the_last_place

So I'd probably rewrite that code to first find the ulp of the larger of the abs of a and b and then assert that their difference is less than or equal to that.

Edit: Or maybe the smaller of the abs of the two, I haven't totally thought through the consequences. It might not matter, because the ulps will only differ when the numbers are significantly apart and then it doesn't matter which one you pick. Perhaps you can just always pick the first number and get its ULP.


This is what was done to a raytracer I used. People kept making large-scale scenes with intricate details, think detailed ring placed on table in a room with a huge field in view through the window. For a while one could override the fixed epsilon based on scene scale, but for such high dynamic range scenes a fixed epsilon just didn't cut it.

IIRC it would compute the "dynamic" epsilon value essentially by adding one to the mantissa (treated as an integer) to get the next possible float. Then subtract from that the initial value to get the dynamic epsilon value.

Definitely use library functions if you got 'em though.


Because of the representation of floats, couldn't you just bitwise cast to uints and see if the (abs) difference was less than or equal to one? But practically you probably should check if it's less than or equal to say ten, depending on your tolerance.


i find the best way to remember it is "it's not the epsilon you think it is."

epsilons are fine in the case that you actually want to put a static error bound on an equality comparison. numpy's relative errors are better for floats at arbitrary scales (https://numpy.org/doc/stable/reference/generated/numpy.isclo...).

edit: ahh i forgot all about ulps. that is what people often confuse ieee eps with. also, good background material in the necronomicon (https://en.wikipedia.org/wiki/Numerical_Recipes).


It would be very useful to be able to compare the significant directly then. I realize there is a boundary issue when a significant is very close to 0x00..000 or 0xFFF..FFF


It's been a few years since I worked with the dragon book, but I think the most common complaint was that it starts with like 350 pages on parser theory: generating bottom-up and top-down parsers from context free grammars, optimizing lexers for systems that don't have enough RAM to store an entire source file, etc... before ever getting to what most people who want to write a compiler care about (implementing type inference, optimizing intermediate representations, generating assembly code). Of course parsing is important, and very interesting to some. But there's a reason most modern resources skip over all of that and just make the reader write a recursive descent parser.


I guess "back in the day" you had to be able to write an efficient parser, as no parser generators existed. If you couldn't implement whatever you wanted due to memory shortage at the parser level, then obviously it's gonna be a huge topic. Even now I believe it is good to know about this - if only to avoid pitfalls in your own grammar.

I repeatedly skip parts that are not important to me when reading books like this. I grabbed a book about embedded design and skipped about half of it, which was bus protocols, as I knew I wouldn't need it. There is no need to read the dragon book from front to back.

  > But there's a reason most modern resources skip over all of that and just make the reader write a recursive descent parser.
Unless the reason is explicitly stated there is no way to verify it's any good. There's a reason people use AI to write do their homework - it just doesn't mean it's a good one. I can think of plenty arguments for why you wouldn't look into the pros and cons of different parsing strategies in an introduction to compilers, "everyone is(or isn't) doing it" does not belong to them. In the end, it has to be written down somewhere, and if no other book is doing it for whatever reason, then the dragon book it shall be. You can always recommend skipping that part if someone asks about what book to use.


The thing about parsing (and algorithms in general) is that it can be hair raisingly complex for arbitrary grammars, but in practice, people have recently discovered, that making simple, unambiguous grammars, and avoiding problems, like context dependent parsing, make the parsing problem trival.

Accepting such constraints is quite practical, and lead to little to no loss of power.

In fact, most modern languages are designed with little to no necessary backtracking and simple parsing, Go and Rust being noteworthy examples.


> In fact, most modern languages are designed with little to no necessary backtracking and simple parsing, Go and Rust being noteworthy examples.

But to understand how to generate grammars for languages that are easy to parse, you have in my opinion to dive quite deeply into parsing theory to understand which subtle aspects make parsing complicated.


My personal context to understand where I'm coming from - I'm working on my own language, which is a curly-brace C-style language with quite, where I didn't try to stray too far from established norms, and the syntax is not that fancy (at least not in that regard). I also want my language to look familiar to most programmers, so I'm deliberately sticking close to established norms.

I'm thankfully past the parsing stage and so far I haven't really encountered much issues with ambiguity, but when I did, I was able to fix them.

Also in certain cases I'm quite liberal with allowing omission of parentheses and other such control tokens, which I know leads to some cases where either the code is ambiguous (as in there's no strictly defined way the compiler is supposed to interpret it) or valid code fails to parse,

So far I have not tackled this issue, as it can always be fixed by the programmer manually adding back those parens for example. I know this is not up to professional standards, but I like the cleanliness of the syntax and simplicity of the compiler, and the issue is always fixable for me later. So this is a firm TODO for me.

Additionally I have some features planned that would crowd up the syntax space in a way that I think would probably need some academic chops to fix, but I'm kinda holding off on those, as they are not central to the main gimmickTM and I want release this thing in a reasonable timeframe.

I don't really have much of a formal education in this, other than reading a few tutorials and looking through a few implementations.

Btw, besides just parsing, there are other concerns in modern languages, such as IDE support, files should be parseable independently etc., error recovery, readable errors, autocomplete hints, which I'm not sure are addressed in depth in the dragon book. These features I do want.

My two cents is that for a simple modern language, you can get quite far with zero semantic model, while with stuff like C++ (with macros), my brain would boil at the thought of having to write a decent IDE backend.


I actually think the parsing part is more important for laymen. Like, there may be a total of 10K programmers who are interested in learning compiler theories, but maybe 100 of them are ever going to write the backend -- the rest of them are stuck with either toy languages, or use parsing to help with their job. Parsing is definitely more useful for most of us who are not smart enough :D


Yeah I agree, that seems vey true. Although the average person probably also benefits more from learning about recursive descent and pratt parsing than LL(k) parser generators, automata, and finding first and follow sets :)


1) If someone killed my child, I would probably want to kill them back. And yet we don't consider that sufficient reason to make revenge killing legal. The wishes of the victims need to be weighed against the cost it imposes on everyone else, including those who are innocent. The cost of violating everyone's right to privacy, the social impacts of mass surveillance, and the risk of that data being abused.

2) > Isn't that a privacy risk?

Yes, it is!

> Should we ban cameras in smartphones?

No? How about making it difficult for the police to seize everyone's videos without a good reason? We already do that for phone videos, it's called warrants. But Flock doesn't. They just ask cops to enter any arbitrary "reason" text into a HTML textbox and instantly get access to everyone's videos. And if the people explicitly said they don't want those specific cops to have access, like many people decided about ICE? Well, just ask the next county over and use their system, it's not checked in any way.


People don't have a right to privacy in public (at least in the US). Do people not realize anyone can photograph or film them in public at any time. Heck, photographers can even then around and sell the without the subject's consent. Case in point: https://en.wikipedia.org/wiki/Nussenzweig_v._DiCorcia

I'm really struggling to see the parallel between being filmed in public and committing revenge murder.


Sorry if I was unclear. My point was just that "if you were a victim, wouldn't you want this?" is not a very strong argument. What victims want does matter. But when it affects other people, their needs matter too.

Especially with mass-surveillance, which affects everyone. It's not possible to mass-surveil only people who would commit crimes, you need to surveil all innocent people too.


> My point was just that "if you were a victim, wouldn't you want this?" is not a very strong argument. What victims want does matter. But when it affects other people, their needs matter too.

Right and you used murder as an example. Do you think murder is even remotely comparable to putting up a security camera in a public space?

Yes, a victim might want some sort of response that is socially unacceptable, sure. But if you want to make a convincing argument you have to explain why the proposed response is unacceptable. Not some different, extreme, response of your own invention.

I'm really not sure how "committing vigilante murder is wrong" is supposed to be a good argument against putting up security cameras in a public space.


[flagged]


Which points do you disagree with?


The part that you prioritise your convenience over life-long tragedy of someone else.


Privacy is not "convenience", I'm not sure how you arrive at that. And it's also not mine, it's everyone's.

I don't want children to die (obviously). I also don't want governments to track the movement of protestors and dissidents, police to stalk their ex-girlfriends, etc.

I don't think the effectiveness of mass AI surveillance in preventing crime is high enough to justify the drawbacks.


Two different meanings of "forever" there. An OS runs for an arbitrarily large finite time, which is different from an infinite time.

Same way you can count to any finite integer with enough time, but you can never count to infinity.

Those kinds of interactive programs take in a stream of input events, which can be arbitrarily long, but eventually ends when the computer is shut down.

Termination checkers don't stop you from writing these interactive loops, they only stop non-interactive loops


I used to be able to run ROCm on my officially unsupported 7840U. Bought the laptop assuming it would continue to work.

Then in a random Linux kernel update they changed the GPU driver. Trying to run ROCm now hard-crashed the GPU requiring a restart. People in the community figured out which patch introduced the problem, but years later... Still no fix or revert. You know, because it's officially unsupported.

So "Just use HSA_OVERRIDE_GFX_VERSION" is not a solution. You may buy hardware based on that today, and be left holding the bag tomorrow.


The switch release is rated PEGI 18, ESRB M 17+


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: