|
| ntoskrnl wrote:
| I write a decent amount of Rust and I find it productive, but I
| can see how it might be easy to get nerd-sniped trying to get rid
| of every last allocation. There's no shame in a Box/Arc. Remember
| that almost every language puts almost everything on the heap.
| Just allocate. It'll be fine. Really.
|
| I set a rule for myself that I'll spend up to one minute trying
| to save an allocation. Beyond that it's not worth getting
| sidetracked.
| marcosdumay wrote:
| > I set a rule for myself that I'll spend up to one minute
| trying to save an allocation. Beyond that it's not worth
| getting sidetracked.
|
| My rule is that I'll do what is the easiest, unless it's an
| inner loop that runs all the time, where I'll try to make it
| fast.
|
| In rust, usually the easiest is to not allocate or copy data.
| When it's easier to allocate or copy, I don't do a cost-benefit
| analysis at all, I just do it.
| [deleted]
| bombela wrote:
| I am like a moth irresistibly attracted by the far away light
| of zero allocation. I can almost reach it. Just one more little
| lifetime annotation. One more...
| whatshisface wrote:
| You can return structs to an allocation pool ring buffer by
| writing a custom implementation of the Drop trait. If you do
| that, almost anything can be zero-allocation. In Zig you can
| control the allocator as a first-class citizen and, I think,
| are _meant_ to do things like that.
| lumost wrote:
| so something I wonder, in a typical language you can easily
| pass by reference between components and threads. Avoiding any
| allocations.
|
| In rust the unspoken rule seems to be to allocate, allocate,
| allocate - unless you are writing a special purpose library
| etc. Which makes me wonder, is rust actually faster than a GC'd
| language when doing heavy async work?
| marcosdumay wrote:
| > in a typical language you can easily pass by reference
| between ... threads
|
| Yeah. And that is almost always an error on those languages
| too.
| [deleted]
| pclmulqdq wrote:
| It's often not. Many high- performance libraries use fine-
| grained locking within logical units (eg locking buckets
| within a hash table in the fast path rather than locking
| the table), which almost necessitates sharing references.
| marcosdumay wrote:
| So, you keep the hash table read-only on a static
| context, and only mutates some internal references?
|
| In rust you will have to declare it exactly like that.
| That's not really an example of passing mutable
| references between threads.
| [deleted]
| bluejekyll wrote:
| This isn't quite accurate. It's generally really easy in Rust
| to pass things by reference, and even inner async fns, this
| is easy. The issue with async and Futures, is that sometimes
| you need to capture the future and then pass that to
| something else to execute. In that context, shared references
| are hard, and just clone, or arc box like mentioned.
| coder543 wrote:
| I think a lot of Rust users would argue they don't even care
| much about performance. They just enjoy all of the
| correctness guarantees the compiler can enforce, as well as
| how ergonomic the language can feel. Being able to deploy a
| single static binary, and having an easy to use build tool
| and package manager are significant bonuses as well. Rust
| makes really hard problems easier when you know the compiler
| has your back.
|
| Rust gives you the tools to write very high performance code,
| but it doesn't have to be about that.
|
| I've had to point out to people quite a few times that
| garbage collectors can _improve_ performance... especially
| compared to naive implementations of manual memory
| management. GCs are not just a tool for lazy programmers. GCs
| can make allocation incredibly fast, and you get to defer
| cleanup work to another thread(s), which means less work in
| the critical path. Removing work from the critical path is
| how you make software faster. Every tool has tradeoffs, and
| GCs are a tool. GCs often use more memory as a tradeoff.
|
| I like Rust well enough, but I do wish we had a language that
| combined the ergonomics of Rust with the dead simple
| concurrency model of Go/Erlang. I haven't tried it, but
| Luantic looks promising: https://github.com/lunatic-
| solutions/lunatic
|
| As it is, we're fortunate to have quite a few great languages
| and platforms these days.
| ptato wrote:
| > I think a lot of Rust users would argue they don't even
| care much about performance. They just enjoy all of the
| correctness guarantees the compiler can enforce
|
| Aren't these correctness guarantees only for performance
| related factors though? (allocation/memory and
| concurrency). The rest of your program would be just as
| correct in any other language.
| ssokolow wrote:
| Not really.
|
| For me, the number-one Rust feature I love is that, by
| baking monadic optionality and error return in from the
| beginning (Option and Result), I can trust that,
| unless an author abuses panic! (in which case I never
| trust their code again), I can see all a function's
| return paths in its type signature. (Without having to
| choke down a pure functional language like Haskell with
| that currying-based function call syntax that I can never
| get used to.)
|
| The runners-up are how fast Rust starts compared to
| Python or Java or similar when writing a CLI tool and how
| nice PyO3 makes safely writing libraries or backends for
| tools that need to be in Python for some reason like
| "nothing but PyQt, PySide and possibly QtJambi offers
| memory-safe QWidget bindings... and I've never found a
| Java app that didn't feel laggy and sluggish on X11".
|
| See also https://cliffle.com/blog/rust-typestate/
| coder543 wrote:
| No. A ton of languages don't support proper Sum Types,
| and Rust's emphasis on errors-as-values helps you think
| about error handling, instead of only thinking about the
| happy path. Rust also doesn't do implicit type coercion
| and a host of other things that can cause correctness
| issues. Rust gives you the tools to express more of what
| you're doing to the compiler than a lot of languages,
| which lets the compiler help you more.
|
| It's natural that a lot of programs have some form of
| concurrency, so that's an extremely common thing for Rust
| to help with, but it's not the only thing.
| lumost wrote:
| interesting! The main reasons I like rust are
|
| - Can compile to anything, you can use one language to
| write Cuda Kernels, backend services, distributed
| processing jobs, WASM apps, and native apps.
|
| - Performance, comparable to better than C.
|
| I like fast languages as it avoids headaches that come from
| slow languages. If rust wasn't fast, I'd probably just
| stick to a polyglot language portfolio and pass it by.
|
| EDIT: I also just clicked through to lunatic, that does
| look very promising!
| drogus wrote:
| You should also check out Gleam! https://gleam.run/
|
| It's almost like Rust and Elixir had a baby :D
|
| As for the rest of the comment: totally. Almost none of the
| code I write in Rust needs C-like performance and I still
| choose Rust for it.
| lijogdfljk wrote:
| Fwiw i rarely allocate around these "issues" and i use all
| async. I think your comment could be tweaked to say:
|
| > In rust the unspoken rule seems to be to allocate,
| allocate, allocate _when you run into a lifetime issue_
|
| Lifetimes work fine with async, but _some_ types of lifetimes
| can be problematic, for sure.
| drogus wrote:
| > in a typical language you can easily pass by reference
| between components and threads.
|
| and that's when you usually get data races ;)
|
| > In rust the unspoken rule seems to be to allocate,
| allocate, allocate - unless you are writing a special purpose
| library etc. Which makes me wonder, is rust actually faster
| than a GC'd language when doing heavy async work?
|
| I think it's a bit more nuanced. `Arc` is still a "smart
| pointer", so while it's not a straight Rust reference, it
| acts as a pointer. Yes, it allocates and it needs to do a
| reference count, but the overhead is very small. So while in
| practice it is not "zero cost", it's usually negligible.
| lijogdfljk wrote:
| Yea, i definitely agree with this author more than the last.
| Also, as both a writer of apps and libraries, i agree libraries
| pose more opportunity to drag yourself deeply into generic
| relationships and hyper optimizations.
|
| Strangely i haven't had many of the issues that the previous
| poster was discussing, though. My issues are usually trying to
| work around the lack of GATs, lack of trait aliasing, etc. But
| i use `async_trait` so maybe i'm sidestepping many of the
| issues from the previous post. /shrug
| JoshTriplett wrote:
| Absolutely agreed. You can get so caught up in trying to make
| it perfect that you don't ship something.
|
| https://raw.githubusercontent.com/luser/keep-calm-and-call-c...
| whatshisface wrote:
| My rules of thumb for Rust, that for the most part keep me out
| of allocation puzzles, are:
|
| - Never combine two things that can be valid for different
| amounts of time into one struct. For example: A file struct
| should not contain both information about how it is formatted,
| which is true forever and can be used for many files, and a
| file handle, which could be invalidated by the OS at any time.
| Breaking this rule will fill your code with Box/Arc as you try
| to imitate classical OOP.
|
| - Don't be afraid to frequently pass contextual information to
| functions; you don't need to put everything that will remain
| the same between two calls into `self`. For example, every
| function that works with the file can take the file handle and
| the format information as separate arguments. Trying to DRY
| function arguments by combining data with different lifetimes
| into a single struct and then hiding that argument in the
| `self` parameter might feel like simplification, but in Rust it
| triggers the above problem.
|
| - Functions that call functions that take mutable references as
| output locations should do the same, unless they have to
| allocate for another reason. This rule of thumb will tend to
| push allocation as far outside of loops as possible.
|
| With these in hand, I almost never need to box or reference
| count anything. If you fail to heed the fact that Rust is not
| really an OOP language, your entire program will start to look
| like the hairy parts of C libraries that interface with the
| Python interpreter.
| ssokolow wrote:
| > If you fail to heed the fact that Rust is not really an OOP
| language, your entire program will start to look like the
| hairy parts of C libraries that interface with the Python
| interpreter.
|
| Any tips for those of us who see PyO3 as one of Rust's
| biggest killer apps?
|
| (Honest question. I hate how unmaintainable Python is but I
| don't know of any better equivalent to PyQt/PySide's memory-
| safe QWidget bindings or the RAD-friendly ORM migrations in
| Django ORM or SQLAlchemy+Alembic.)
| whatshisface wrote:
| I imagine it would go like writing a good C library for
| Python, where the PyResult<> wrappers disappear as you move
| deeper into your code and away from the interface.
| Hopefully working with Python objects on the outside won't
| require using references everywhere on the inside.
| ssokolow wrote:
| Ahh, so more or less what I'm doing. Design the Rust code
| as if it's going to be a generic C library with bindings
| for multiple languages and then write a PyO3 binding
| layer that just clones however much is necessary to
| convert until it's proven that more optimization is
| needed.
| mcronce wrote:
| > I can see how it might be easy to get nerd-sniped trying to
| get rid of every last allocation
|
| Honestly, this is a really good way to put it, and that one
| minute rule sounds like a pretty good rule (ignoring cases
| where performance requirements led to profiling, which pointed
| you at some specific piece that you need to optimize,
| obviously)
| substation13 wrote:
| > Remember that almost every language puts almost everything on
| the heap
|
| True, but then those languages are heavily optimized for that
| scenario. This is why Rust written like Java often performs
| worse than Java (and optimized Rust).
| RazeLighter777 wrote:
| I'm currently writing an async rust application. I think the
| biggest thing to avoid coding yourself into a corner with async
| rust is to prefer transferring ownership over using references
| when possible. Channels are a great way of doing this, and
| likewise communicating with sync code
| dang wrote:
| Recent and related:
|
| _Rust Is Hard, Or: The Misery of Mainstream Programming_ -
| https://news.ycombinator.com/item?id=31601040 - June 2022 (655
| comments)
| crabbygrabby wrote:
| If you read the article this post actually links that article
| and explains that it was written to address it...
| olliej wrote:
| I've always felt the "if you use Arc" why not just use GC is a
| weak response. The benefit of rust is that you only pay the cost
| if you need it. I'm not saying "yay it's all easy" I found
| concurrency frustrating in rust because it refused to let me do
| things that I "knew" we're safe :)
|
| The always use Arc model is actually what swift and objc
| fundamentally do. Everything's lifetime has to be threadsafe, so
| the refcount itself must be threadsafe, and so any refchurn is
| atomic. For a single thread as I understand it modern CPUs handle
| uncontended churn without a real perf hit.
|
| But I was writing a raytracer in swift, and once I made it
| multithreaded the refcount cost on my _non mutating_ objects
| became a massive perf cost. It was super frustrating, and is
| fundamentally what would happen if you took the "Arc everything"
| approach. But you don't have to, and this get perf where it's
| safe and possible.
| ComputerGuru wrote:
| I can get behind the general recommendations outlined in this
| article (with the caveat that they're only to be used if you
| don't need every last drop of performance, you're not writing a
| library, and you find the current async situation difficult)
| except for the complete cop-out of implementing each handler
| routine as not just a function (which could at least be
| nested/local) but as a completely separate type implementing a
| trait.
|
| That's fine if all your transforms are strictly defined, often
| reused, and you're just choosing between them but if many of your
| transforms are just one-offs then that's an _insane_ amount of
| boilerplate and a very clunky approach. It 's also antithesis to
| the OP's claimed "just get things done" approach since you'll
| always be second-guessing whether something should be a separate
| transform type or if it should be extending an existing one, etc.
| wolfspaw wrote:
| Great post/points. Really enjoyed your rebuttal.
|
| Your version of the dispatcher really shows a simple and
| intuitive way to code without any explicit lifetimes or zero-
| alloc-shenanigans.
| pie_flavor wrote:
| The problem is essentially that nobody ever evaluates how good
| their code is, they evaluate how much better it could be.
|
| Code that has an Arc> around every single type in Rust
| could be made a lot better. Code that does the same in Java could
| not, because Java doesn't have the ability to make types not
| wrapped in Arc>. If you wrote perfect code in Java, it
| would be equivalent to Arc> code in Rust - which is not
| hard to write at all, presenting minimal annoyance. Lest I be
| lambasted for comparing to a JIT language, that's also how
| everyone writes C++ these days, and for the same reasons.
|
| But that's not how anyone looks at code. They see the ability to
| unbox types. They see the ability to borrow instead of moving.
| They see the ability to modify stuff in place instead of making
| it immutable and then deep-copying it. In most cases the code to
| do so is shorter and easier. In some other cases, it's longer and
| more difficult. And people exclusively look at the latter cases,
| the fact that it's difficult to further improve past what they
| previously thought of as perfection, and declare Rust to be
| Hard(tm), and go back to Java where they can attain perfect code,
| even if it's worse than the Rust equivalent.
|
| Like nerd-sniping, except you do it to yourself.
| jstimpfle wrote:
| > even if it's worse than the Rust equivalent.
|
| If the Java version does the same thing, but with less typing
| and unnecessary fluff, then it is indeed better.
| athrowaway3z wrote:
| No its not.
|
| The quality of a abstraction dictates how clear you are able
| to think about things.
|
| 'Less typing and unnecessary fluff' is the difference between
| writing '100' in Arabic numerals or writing 'C' in roman
| numerals.
| dgb23 wrote:
| If we're being pedantic then the Rust version is not an
| abstraction at all. It explicitly states how exactly the
| object is de-allocated and how it is accessed.
|
| In the Java version, which is definitely not just an
| Arc but something more opaque and powerful, you let
| the runtime handle and optimize these things for you,
| because you really don't want to know. It would only get in
| the way of the essence of your program, because your
| program is decidedly not about expressing these things.
| Well at least the Arc, the Mutex part is not necessarily
| much better.
|
| So it's not '100' vs 'C' but more like '100 apples' vs
| 'enough apples'. Both have their upsides and downsides. It
| certainly is a good thing that both expressions exist.
| Karrot_Kream wrote:
| Not everyone values this the same and this is where a lot
| of preference wars start in PL communities. Some people
| enjoy keeping the problem space small and having
| opinionated decisions at the cost of limited
| expressiveness. Others value expressiveness maximally. This
| [1] article goes into a lot of the tradeoffs involved with
| expressivity and cognitive load (with the background of how
| being a PL enthusiast tends to color one's thoughts).
|
| [1]: https://scattered-thoughts.net/writing/things-
| unlearned/
| felipellrocha wrote:
| It isn't, though. The point is that you *can* write it
| without thr Arc> and gain significant speed without
| it. Even if your first iteration started with it.
| Hirrolot wrote:
| I am the author of the original post. Unfortunately, before
| publishing anything, it's very hard to predict all possible
| misinterpretations of my text.
|
| > I really wish the author clearly pointed out that they write
| the article from a point of view of a library author trying to
| come up with generic and flexible APIs.
|
| Most commentators viewed the text from the perspective of
| application programming. You are more close to true: I am a
| library author and the dispatcher example was concerned with the
| problems of library maintainers. However, I wrote this post
| mainly to talk about _language design_.
|
| Rust is ill-suited for generic `async` programming, because when
| you enter `async`, you observe that many other language features
| suddenly break down: references, closures, type system, to name a
| few. From the perspective of language design, this manifests a
| failure to design an orthogonal language. I wanted to convey this
| observation in my post.
|
| Additionally, how we write libraries in a given language reveals
| its true potential, since libraries have to deal with the most
| generic code, and therefore require more expressive features from
| language designers. This also affects mundane application
| programmers though: the more elegant libraries you have, the more
| easily you can write your application code. Example: language's
| inexpressiveness doesn't allow you to have a generic runtime
| interface and change Tokio to something else in one line of code,
| as we do for loggers.
|
| One gentleman also outlined a more comprehensive list of the
| `async` failures in Rust [1]. This pretty much sums up all the
| bad things you have to deal with in generic `async` code.
|
| UPD: I added an explanation section [2] to my original post.
| Thank you for your feedback, this is very appreciated.
|
| [1]
| https://www.reddit.com/r/rust/comments/v3cktw/comment/ib0mp4...
|
| [2] https://hirrolot.github.io/posts/rust-is-hard-or-the-
| misery-...
| dgb23 wrote:
| My intuition is that async is fundamentally the wrong
| abstraction. First, you want to get rid of function coloring
| and make coordination itself explicit. Then you build an
| abstraction over that, where you can declare or infer whether
| an operation is commutative or associative and generate/select
| scheduler logic from that. Right?
| steveklabnik wrote:
| That is a valid way to design things, as long as it doesn't
| clash with other goals. It's not clear that this is possible
| given all of the other constraints involved that Rust is
| attempting to fit together. Doesn't mean it's impossible, but
| the possibility is an open question.
|
| This is partially because some of it is subjective! For
| example, the whole idea that "function coloring" is
| inherently bad is not a given in a language like Rust.
| Languages that want to reach down into the lower levels often
| make costs fairly explicit. Async and sync functions are
| significantly different, and so some may argue that in a Rust
| context, this is a good thing, not a bad one. It's the same
| idea with values vs references: in many languages, the
| difference is papered over, not shown to the end user. But in
| Rust, it is, and this does lead to some ceremony if you want
| to call a function that takes one with an argument that's the
| other. But nobody is arguing that Rust should totally remove
| this distinction (other than the fact that references are
| themselves values but that's not really relevant here...) due
| to some sort of two colored functions. However, some _do_
| want this for mutable vs immutable references.
|
| Tl;dr it's not that simple, in many contexts.
| drogus wrote:
| > From the perspective of language design, this manifests a
| failure to design an orthogonal language. I wanted to convey
| this observation in my post.
|
| I wouldn't say it's a "failure". It's an incremental design.
| This stuff is known to language maintainers and it's being
| worked on as far as I know. I understand your point of view,
| but I felt like it would be good to point out that it should be
| viewed only in a very specific context.
|
| > Additionally, how we write libraries in a given language
| reveals its true potential
|
| Yes and no. Rust has many flaws in this context and yet I think
| it's still one of the best languages out there. Can it be
| better? Sure, and I hope it will be. Is it good enough for most
| of its users? Yeah, I think so.
|
| > One gentleman also outlined a more comprehensive list of the
| `async` failures in Rust [1]. This pretty much sums up all the
| bad things you have to deal with in generic `async` code.
|
| I really hate this kind of comments. Saying that async was
| "rushed" is an insult to all of the people that put so much
| time and effort into releasing the future. It wasn't rushed, it
| took years to release it. All of these issues are well known
| and many people are working on improving the situation and
| comments like this are not only not constructive - they're
| actively harmful to the development of the language.
|
| To be clear: I don't mind listing things you find frustrating,
| it's fine. I just don't like doing it in this kind of
| unconstructive way that basically just burns out language
| maintainers.
|
| I really hope the issues listed there can be resolved in time,
| but if I had a choice between having async in its current form
| vs waiting for an ideal release in 10 years, I would vote for
| releasing it even sooner. Again, it's not ideal, it has lots of
| problems, but I wrote _very_ successful async web services and
| there are countless companies that did so too, so I 'd say it's
| good enough.
| crabbygrabby wrote:
| Yea the original post reads like "you cannot get rusts' async
| to do anything useful" and then there were hundreds of trolls
| jumping in "the language is unreadable" train. "oh yeah, no
| one can actually use rust". Uh okay... That's why I've been
| using it in production for two years ok...
|
| Async is hard, that said it really does work just fine.
| dmitriid wrote:
| > I wouldn't say it's a "failure". It's an incremental
| design.
|
| I'd say all this is why you don't usually try and bolt on
| async (and anything doing multithreading, async, parallel, or
| distribution) after the fact. It has to be in the language
| from the very beginning.
| ssokolow wrote:
| The bits of Rust that feel "bolted on" were known to Rust's
| developers prior to the v1.0 freeze. You can go back
| through the mailing lists and find talk about things like
| higher-kinded types and guaranteed tail call optimization
| and all sorts of other things.
|
| ...the problem is that doing this sort of stuff in an
| eagerly evaluated native-compiled imperative language with
| no GC and an emphasis on C interop is an area of active
| research.
|
| Rust is literally pushing the envelope with things like its
| take on async/await.
| jph wrote:
| Async does have to be hard, sometimes, at least right now.
| Iterators, closures, selects, and more are IMHO hard, or absent,
| or not intuitive. I know these are being worked on-- thank you to
| the language developers.
| drogus wrote:
| I think it depends on what you call hard. Things you listed
| usually make things unergonomic, not necessarily hard. You can
| still do a lot of stuff with async Rust, but it often requires
| a lot more boilerplate and compromises.
|
| Granted, it can be hard, but I wrote _a lot_ of async code,
| including async streams, traits, saving `Future`s for later
| execution etc and usually you don 't need anywhere near as much
| complexity as was presented in the first post.
| jmartin2683 wrote:
| Tokio is awesome and very easy to use imho
| tus666 wrote:
| > So I'll start with a note for all the people intimidated by the
| techniques the author is trying to use in the post: when writing
| Rust code you almost never use this kind of stuff
|
| Never write async code? Or expect a library will always cover
| every use case where async might be needed?
| daenz wrote:
| I learned Rust by writing a (fuse-based) filesystem [0]...about
| 30k lines iirc. Rust was challenging, but not crazy difficult (it
| helps that I have a C++ background). I loved it. Development was
| slower than a dynamic language, but faster than C++, and most
| importantly, I felt _safe._ It 's a really solid language.
|
| However, when I took a look at async Rust, it really did appear
| to be a mess. I have substantial experience with Python
| async/await (which is also a mess), so I'm not unfamiliar with
| the async concepts. Honestly, I think it's the idea of an event
| loop in a compiled + non-memory-managed language. You really have
| to think hard about where objects are living and for how long,
| and combine that with the illusory world of how async/await
| appears to work (versus how it actually works), it gets hard to
| conceptualize. Maybe I just didn't spend enough time with it to
| feel comfortable with it, but that's my hot take.
|
| Imo, Go does performant concurrency right. Rust would be smart to
| adopt what Go offers.
|
| 0. https://github.com/amoffat/supertag
| whatshisface wrote:
| Go has a runtime and a GC, and importantly can implement an
| event loop outside of anything reached by tracing execution
| from your code's entry point. Rust's philosophy lead them to
| make the runtime something you bring in with a library import
| and start manually. Bundling Tokio with every binary would not
| be their way, although something like it may one day wind up in
| the standard library.
| erikpukinskis wrote:
| Does Tokio have a concurrency model similar to Go's?
| oandrew wrote:
| No, since rust async is stackless. There is a stackful
| coroutine implementation for Rust:
| https://github.com/Xudong-Huang/may
| steveklabnik wrote:
| Rust cannot copy what Go does without compromising on various
| language design goals. What Go does is good, but what's good
| for Go isn't always what's good for Rust. That goes the other
| way too :)
|
| Rust did try to have something closer to Go before 1.0, but it
| led to so many issues it was removed. Those issues aren't a
| problem for Go.
| ssokolow wrote:
| Here are a couple of examples of the changes that were made
| in the two or three years before Rust 1.0 if anyone wants to
| read more:
|
| https://github.com/rust-
| lang/rfcs/blob/master/text/0230-remo...
|
| https://pcwalton.github.io/2013/06/02/removing-garbage-
| colle...
| Karrot_Kream wrote:
| My path to Rust async sanity was using lots of Rc. Though at
| times when I'm using lots of Rc, I question why I'm not just
| using a GC language.
|
| I really like Go as a language and you can approximate a lot of
| its development style by just using channels similarly in your
| own code. I frequently employ crossbeam channels to that
| effect.
| felipellrocha wrote:
| I was about to say. You could avoid _a ton_ of 'Rc's by just
| using channels instead.
| samwillis wrote:
| > Python async/await (which is also a mess)
|
| This! There seems to be a move to (somewhat) "async everything"
| in Python. But it just results in bad developer UX, it's
| verbose and unneeded 95% of the time.
|
| I wish Gevent had been adopted as the starting point for an
| official way to do "none threaded" parallelization.
| kosherhurricane wrote:
| Lack of 'async' in Go has been a great design choice (aka, an
| absolute blessing).
|
| People complain a lot about Go's missing features, and they did
| end up adding generics, but their conservative approach I think
| has been a net benefit for Go.
|
| Rejecting language feature is something that's hard to do, and
| it takes a lot of experience, and I dare say wisdom to stick to
| it.
| jchw wrote:
| Exactly. And worse, async in Rust is viral, because any code
| outside of async doing I/O is not good to use inside of async.
| And it gets worse: making everything async is not a good
| option. Effectively, it seems there is no viable answer: you
| need two of _everything_.
|
| Go does do concurrency well, but unfortunately it's only able
| to do that because of opinionated decisions that a language
| with Rust's design goals cannot really make. They also have
| tradeoffs that I don't think Rust developers would accept, like
| making calls into C functions slower. So I feel as though Rust
| async is at an impasse. It can be made much better, but it
| feels like the improvements will come at increasing complexity
| in the language, compiler and ecosystem. Meanwhile, the ideal
| end state seems like it will still have a lot of annoyances,
| such as the need to tirelessly duplicate anything that needs
| I/O for sync and async.
|
| I almost kind of wish Rust would just drop async, as the rest
| of Rust is much better, having only minor issues that are very
| much fixable IMO. Instead the ecosystem is accelerating into
| it, and now it's hard to avoid for some use cases.
|
| I hope it all works out, because Rust is good and I've been
| advocating for its adoption at work and continually trying to
| adopt it elsewhere. But the issues with async are very dire,
| imo. Async rust is cool, but it isn't what I would consider to
| be a similar level of robust, thoughtful design.
| daenz wrote:
| If Rust does decide to continue with async, they should
| really look into investing in improved docs and education
| around it. When I was looking at it a few years ago (maybe it
| has improved since then), I really did feel stupid for not
| getting the ideas/conventions/reasoning. If it was that off-
| putting to me, as someone with some experience, I can imagine
| it is very unapproachable to complete newcomers. It just felt
| like I was looking at something that shipped way too early.
| Karrot_Kream wrote:
| Async is new enough that the function and trait docs are
| getting better all the time. There was a time it was really
| ugly to use async, and I still find it a bit crufty but
| it's a lot more approachable for someone new to Rust.
| ollien wrote:
| The async chapter in Rust for Rustaceans was a great
| introduction. It leaves something to be desired because you
| walk away with it without knowing how to run any async code
| (it doesn't mention any runtimes, for instance), but it
| explains the concepts really well.
| berkut wrote:
| The viral aspect is the thing which _really_ annoys me as
| well about it: I have some apps that make one or two single
| requests to DBs in certain configurations, and I 've had to
| end up using async for those parts (up to main() obviously,
| although not all code in the apps needs to be aware of async
| thankfully) due to many crates needing/using async now for
| this (which may well be good/fine for other peoples' heavy
| usage, but not really for my usage).
|
| However, it means that even when these requests are not being
| done (and might never be for the running of the apps, as it's
| user-configured what the apps do and whether they make DB
| requests occasionally), these apps end up having some (it may
| not be much, but it's somewhat noticeable) overheads, i.e.
| coreCount threads are always created by the async
| infrastructure (despite being a single-threaded app in one of
| the apps cases), call stacks are deeper even though async is
| not really being used (although due to main() being async
| effectively that changes everything), meaning more memory
| usage for those threads's stacks (which is somewhat ironic,
| as that's one of the points of utilising async for heavy IO
| requests - reducing memory usage! - but in reverse it hurts
| my use cases a tiny bit).
|
| Edit: I tried to use things like pollster (in an attempt to
| significantly isolate and limit the async usage to just where
| it was needed), and it wouldn't work for my use cases.
|
| I'm on the verge of splitting the apps into two parts due to
| this, but due to the shared state, that would involve
| additional complexity (RPC or something), which I don't
| really want to stomach.
| the_gipsy wrote:
| I worked on a rust project that had one thread with a
| httpserver all async, and another thread with some mqtt
| client all sync. Communicate over channel. No big deal.
| SuperFluffy wrote:
| You can spin up a single threaded runtime to perform these
| async calls without the need to "infect" the rest of the
| program.
|
| The strategy is to spin up a single threaded async runtime
| and to just perform that call and block on the runtime
| itself. The easiest way to do that is probably
| https://github.com/zesterer/pollster
|
| And there really isn't much in there so you didn't need to
| worry about performance or anything like that.
|
| I recently refactored a colleague's program from async to
| sync because it's essentially and entirely sequential.
|
| The reason it started its life as async was the reqwest
| library, which first and foremost provides async methods to
| perform http requests.
|
| However, tucked away behind a feature flag aptly named
| `blocking` there is a small API that wraps the async api
| and allows making sync/blocking calls in a non-async main.
| And there they employ the same strategy of having a thin
| async runtime that blocks on completion of the async call.
| ibraheemdev wrote:
| This works with runtime agnostic futures, but you can't
| do any I/O without requiring a specific runtime. reqwest
| for example doesn't use a generic block_on, it runs tokio
| behind the scenes.
| armchairhacker wrote:
| You can spawn a simple Tokio I/O runtime with
| Builder::new_current_thread().with_io().build().
|
| The catch is that it's not really "simple", and idk the
| performance penalty.
| Matthias247 wrote:
| A multithreaded runtime that is shared for all functions
| in the app - that other synchronous code then "blocks_on"
| - will also work. Or e.g. having a cached thread_local
| tokio runtime.
|
| Btw:
|
| > And there really isn't much in there so you didn't need
| to worry about performance or anything like that.
|
| Actually you have to! In case you write a program that
| spawns background threads (with whatever async runtime),
| and then let your foreground thread interact with that -
| it will have performance implications since your program
| now does additional context switches. It might or might
| not matter for your application, but in general it's
| rather easy to lose all perf benefits that async code
| actually might provide by still requiring switches
| between full threads.
| berkut wrote:
| I tried things like pollster, and they wouldn't work in
| my case for reasons I can't remember (but I asked for
| assistance on the Rust Discord and there didn't seem to
| be ways around it).
| miohtama wrote:
| > because any code outside of async doing I/O is not good to
| use inside of async
|
| This is (somewhat) similar in all programming languages and
| it is called coloured function problem:
|
| https://news.ycombinator.com/item?id=8984648
| athrowaway3z wrote:
| I agree to a large extend. However its not that viral if
| you're the app developer. Tokio ( and iirc most other
| executors ) have a block_on and a spawn_blocking to jump
| between 'worlds'.
|
| If all you're doing is something like "get 10 http request
| and concat them to stdout" then doing it with Rust async + a
| block_on call is pretty straight forward.
| ssokolow wrote:
| The problem is, Go uses a concurrency model (stackful
| coroutines/fibers) that was all the rage in the 90s (to the
| point where the Windows APIs have vestigial support for it) and
| then got abandoned by everyone except them for reasons that are
| still applicable.
|
| Here's a paper on the history and flaws that was written to
| argue against adding them to C++:
|
| "Fibers under the magnifying glass" by Gor Nishanov
|
| https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2018/p13...
|
| TL;DR: They don't play nicely with FFI and that's a big part of
| why Go is its own little semi-closed ecosystem... the polar
| opposite of Rust, where things like PyO3, Helix, Neon,
| cbindgen, etc. are a huge competitive advantage.
| qsdf38100 wrote:
| It's interesting that to criticize rust without being downvoted
| to death, one must first say how wonderful and superior to c++
| it is.
| linkdd wrote:
| You mean that acknowledging the strengths of a technology and
| adding constructive criticism is not downvoted while claiming
| that "it sucks" without any argumentation is?
|
| Who would have thought...
| qsdf38100 wrote:
| See?
| qsdf38100 wrote:
| I love it keep them coming!
| linkdd wrote:
| I'd like to have some clarification on the following claims:
| - Rust async is a mess - Python async is a mess
|
| What does "mess" means here?
|
| I have been writing Rust code for a few months, and Python code
| for more than a decade. I have to admit that I do not know the
| internals of the Python's async model, but as a "user", I think
| that trio (and asyncio with 3.10 / 3.11 is getting better) is
| really great.
|
| The ability to call `trio.run` / `asyncio.run` anywhere to
| start an async loop does not "contaminate" the async/await
| keywords up to the entrypoint. Which is great.
|
| In Python, calling an async function returns a coroutine object
| (that can be awaited). In Javascript it returns a Promise. In
| Rust it returns a Future.
|
| From the "user" point of view, it's all the same. And I don't
| know many developers who are interested in how it works under
| the hood, because (like in math/physics/science in general)
| it's very useful to sit on the shoulders of others.
|
| If I understand correctly, Rust gives you the tools, and the
| "shoulders" belong to the library developers (like tokio).
| Which is fine to me.
|
| So, I really do not understand what "mess" means, what it
| refers to, and what I (as a "user") can do about it. Also, if
| the internals change, how will this impact my code?
| the__alchemist wrote:
| Pet theory, as someone who falls into the "Loves Rust; avoids
| Async and generics" alluded to in the beginning of this article,
| the one it's replying to, and comments on the latter's thread
| here:
|
| Is the Async crew mostly writing web servers and other things
| that operate using TCP and HTTP? Lower level (eg IP, Eth, network
| hardware drivers) isn't well supported by rust libs. Nor is
| higher - we have Flask analogs, but no Django analogs.
|
| As Async ingresses in Embedded Rust, I seek answers to "is this
| worth the viral qualities, and API rift?".
|
| For the adjacent question re generics by default, I ask "Is the
| flexibility and type checking worth the API complexity, and
| documentation dead-ends?"
|
| Does anyone here use Async Rust in domains outside those sections
| of network and web programming?
|
| I've found rust to be a great fit as a cleaner, more explicit C
| alternative.
| xavxav wrote:
| > Is the Async crew mostly writing web servers and other things
| that operate using TCP and HTTP? Lower level (eg IP, Eth,
| network hardware drivers) isn't well supported by rust libs.
| Nor is higher - we have Flask analogs, but no Django analogs.
|
| Something I have never understood is _why_ people want to write
| web-apps in Rust. I love the language, but honestly, a managed
| language (like js /ruby/go) is always going to be better suited
| to the world of web-apps.
|
| I feel like this imposes an undue burden on Rust to do
| everything for everyone which has to break down _somewhere_
| gbear605 wrote:
| For me, I'm currently running a Rust webapp and a Python
| webapp on a server; both are of similar complexities, and I'm
| probably a bit better at Python. I keep on having to fix the
| Python app, because of both OS upgrades and problems with the
| implementation. The Rust app has been working without any
| issues for the last three years.
|
| For a relatively simple web server, where I don't need to
| collaborate with anyone, I'm choosing Rust.
| sonthonax wrote:
| I've written quite a lot of rust GRPC web services. The
| reason being that we wrote a lot of propriety derivatives
| library code in Rust that we needed to Interface with. We
| could have wrapped this in python, but to be honest, it just
| wouldn't have been worth the effort.
|
| I ended up writing something that faced external clients in
| rust, that had to do traditional web app things, and it was
| okay. The pros were that you could write idiomatic interfaces
| to things like auth that wouldn't look out of place in a
| Django app. The cons however, was that writing these
| Interfaces took some non trivial rust knowledge, especially
| since we did a lot of async rust.
| spullara wrote:
| 100% agree. It drives me nuts when people use the wrong tool
| for the job.
| the__alchemist wrote:
| I'm suspicious this occurs when there are too few tools in
| a given box. See also: NodeJS.
| mcronce wrote:
| Better suited in what way? I've written HTTP APIs in all of
| the above - plus Python and PHP - and choose Rust for new
| ones 99% of the time.
|
| Just recently I had a need for a fairly simple webapp.
| Whipped up the back end in Rust in, like, an hour; only had
| to bolt on a couple tests for the single complex bit of
| business logic, because I have a high degree of confidence
| that the compiler has me covered for everything else.
|
| Now it sits there quietly using 4.9 MiB of physical memory
| and 1.5 millicores on one of my servers.
| steveklabnik wrote:
| The answer to this question is often one of a few different
| things: reliability ("we didn't touch our service for 18
| months after deployment and it never ran into issues"), low
| resource usage ("we decommissioned X servers which saves us
| $Y/year"), and high performance are common responses.
| bool3max wrote:
| How do you manage to avoid generics while writing Rust?
| whatshisface wrote:
| You can't avoid _using_ generics, but you can avoid _writing_
| generic data structures if the standard library is sufficient
| and your application won 't benefit from de-duplicating code
| shared between similar structs.
| the__alchemist wrote:
| It depends on the use case. The short and simple answer is
| avoid libs that rely heavily on them, and use structs and
| enums. This gets into the area of application code vs
| libraries. A simple example, for say, a struct used to
| interact with a bus on a MCU is to use a `I2c` struct,
| instead of `I2c |