17 ms·
Async and Await in Rust: a full proposal
- majewsky 8y agoUsually we add notes like (2016) to old articles. In this case it would be appropriate to add (April 2018) to the title given how fast the futures ecosystem is moving in Rust.
- TekMol 8y agoI'm waiting for the day when 'async' will be the default function type and 'await' will be the default type of function call. If anywhere down the callstack a function needs to await something it has to become an async function. And this needs to be done to the whole callstack recursively. So over time more and more functions of every codebase turn into async functions.
- drb91 8y agoAsync being a “default” function type doesn’t make much sense unless you’re doing io. If you’re just computing, you often can’t proceed without the result and want to proceed as soon as you have the result—i.e. blocking. Both are critical to getting good performance. I’m curious if discerning which is the best type is possible to tool automatically with static or tracing analysis.
- gpderetta 8y agoGenerators, a-la python for example, are completely equivalent to async functions and have nothing to do with IO.
- drb91 8y agoTrue! However, I believe rust has a separate, distinct yield/generator functionality coming that should address those needs separately. I think off-hand it’d be pretty easy to wrap them in a stream, or to join a stream into a generator, if you did want to compose them.
- steveklabnik 8y agoThat’s correct; async/await uses Generators under the hood, but we haven’t stabilized them yet. It’s the plan to though!
- quotemstr 8y agoHaving made "async" the default, you've just returned to regular threading, which is what we should have stuck with all along. The thread model is actually pretty useful, the pitfalls are well understood, and the tooling very mature.
- noobermin 8y agoI feel like you're making a very interesting point but it seems a bit broad and abstract for me to justify in my head. Could you clarify what you mean?
- ploxiln 8y agoIf you want easier concurrency with higher overhead, that's exactly what OS threads do. The OS kernel is the "event loop" in this case, and they're highly optimized for this, though there is some overhead which is hard to avoid. Rust has gone back and forth on this. Pre-1.0 versions of Rust had a "green threads" mode. It was removed in favor of OS threads because it wasn't worth the complexity. Go is a language in which "all functions are async" and it's pretty cool. But it needs a runtime, and interop with C / C++ has some real overhead related to switching to a C-style stack. Rust did not want to make those compromises, it wanted to be appropriate for bare-metal or kernel programming, and for libraries used by big high-performance C++ applications. In these cases, Go's model does not work well. I personally use Go a lot, it's great for network services and command-line tools, and that's mostly what I do.
- deleted 8y ago[deleted]
- weberc2 8y ago> In these cases, Go's model does not work well. I don't think there's anything about Go's concurrency model that precludes bare metal; I think it's just that the maintainers didn't want to support a bare metal runtime. In fact, I recall at least one other project that modified Go's runtime to support exactly this. I'm also not sure how much of the C interop overhead is due to the different stack models and how much is due to GC concerns or other things. But generally I agree with you--in practice Rust is well suited for these types of tasks and Go is not.
- kevingadd 8y agoOne of the biggest advantages of await is that it's explicit. Otherwise literally any function call can block, and whether or not it blocks is dependent on the body of the function so you can end up with races appearing in random parts of your code because you update a package. It's bad. At that point you might as well just use threads and blocking calls.
- wahern 8y agoData races like that can only happen when you share mutable state across logical flows of control. Don't share mutable state, regardless of your concurrency framework. I don't see how explicit vs implicit yield points change this fact. Either way there's precisely one logical flow of control that should own your data. In a language like Rust this is strongly enforced, regardless. Futhermore, in any complex asynchronous I/O app most functions will end up being tagged async. The only ones that won't are simple leaf functions that wouldn't implicitly yield, anyhow. If someone has the bad idea to, e.g., put a yield point in non-obvious leaf functions as part of some kind of hack (e.g. logging, tracing), they're gonna do it in the async/await case, too, because they're already convinced it has value. Having to a drop a few more annotations here or there won't stop them from breaking the app. Lastly, the bugs that do occur in these sorts of cases usually have to do with unpredictable latencies violating implicit or accidental ordering assumptions. async/await doesn't mitigate that at all because latencies are just as unpredictable. The solution, as always, is to avoid these ordering dependencies by not sharing mutable state. This defense of async/await is a red herring.
- yazaddaruvala 8y agoThis seems like the next logical step. A language where all functions are async/await. And it’ll likely pull in some nice features from academia like, building a dependency graph of async statements so it can automatically reorder async statements to get optimal concurrency.
- arianvanp 8y agoThis is Haskell's IO type and its do notation ! And the reordering of statements exists too. It's called ApplicativeDo[0] and is heavily used at Facebook[1] [0] https://www.microsoft.com/en-us/research/wp-content/uploads/2016/08/desugaring-haskell-haskell16.pdf https://www.microsoft.com/en-us/research/wp-content/uploads/... [1] https://youtu.be/sT6VJkkhy0o https://youtu.be/sT6VJkkhy0o
- Peaker 8y agoThere's a huge advantage to not everything being async/await. Knowing exactly when you yield control can give you atomicity and more determinism for free.
- noobermin 8y agoNot everything is a webserver/frontend/web related.
- eikenberry 8y agoI'm curious as to why you think that matters? Async/Await are general purpose concurrent programming abstractions.
- ori_b 8y agoConcurrency has overhead. On top of that, synchronous is easier to reason about.
- tormeh 8y agoThey are, but calling a function asynchronously takes a lot longer than synchronously. For small functions the execution time could easily be increased by more than 100%. It's something that only really makes sense if the function will do (slow) I/O or a lot of computation, and the program has other things it could do in the meantime. Making every function call asynchronous is likely to make most programs a lot slower.
- noobermin 8y agoAlright, so I do computational plasma physics simulations for a living. Given our problem sizes often reach sizes which require teraflop level of parallelism to get done before I retire (that's my scale, I'm sure you're aware of petaflop sims the fluid/engineering guys do) we do need concurrency but it's done by chopping up the domain, literally the volume of space we simulate, onto processors. Now, we could possibly do async at some level (sub node level), but we absolutely need to be syncronized globally because we are simulating real-life physics and we need to maintain causality, especially when we pass information between nodes. Say a wave front passes across one node boundary into an adjacent one, waves must move at a finite and causal manner, of course, just as if you watched a ripple on the surface of a puddle. If we did the whole problem async (async by default as I interpret it) it would require so much bookkeeping to keep causality that would make it unreasonably difficult. For things that require causality like that, we are lucky that the default mode of computation is synchronous because it fits my problem domain perfectly. That's why I say "not everything is web" because while async maps well to problems like a server-client type application, it doesn't map well to all problems like my problem for example. Also, as someone else said, sync is easier to reason about for problems that don't require a lot of parallelism.
- barrkel 8y agoThat model is called green threads. Continuation passing style means passing along your continuation to your callee, and is the implementation of the async model. But normal calls do pass their continuation: the return address, pushed on the stack, is the continuation function, and the stack itself is the scope, and together they are a closure. Make the stack a first class value that can be switched, and you get to continuations - and that's what the runtime gets with green threads. It means much harder interop because you can't have native frames on your stack. But you have the same problem with async.
- gpderetta 8y agoGreen threads is very different from CPS actually both from an implementation and semantics point of view. In a CPS model the continuation is first class and is available at every single call point. In practice this would require heap allocating every single function frame, which is expensive and and a huge problem for interoperability (a smart compiler can turn it back to a traditional stack if the continuation is only used in traditional ways) With the normall call/return model, the implicit continuation in a normal call stack can only be accessed via return. Green threads do allow accessing the continuation at specific yield points (although most green thread implementations do not expose it to the user) but because the yield point are much fewer than every single call, a whole stack can be allocated and used in one go. There aren't in fact many issues with interoperability as foregin functions or even os calls can be accommodated on this stack. Async is a tradeoff, basically the programmer is responsible of marking functions that are to be CPS transformed and that need their stack frame reified. This way no full stack is needed nor there are the performance and interoperability issues of full CPS. You end up with the blue/red functions issue though.
- barrkel 8y agoIf you want call/cc (for repeated executions of the continuation) you need a first class stack. A CPS execution model can use green threads right up until you want that explicit continuation in hand and split the stack. In most implementations of CPS the continuation is not in fact available for the user to execute: stack-oriented programming is the mental model, until you pull out call/cc or something similar. Note that you don't get the continuation in hand for the async-everywhere advocated by GP, only blocking operations get the continuation, and most of those are implemented by the runtime.
- Koshkin 8y ago> a function needs to await something According to a famous quote, the source of which escapes me, "await does not wait for anything, and async is not asynchronous." (This could be specific to C# though.)
- jeremyjh 8y agoRust worked this way earlier in its development. The keyword as others mention is “green threads”. It moved away from this because this model has some overheads, and not every application is a socket server. Languages with this feature include Go, Haskell and Erlang.
- algesten 8y agoAnd also the ABI compatibility for C interop is harder without a "classic" stack.
- pron 8y agoThis is the situation in Scheme, Erlang and Go (although the latter two strongly bind continuations with a specific, built-in scheduling), and what we're doing in Java as part of Project Loom. All Java functions will be able to run inside delimited continuations without modification. Continuations/coroutines are thus a purely dynamic rather than a syntactic entity.
- avodonosov 8y agoAny news on Project Loom? When to expect it available?
- pron 8y agoWe should have a public repo you can use to build Loom (a prototype at this stage) in a couple of weeks.
- avodonosov 8y agoWhat does such build produce? A JVM? A bytecode converter which produced code for existing JVMs?
- pron 8y agoA full JDK (with a VM, of course). Loom includes both changes to the VM and the core JDK libraries.
- avodonosov 8y agoIs there any roadmap for inclusion into release?
- pron 8y agoNo. OpenJDK projects are no longer planned to target a specific release.
- weberc2 8y agoOne thing I like about Go is that there is no dichotomy between sync and async. Every function call presents a sync interface, but under the hood all I/O is async. I really miss this in Python, where every library has an async twin, usually maintained by someone unafiliated with the sync library.
- kibwen 8y agoI think that being async-by-default makes sense for any new high-level language written these days, but the machinery required to make that happen is probably always going to be magic enough that there will be some space for low-level languages that will find value in giving the user choice between synchronous and asynchronous operations.
- k__ 8y agoSo we remove async/await from our async functions and add sync/block to our sync functions? :D
- pimeys 8y agoActually Tokio already has `blocking` implemented! https://docs.rs/tokio-threadpool/0.1/tokio_threadpool/fn.blocking.html https://docs.rs/tokio-threadpool/0.1/tokio_threadpool/fn.blo...
- infogulch 8y agoI think it should be possible to design an async system that is generically async, i.e. whether a given function with no special annotations or syntax is async (including all of the async-able calls that function makes transitively) is entirely determined by the top-level caller at compile time. The called function doesn't need any special syntax, and in fact doesn't even need to consider being async at all: the async-capability bubbles up based on what other async-able functions it calls. Only the very ends of the chain: at the bottom doing IO via system calls, or at the very top where request handlers are first dispatched (for example) should need to think about being asynchronous or synchronous at all; everything in-between is compiled for whatever the top wants.
- gpderetta 8y agoTo allow for separate compilation you need to compile each function twice though: a CPS version and 'classic' one. This can be wasteful. You also need annotations in the object file to describe the frame size of each CPS function. At some point this was seriously considered for C++ and I'm sure some language implementations actually do it. Or you go with cactus stacks which have their own set of issues.
- infogulch 8y ago> you need to compile each function twice ... This can be wasteful. More wasteful than writing each function twice? See C# for endless examples of libraries that have manual duplicate X() and XAsync() for every single method. If you compare the two they're almost always have the exact same body, except the async version has "async" and "await" peppered in and calls duplicate XAsync methods (which are implemented the same, except they call XAsync methods... you might be seeing a pattern here). If I'm not mistaken most rust doesn't use separate compilation and is built from source. In this case the compiler can treat it just like normal generics and just not compile it until it's used. And even with separate compilation for, e.g. a library, a very basic LTO would trivially trim out the unused methods when compiled into a final binary. And if you don't want 2x library itself, even then you can just use an escape hatch to turn it of on the crate/impl/function level and it's still better for you than the status-quo where you have to manually add a bunch of tokens and junk that the compiler already knows how to do precisely. If anything is wasted it's human effort because we're wasting our time doing something the computer can do better and faster. > "annotations in the object file to describe the frame size" Wouldn't you need this anyway for anything async, even if it's manual? This seems like an argument against async in general, not automatic async.
- nercury 8y agoI want to reflect on that this is still done without any garbage collector.
- UncleEntity 8y agoWhy would anyone assume that a GC is needed for async/await? The Future object seems to do the all bookkeeping needed by the borrow checker (which I believe is what rust uses to track the lifetime of objects).
- Matthias247 8y agoMaybe because most future implementations (e.g. in Javascript, the proposed C++ solution, Seastar, etc) work by scheduling a continuation on an event loop. Which obviously requires at least some form of dynamic memory for queuing up these continuations.
- gpderetta 8y agoI think seastar futures are allocation free. Standard c++ futures are not really a paragon of efficiency or good design. Still no GC though.
- Matthias247 8y agoI haven't read the code, but there are 2 areas where I expect allocations: - The continuation which is passed to .then, and which is typically a closure, must be type-erased, which requires an allocation. Storing the continuation in a std::function would allocate too. A short glance at https://github.com/scylladb/seastar/blob/master/core/future.hh https://github.com/scylladb/seastar/blob/master/core/future.... also confirms that there is a make_unique there. - Since continuations are most likely not called inline on completion but deferred into the next eventloop iteration there needs to be a dynamically sized queue to hold the ready continuations. I am not 100% sure if that's the case for seastar too, but I would guess so.
- AndyKelley 8y agoThis is exciting. It allows programmers to model their problems in code that fully accounts for the parallelism. For comparison, here is async/await in Zig: https://ziglang.org/documentation/master/#Coroutines https://ziglang.org/documentation/master/#Coroutines Zig decided to go the other way - when you async call a function, it does eagerly evaluate until the first suspend point. This is less overhead than immediately suspending, plus it removes the dependency of the language feature on a userland event loop. Users who want the immediate suspend feature can call a userland utility method of the event loop which suspends and then tail calls the async function in question.
- dom96 8y agoIt's exciting to see so many systems programming languages implement async/await. For a further comparison, Nim works in the same way as Zig when it comes to eager evaluation. Something that I'm particularly proud of when it comes to Nim's async/await implementation is that everything, right down to the macro which defines what `await` means, is implemented in the standard library. The compiler only implements the coroutines. This means that the language isn't bloated by this extra feature and makes it much easier for developers to implement their own async/await. I believe there was talk to do the same in Rust, but for some reason the developers decided to implement it in the compiler instead.
- steveklabnik 8y agoAsync blocks require a keyword to be unambiguous with a struct constructor.
- aidanhs 8y agoCan you clarify what youre referring to with "decided to implement it in the compiler instead", be it from this blog post or elsewhere? Clearly you need coroutines in the compiler, and the way Rust treats ownership means pinning is needed in some way (I've not been following closely so don't know if this is in libstd or a language-level feature), but the rest (according to this post) seems to be going in libstd so you can write your own if you want.
- kbumsik 8y agoAs an embedded system dev, I'm wondering if these async features can be implemented on bare-metal (or without runtime)? Maybe I'm dumb and it might be a wild thought but it would be great if I could easily integrate async language features with hardware interrupts.
- alexnewman 8y agoPretty sure you need at least an operating system and memory allocation
- steveklabnik 8y agoFutures have landed in libcore, and do not inherently allocate. You can write an executor with a fixed-size queue as well, and not require dynamic allocation at all. Being able to do this is a hard constraint on the design.
- Q6T46nT668w6i3m 8y agoIf I’m reading the proposal correctly, it’ll be possible and even straightforward because the standard library only provides the interface. You’ll be able to swap implementions by replacing the package with whatever fulfills the interface requirements.
- gpderetta 8y agoI'm not familiar with the way rust is going, but in general Async can be implemented purely a compile time by turning an async function into a set of functions tail calling each other or a switch statement. The stack frame need to go somewhere, but with allocator support this does not need to be exclusively handled by the runtime.
- nine_k 8y agoThis is likely doable if no I/O is involved in the scheduling of coroutines. Usually it is.
- 8y ago
- he0001 8y agoWhy does it have to be either one? Why not the possibility of choosing the implementation you want to solve the problem? A language where you could easily run whatever you want would have my vote. Not having it shoved down my throat (I’m looking at you JS). While it’s possible to build most of it yourself you should have the possibility to choose.
- sametmax 8y agoBe careful with this. I'm not saying you should not do it, but consider you'll have to design it very carefully. In python you can chose what event loop to hook to async/await, and hence we have gevent, qt, uvloop, twisted, asyncio, tornado, trio and curio as competing implementations. They are very difficult to mix, and their ecosystems are mostly isolated, dividing the man power to add features, fix bugs, provide support, create libs or frameworks and write docs or tutorials. Another problem is that you have (except for gevent which causes other problems by monkey patching the stdlib) to setup the event loop explicitly. Those mechanisms are complicated, easy to get wrong, confuse beginners, make docs introduction long and annoying or misleading before getting to anything interesting. E.G: to use asyncio, you are exposed to an event loop, an event loop policy, awaitable, coroutines, coroutine functions, futures, tasks and task factories. But there is worse... You can setup any event loop any way and time you want, and because the api is public, another lib can come and swap it. No lib to my knowledge provide any form of locking. This leads to some weird situation where libs are considered so low level you end up writing wrappers on top of it (e.g: https://github.com/Tygs/ayo https://github.com/Tygs/ayo) just to be able to start using it sanely. Now compare with JS. I'm really not a fan of the language. However, even when you can't use async/await, using a promise is straightforward. You don't have to bother about creating the loop, starting it, stopping it, cleaning after it has stopped. You don't have to wonder if somebody is going to swap the loop. You don't have to get a reference to a loop to schedule anything. Actually you can mostly ignore the loop and just code the solution to your problem. Now this makes JS dependent on one loop implementation for each runtime. Also you can't code any new async feature in JS, only use the existing ones. It's probably not what you want for a language like rust. However, you should learn from the python ecosystem fragmentation and overly exposed low level API to avoid the same mistakes. Somebody that just wants to use async/await should not have to learn how the implementation works in details, nor take so much precaution to avoid implementation lock in or break somebody else work. And you really want a federated ecosystem. Having 7 incompatible websocket lib sucks.
- Q6T46nT668w6i3m 8y agoI just finished reading the proposal and I’m super impressed and even excited. I really appreciate the commitment to not leaking implementation details into the standard library (i.e. exposing only the interface). This is fantastic for both embedded and (operating) system applications. Nice work everyone.
- kibwen 8y agoNote that this post is from April, and a lot of experimentation and reams of discussion and debate have taken place regarding the details of the two RFCs in question (along with other related RFCs such as the one for the Pin trait). In fact, one of the RFCs linked at the top of the OP has been closed and superceded by a newer one: https://github.com/rust-lang/rfcs/pull/2418 https://github.com/rust-lang/rfcs/pull/2418 . I've actually heard that tomorrow (Monday) the futures and networking working groups will begin a series of regular blog posts on design and implementation leading up to the release of Futures 0.3 (which IIRC intends to be more-or-less the final design for Futures 1.0). As for this article, I think the broad implementation details that it talks about are largely still accurate, just don't take it as gospel. :)
- zenjester 8y agoblog in hugo wish rust had a static web generator
- zenjester 8y agokool But the blog post is in Hugo :-)
- api 8y agoHow about lightweight threads, or are they there already? Going to Go with its LWT goroutines was awesome. Would never want to go back to async, which is really just a manual way of implementing LWT.
- steveklabnik 8y agoYou can sort of think of tasks as green threads. It just really depends on exactly how you define your terms; this area has a ton of similar sounding terms that different people define differently. The downside of Go’s approach is the overhead of calling into C; Rust can’t afford this. Go can. IMHO both languages are making the correct choices with regards to their constraints.
- gpderetta 8y agoI'm somewhat disappointed that rust is going with async style stackless continuations. I'm a huge fan of stackfull coroutines/continuations as they are much more elegant and flexible. The downside is that they need a full stack, but I strongly believe (but can't prove) that rust has enough annotations and lifetime capabilities that it should be able to guarantee single frame allocation (or even no allocation for fully scoped coroutines, like many generator use cases) in every situation that the async model would. There is an ongoing discussion in the C++ world between traditional C# style async, a more extreme non-type erased version (similar to the rust implemation I think but unsafe) and stackfull coroutines. Some (like me) hope that an hybrid solution might be possible.
- shawn 8y agoUndelimited continuations are memory sieves. Delimited continuations are better but a bit less useful.
- gpderetta 8y agoGenuine question: aren't all one shot continuations (like the ones this thread is about) naturally delimited? Is there even such a thing as an undelimited one shot continuation?
- deleted 8y ago[deleted]
- catern 8y agoSure there is: take an undelimited continuation API, and restrict the resulting continuations to be called only once. Most use cases still work just fine, and I think it would be strange to call that anything other than "undelimited one-shot continuations". Maybe what you have in mind, though, is the traditional argument, that there's no such thing as undelimited continuations. All continuations are naturally "delimited" by the boundary of the language runtime, operating system, etc, even with undelimited, multi-shot continuations. One-shot continuations make this argument more natural, because they have an obvious stack-based implementation for both delimited and undelimited continuations, and stacks are clearly delimited.
- lapinot 8y agoAnd yet we somehow don't acknowledge the fact that this is just do-notation for some specific monad--yeah, that powerful abstraction that can't be expressed in Rust because we don't allow higher-order polymorphism. Don't get me wrong, i'm bitter because i feel like Rust really is almost in the right direction for the future of language design. Yet there is a long time before we get a language with a really precise type system (linear, "pure") and thus with highly efficient code generator (~rust), that at the same time has all the higher abstraction goodies, maybe even dependent types (cf quantitative type theory [1]). Of course they took that into account, it's even explicitly mentioned in the rfc, and it got turned down for being too experimental, but at some point you gotta make a bold step forward and force people to actually care about abstractions. Now that Rust is getting some maturity i worry that it gets too complex in too much direction just because it's not powerful enough to express the few common abstraction, of which several instances are being added as distinct concepts (async&try, const datakind, higher-order poly--especially for lifetimes, named impls, before-after memory state for references...). [1] https://bentnib.org/quantitative-type-theory.pdf https://bentnib.org/quantitative-type-theory.pdf
- steveklabnik 8y agoSupporting do notation or not isn’t some sort of position we’re taking as a language; it’s an open research problem if do notation can work in Rust. Until that’s solved at all, we’re just not sure it’s possible. (Previous version of this comment said "HKT" but that's not purely right; we have a path forward for something at least HKT-like, but do notation is the bigger question.)
- lapinot 8y agoYeah, sure, i understand that, hn comments just need to be a bit spicy (btw, any ref of papers or other stuff in that area? i didn't follow closely but didn't see too much moving, actually i don't even know what the big question marks are). Although i really believe it's not about if but how, so let's hope that when the time comes Rust will be able to take the tough changes necessary to unify the different branches that split off. Like what is the real road-blocker, since we're talking here about abstractions that are gonna be monomorphised (at most into lifetime-polymorphic function, which anyway get erased) and known statically, right? Is it only a problem of getting the typing rules consistent and robust?
- wruza 8y agoThis thread is so confusing to me. Not because of complex differences in a language theory, but on choices that developers do follow due to experience in ‘the past’, regarding light threads. I understand that rust has no stdlib, that go has, and that js just doesn’t have switchable stacks. But why does almost everyone inclined to async-await in general? The answers I got in other threads (not on this exact question, but along the lines) is that async-await makes runloop-tearing points explicit. But is it really so important? How does a regular js guy manage a state incapsulation in-between their futures’ callback invocations? All the code I see is then-then-catch and it doesn’t account for, well, asynchronous effects like races; it just occurs naturally by not modifying other chains’ states. Why not just go with seamless coroutines then? Why making functions explicitly async? (Or sync, if that matters.) Maybe I’m misunderstanding something, but since people here are so fluent in concurrent execution, can someone point me to an in-depth explanation why are light-threads, coroutines, current-continuations, etc. so opposed by async keyword (and futures in general) today? I have some experience with low-level runlooping via coroutines in luajit and understand it to the point to be able to create asynchronous system, consisting of mix of os threads and coroutines (and it worked smoothly until our project was closed due to company’s external issues). I can say, I never felt the need of something different, neither met the ‘complexity’ of everything-can-yield rule. And the possibilities that open, i.e. scalability of simple code around io and cpu cores is just outstanding. I am genuinely curiuos what’s so great (or different) in futures, which seem to me, for now, just poor man’s light threads implemented via lexical closure overhead along with syntactic snow, running on a single-core cpu. This topic seems to be so narrow that modern google is too shallow to answer that. I believe there should be a LtU or similar thread that discusses it in classic depth. Thanks in advance!
- dpc_pw 8y agoNote sure if I fully grasped your quesiton but here it goes. Light threads and stackfull coroutines require stack allocation. That is their cost and it is unbearable for system-level language priding itself in zero-cost abstractions. Eg. AFAIK, it's the main reason that is making Go calling C code slow. Also, (again AFAIK) Rust stackless coroutines and futures compile down to state machines, so I don't understand the "lexical closure overhead". There's nothing forcing futures to be "running on a single-core cpu" in Rust, since Rust can reason about thread-safety. Personally, after couple of months of using JS at dayjob, I very dislike JS (as I thought I would), but I love coroutines/yield. I think they will be glorious in Rust: they will allow writting reasonably nice code with an amazing performance.
- EGreg 8y agoI know this may sound silly, but can someone make a quick rundown of the pros (and maybe cons) of Rust as compared to NodeJS, Go and Erlang? Why would people use it as opposed to these far more mature ecosystems, especially if it’s hard to master based on the comments I have seen from Rust users? (Not trying to be biased, actually want to ask people who do choose it.)
- grzm 8y agoYou might find this article useful: https://thenewstack.io/safer-future-rust/ https://thenewstack.io/safer-future-rust/
- Retra 8y agoHard to master doesn't mean it isn't worthwhile to master. And the only reason the Rust ecosystem is not more mature is because not enough effort has been put into it. By learning the language and working in it, both of the problems you point out will solve themselves. This is a classic "being traffic" comparison. If you're in a traffic jam, you are as much the cause of it as anyone else is. Likewise, if the ecosystem is immature, you are as much the reason for that as anyone else who doesn't participate in it.
- EGreg 8y agoThe same could be said about any charity or relief effort, but typically people join the ones that are already easy to join.
- Retra 8y agoI wasn't meaning to imply any obligation that you should be using Rust or anything. My point was only that the reasons people do things are already very familiar to you, so it doesn't do much to be incredulous of their motivations. I don't watch baseball, but I understand why people do it even if I can see no real value in it. People are motivated by a rational advantage far less often than by things like accessibility, familiarity, habit, curiosity, popularity, culture, counter-culture, identity, or need -- all of which are reasons that people use programming languages not present in your list. Perhaps this is just obvious. But I do frequently see people pretending to be objectively objecting -- complaining about traffic that they are themselves the cause of as if they didn't already know the answer to their question.
- Const-me 8y agoI think the hard part here is not language design, it’s implementation of the runtime. Linux is especially problematic, asynchronous IO has arrived late, years later than IOCP and kqueue. It took multiple kernel versions to make it usable, and still the APIs are questionable, e.g. files and sockets use different ones. Even MS failed to do it right, see a bug I found: https://github.com/dotnet/corefx/issues/25066 https://github.com/dotnet/corefx/issues/25066