5 ms·
> working with async Rust is not as simple as writing regular Rust code Working with async Rust is very simple if you are not writing futures yourself. Tokio a
by ibraheemdev 6y ago
> working with async Rust is not as simple as writing regular Rust code
Working with async Rust is very simple if you are not writing futures yourself. Tokio and async-std provide api's that mirror std except for the fact that you have to add `.await` to the end of the operation and add the `async` keyword to your function. With proc-macros, `async main` is just a matter of adding a small annotation to the top of the function. Async functions in traits are blocked due to compiler limitations, but `async-trait` makes it easy. What part of async Rust is more complicated than synchronous Rust?
> and it just doesn't provide you the same features as Elixir Processes do.
Can you explain this? How is tokio's `task` different then elixir processes? From the tokio docs:
> Another name for this general pattern is green threads. If you are familiar with Go's goroutines, Kotlin's coroutines, or Erlang's processes, you can think of Tokio's tasks as something similar.
- romanoderoma 6y agoErlang processes are not green threads. Green threads can share memory while Erlang processes cannot, they are strictly forbidden to do it. Also Erlang scheduler is highly optimized to work as a soft real-time platform, so they never run for infinite amount of time, they never block and never (at least that's the goal) bring down the entire application, the worst thing that can happen is that everything slows down but it's still functional and responsive. I don't know about Tokio.
- ibraheemdev 6y ago> Erlang processes are not green threads. Green threads can share memory while Erlang processes cannot, they are strictly forbidden to do it. So message passing is the only way to communicate between proccesses? I guess that makes sense with elixir being a fp language. This was not clear in the article: > Lunatic takes the same approach as Go, Erlang and the earlier implementation of Rust based on green threads.
- dnautics 6y ago> So message passing is the only way to communicate between proccesses? There are escape hatches. Obviously, you can do something really heavy like open a network port or open a file descriptor, but it also provides you with use ets, which is basically "really fast redis for intra-vm stuff" and you can transact over shared memory if you drop down to FFI.
- svrtknst 6y agoBasically only message passing. As another poited out, you can use FFI calls and Erlang Term Storage, possibly some other means to communicate, but the central feature is that each process has an ID, and then you send() a message to it. each process also has a receive() block where you essentially listen to everything that ends up in your mailbox and pattern match on it to take action.
- sa1 6y agoIn general thinking about concurrent code in terms of threads is easier than thinking in terms of async code(it's a lower level abstraction). > Can you explain this? How is tokio's task different then elixir processes? Tokio's tasks and go's goroutines and kotlin's coroutines are cooperatively scheduled, i.e a infinite loop can block other tasks from running. Erlang and lunatic have pre-emptive schedulers(similar to a OS scheduler) that schedule processes fairly by giving time slices to threads.
- ibraheemdev 6y agoI'm not familiar with erlang/elixir so I assumed that processes were similar to goroutines: > In other languages they are also known as green threads and goroutines, but I will call them processes to stay close to Elixir's naming convention.
- dnautics 6y agothey're similar, but the developer ergonomics around processes are way better. It's difficult to mess up, coding in the BEAM feels like bowling with those rubber bumpers in the gutter lanes, especially around very difficult concepts like concurrency and failure domains. Go makes it easy to mess up because the abstractions are superficially simple, but it's a pretty thin layer that you punch through.
- jolux 6y agoThe BEAM scheduler is not quite preemptive as I understand it, but in practice it gets close because instead of yield points being defined manually with “await” they are inserted automatically when you return from a function or call a function or do some number of other fundamental operations, and the pure functional nature of the language means that functions are short and consist almost solely of other function calls.
- sa1 6y agoThat is how haskell's scheduler works, but I was not aware that it was the same with BEAM. Makes sense.
- masklinn 6y ago> Working with async Rust is very simple if you are not writing futures yourself. > Tokio and async-std provide api's that mirror std except for the fact that you have to add `.await` to the end of the operation and add the `async` keyword to your function. There are absolute pain in the ass problems with `async` currently, in large part due to async closures not being a thing, which means it's very hard to create anything other than an `FnOnce` unless you desugar the future entirely.
- ibraheemdev 6y agoAsync `Fn*` traits are possible on nightly with the `unboxed_closures` and `async_closure` features.
- masklinn 6y agoYes, by "not being a thing" I meant "not being stable", I thought I'd edited before posting but apparently I only thought of doing so, sorry 'bout that.
- estebank 6y agoI don't consider things enabled by nightly features to be "possible" today, only "potentially possible at some time in the future". The days of using nightly because of a single needed feature in a popular crate are (in my eyes) gone.
- _flux 6y agoAnd what do you do with functions from external crates that take callback functions but are not async themselves? You are now limited to non-async functions and if the operation of the crate depends on the return values of those functions, you will need some extreme measures to make it work.
- akiselev 6y ago> What part of async Rust is more complicated than synchronous Rust? The Rust part, of course :) Seriously though, the compiler error messages alone make it a major pain - although I can't figure out if it's an issue of maturity (language and ecosystem), a fundamental tradeoff with Rust's borrow checker, or me just getting way ahead of myself. I can rarely go a few days of async programming in Rust before running into some esoteric type or borrow checker issue that takes hours to solve because I borrowed some non Send/Sync value across an await point and the compiler decides to dump a hot mess in my lap with multi-page long type signatures (R2D2/Diesel, looking at you).
- estebank 6y agoThose subpar diagnostics are a bug. The underlying reasons are that - The async/await feature is an MVP - async/await desugars to code you can write yourself that leverages a bunch of relatively advanced features, namely trait bounds and associated types - It also leverages a nightly only feature (generators) - Async programming is inherently harder :) - We've had less time to see how people misuse the features to see where they land in order to clean up those cases and make the errors as user friendly as they can be Put all of those together and the experience of writing async code in Rust is similar to the experience of writing regular Rust code maybe two or three years ago. When you encounter things like this, please file a ticket at https://github.com/rust-lang/rust/issues https://github.com/rust-lang/rust/issues so that we can fix them.