Async Rust Is The Bane Of My Existence | Prime Reacts

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
the bane of my existence supporting both async and sync code in Rust you sit beside me and hear this crazy old man's tale of when I asked rust for too much okay I like this introduction imagine you want to create a new library in Rust loser I actually did that right now all it does is wrap up a public API that you uh that you need for something else like the Spotify API or maybe a database like a Rango DB holy my I swear every time I read a database like every time I see a database on any blog post it's a database I've literally never heard of there's only one thing I think that out produces JavaScript Frameworks and that's databases there are more databases than there are JavaScript now that my friends that is the ultimate Jimmy Rustler okay because like who's what what are you going to do are you the contrary what dude this is the ultimate Jimmy rustler right here okay I love it I love it I'm so excited about it we'll see where that one goes we'll see where that one goes at the end of this okay go back to this oh it's so good so good tweeting while streaming is where all my tweets come from all right it's not rocket science you aren't inventing something new or dealing with complex algorithms so you expect it to be relatively straightforward you decide to implement the library with async loser uh by the way my last library that I've been invent trying to do at Netflix I tried to use just uh channels and no async like actually spawn threads and just do it like all myself dude I skill issued myself so hard gave up and went back to Tokyo and async I'm such a loser literally just have a global semaphore that I use as static a lazy static to to create so I don't have to make I can't make too many concurrent requests across my program dude I'm such a loser I am such a loser uh all right anyways uh most of the work in your library has to do with performing HTTP requests which are mostly IO so it makes sense that and because you want the cool kids uh because that's what's the cool kids use in Russ nowadays you start coding and have a v01 release ready in a few days neat you say as cargo publish finishes successfully and uploads your work to crates.io a couple days pass and you get a new you get a new notification on GitHub someone opened an issue how can I use this La Library synchronously my project doesn't use async because it's overly or my project doesn't use async because it's overly complex for what I need I wanted to try your new library but I'm not sure how to do it easily I would rather not fill my code with block on endpoint I've seen creates like request export blocking module with the exact same functionality could you perhaps do that as well I did I literally also used request blocking module to try to make it to try to do my own threading again skill issued I got skill issued so hard holy cow lowlevel wise that sounds like a very complicated task having a common interface for both async code which requires a runtime like Tokyo awaiting Futures pinning Etc still don't know what pinning is I mean I know what pinning is but I don't understand it and regular sync code I mean they asked nicely so maybe we can try after all the only difference in code would be the occurrences of the async and await keywords because you aren't doing anything fancy I feel I feel genuinely bad for this because you know here's the problem is you can't just block with Tokyo right you can't just block on it because the moment you block on something all a sudden you're Tokyo it gets all effed up so you can't just make syn code and then throw it like in a Tokyo spawn you can't just you can't just do that coloring is the worst thing in the universe well this is more or less what happened with the crate rsr spot potify I thought that was rust potify it's actually rust potify okay it's it's a pot distribution Network you wouldn't understand you you just wouldn't get it okay uh I created the rust pocket-based client lip not even joking this literally what happened this literally happened to you D it seems really hard honestly I I like I said I tried to write my own sync stuff just recently it's it's rust is genuinely hard I have fearful concurrency I have Fearless await for fearful concurrency oh well this is more or less what happened with the crate RS potify which I use to maintain long along with its creator ramay uh for those who don't know it's a rapper for Spotify web API to clarify I did get this working in the end although not as cleanly as I was hoping I'll try to explain the situation in this article okay okay to give more context here's what our Spotify uh clients look like roughly impul Spotify async some endpoint self pram string Spotify result string mute pams hashmap new insert Pam get some endpoint pams await okay okay essentially we would make some endpoint available for both asynchronous and blocking users the important question here is how do you do this once you have a dozen endpoints how can you make it easy to switch between async and sync for users I assume copy and pasting this is what uh was first implemented it was quite simple and it worked you just needed to copy regular client code into a new blocking module for RS potify request our HP client and request blocking share the same interface so we can manually remove keywords like async and do8 and in uh import request blocking instead of request in the new module nice how does this feel right now how does this feel reading what's happening like just reading this I'm emotionally getting just just hurt you can definitely do blocking youd put it in spawn blocking yeah but spawn blocking is like a special one- thread item you don't have the same amount of threads if I'm not mistaken I I again I haven't explored it too much but I'm I'm pretty sure spawn blocking is not the simple fix here I hate it but this is exactly how I would do it this is exactly how I would do it too I guess I'm creating two of them anyways then the RS potify user can just do uh RS potify blocking client instead of RS potify client and viola their code is now blocking this will blo the binary size for async users uh async only users so we just feature gate it under the name blocking and done boom bang bomb love it love it okay hold on Yo calm down okay calm down uh yes this is obviously better than just using bidirectional Channel and awake group and go okay come on hey hey we're not talking about go right now the problem was much more CLE uh clear later on though half the crates code was duplicated adding a new endpoint or modifying it meant writing or removing everything twice there is no way to make sure both implementations are equivalent unless you test absolutely everything which isn't a bad idea either but maybe you copy pasted the test wrong how about that atheist the poor reviewer would have to read through the code the same code twice to make sure both sides look all right damn which sounds incredibly prone to human errors yeah this does not sound fun I never even thought about that warning copy pastas yeah I know I actually I even think about that that every time you write a test you have to also copy pasta your test uh where's your feris now let's see in the in our experience it really slowed down the development of RS potify especially for our new contributors who weren't used to the whole ordeal as a new excited maintainer of RS potify I began in investigate uh to investigate other possible solutions all right calling block on uh the second approach consisted on implementing everything on the async side then you just make wrappers for the blocking interface which call blockon internally blockon will run the future until completion basically making it synchronous you still need to copy the method definitions but the implementation is only written once I assume you still need an async runtime though so if you tried to write sync code you'd still have like an async runtime running in the end right is that like is that what would happen where's runtime being defined by the way sounds like it yeah it kind of sounds like you'd have to have like a whole other thing involved too note that in order to call block on you first have to create some kind of runtime in the endpoint method for example Tokyo Tokyo yeah this does not sound fun by the way uh this raises the question should we initialize the runtime in each call to the end point or is there a way to share it oh lazy static Arc mutex lazy static Arc mutex or or Arc or whatever it is I don't even know what it' be does it mutate I have no idea anyways okay we can keep it as a global e our Global's e why do our okay why do people have constant upsetness about globals ever you know every now and then a Global's fine I said it I said it I am not 100% opposed to Singletons every now and then I have a Singleton I need you know every now and then I just need a Singleton yo dog I just need one of them I I I rarely use them because often I shoot myself in the in the foot but I will use it you know what I mean or perhaps better we can save the runtime in the Spotify struct but since oh that seems crazy but it seems to take a mutable reference to the runtime yep I was about to say that then you have the rapbit of AR mutex let's go you don't imagine okay for those that aren't seeing what's funny about this is that imagine you did this right why what makes this so funny is is the following here hold on what makes it so funny is let's say you spawned two threads right you have two threads that are running and each one of them wants to make a request right but your library has a hidden internal runtime that has an arc mutex so when this guy calls a and this guy calls B A goes here and sits on the mutex and this guy has to wait he's just sitting in line waiting for this one to be done and so the duration of a let's say this long it has to be weighted by B and then B can now make the request like how funny I mean this is I would have tried this though but it's hilarious right like you know like I love rust again I love rust it's one of my favorite languages but again this is like 14 seconds of overhead and go you know what I mean like this is like zero seconds and go like it's you're just done you're like you just do it you literally just do it and that's it that's it you just you just do it new go convert no I've been a go I've been on andof go convert for about five years now and I keep on trying to make go not happen Go's like the opposite of fetch I keep trying to make fetch happen and then I go back to go and I use it and I use it the most begrudgingly way I've ever used it I just use it and I complain the whole time and like this is so stupid I absolutely hate it this is just the worst language ever I'm done okay well I guess I don't need to program anymore because I completed my project super quick and everyone's very happy with it fine whatever this is so stupid I better make a quick feature to this is so stupid and then it ends up being super successful you keep saying fetch do you be are you serious are you not aware of the Fantastic film Mean Girls in 2004 really it's it's a it's a national treasure okay I also looking this up makes me feel old and sad I just want you to know right now that I feel old and sad sad that I just looked up this film and it's 20 years old and now I feel shitty okay stop trying to make fetch happen so fetch stop trying to make it happen okay fetch ain't happening anyways I keep like if I keep trying to make go not happen and it keeps happening it keeps happening I know sorry for first off I just ruined like a huge portion of people's day today I'm sorry feels so stupid right now the proper way to do this is with Tokyo's handle which looks something like this lazy static dude okay I feel like have I pre-read this I'm literally thinking I feel like I thought me and this guy have had the same line of thought through everything and that's because I Su at rust again I'm a dude I've been doing rust I've done hundreds if not thousands of hours of rust and I still am not good at rust uh I have preed this problem I know end point here we go I haven't actually I've never actually tried to do what he's doing and I I still am baffled by it while the handle does make our blocking client faster there's even more performant way to do it this is what request does or itself does in the case you're interested in short it spawns a thread that calls block on Waits on a Channel with jobs oh it spawns like a hardware thread is that what it does oh that just took me all the way out here let's just see what it does where does what is thread sorry I don't have my CLI I don't have my LSP so I don't know how to hop around uh standard oh it is a standard thread oh okay so it just it's that's a hardware thread thread right I'm not stupid right that's just it's just straight up spawning a good oldfashioned just spawning a good old fashioned thread okay this makes sense you can't use the runtime for this because it FS everything up the whole thing gets effed up you have to import a runtime uh I led out loud yeah let's see okay hold on let's find out okay where were we where where we here I know where we were two three yeah there we go right here uh unfortunately this solution still has quite a bit of overhead you pull in a large depy like futures or Tokyo Tokyo and include them in your binary all of that in order to actually end up writing blocking code so not only is it a cost at runtime but it's also compile time it feels wrong to me agreed this is a heavy agree uh and you still have a good amount of duplicated code even if it's just definitions that could be summed up request is a huge project and could probably afford this for their blocking module um but for a less popular crate like R RS potify this is harder to pull off I would assume that no module can really pull this off duplicating code's extremely difficult and costs a whole bunch duplicating the crate another possible way to fix this as the feature doc suggests creating separate crates we'd have RS potify sync and rs potify async a user would just pick whichever crate they want as a dependency even both if they need to the problem again yep is how do we exactly generate both versions of the crate I was unable to do this with copy pasting the entire crate even with cargo tricks like two cargo toml files one for each crate which was quite inconvenient could you do an RS build and generate the sync Co like sync code at at build time is that crazy probably is too probably too crazy uh with this idea we can't even use procedural macros because you can't just create uh a new create within a macro okay fair we would Define a file format to write templates of rust code in order to replace parts of the code like async await but that sounds completely out of scope and it sounds crazy honestly as I was saying that out loud I'm like dude imagine building Ginger templates for rust you gen you have some sort of like template language to generate your rust Cod oh counting Tokyo as a dependency is wrong since standard doesn't literally provide a runtime yeah but counting Tokyo as a as a dependency is wrong of course that's what I'm saying counting uh Tokyo as a dependency is wrong because a blocking crate if you only have blocking you shouldn't have Tokyo right uh what ended up working uh the maybe async crate uh maybe a third attempt is based on a crate called maybe async I remember foolishly thinking it was perfect solution back when I discovered it anyways the idea is that this crate can automatically remove async and await occurrences in your code with procedural macros essentially automating the copy pasta process oh that's a good attempt that's a really cool attempt I don't see how that would work but okay I like it because you got like request has this whole blocking thing you can configure whether you want asynchronous or blocking code by toggling the maybe async is uh sync feature when compiling the crate the macro works for functions traits and imple blocks if one conversion isn't as easy as removing async in a away you can specify custom implementations with the async imple and sync imple procedure macros it does this wonderfully and we've already been using an our Spotify for a while now oh clever that's pretty I mean it's pretty clever maybe always makes me nervous inherently it should in fact it worked so well that I made our Spotify HP client agnostic which is even more f flexible than being async sync agnostic this allows us to support multiple HTTP clients like request and Ure you you wrecked boy uh independently of whether the client is asynchronous or synchronous being HTP client agnostic is not that hard to implement if you have maybe async you just need to define a tradeit for HB client and then implement it okay this is actually pretty impressive I didn't know about uh maybe asnc this is cool a snippet of code is worth a thousand words all right trait HP client get string sync implementation Ure async implementation request Spotify endpoint let's go oh cool I don't understand the syntax but this is cool is this is this the equivalent of where HTTP is this thing is that like the equivalent of this is that what this is supposed to be trade bound yeah okay this is a trade boundary inside of a inside the generic statement okay okay I've always just used where T colon is all these things I I never really specify them right here okay it's the same thing cool is that something like inheritance no it's it's it's specifying what the thing can be right it's like saying this this item must adhere to this interface or more I'm using the term interface because I assume that you're just not familiar with rust so that makes more sense and an HP client is defined right uh right here htb client must have this async function right here the sync version looks like this the async version looks like this a little bit confused how this exactly works because this thing defines it as async oh but it's a maybe async so it allows this async to be dropped in favor of this if you have the okay okay okay okay okay okay okay okay hi uh then we would extend it so that whichever client they want to use can enable the feature flag with their cargo tomel for example if client Ure is enabled since Ure is synchronous it would enable maybe async is sync uh in turn this would remove the async await and then imple async blocks and the r RS potify client would use urx implementation internally it's actually pretty cool I didn't realize maybe async I mean it it's it's pretty cool I see the problems coming up but it's still pretty cool the solution let's see has none of the downsides I listed previous attempts no code duplication no overhead neither runtime nor compile time if the user wants blocking client they use urre it doesn't pull in Tokyo and friends quite easy to understand for the user just configure the flag in cargo Tel however stop reading for a couple minutes and try to figure out why you shouldn't do this in fact I'll give you nine months which is how long it took me to do so I I honestly don't know why I I I can't think of why so I don't know cuz I don't have nine months to think about it the problem Ferris R oh rust is the problem rust itself is the problem all right let's see it well the thing is that features in Rust must be additive enabling a feature should not disable functionality it should it should usually be safe to enable any combination of features oh maybe async is an either or you can't have both cargo May merge features of a crate when it's duplicated in the dependency Tree in order to avoid compiling the same crate multiple times the reference explains this quite well if you want more details okay we don't want more details this optimization means that mutually exclusive features may break a dependency tree in our case maybe a sync isync is a toggle feature enabled by client Ure so if you tried to compile with client request also enabled it will fail because maybe async will be configured to generate synchronous functions signatures instead it's impossible to have a crate that depends on both sync and async rs potify either directly or indirectly and the whole concept of maybe async is currently wrong according to the cargo reference man I this is emotional uh the feature uh the feature resolver V2 a common misconception is that this is fixed by feature resolver V2 uh which this reference also explains quite well okay what is that it has been enabled by default since 2021 Edition but you can specify it inside your Cargo toml in previous ones or in previous ones this new version among the other things avoids unifying features in some special cases but not in ours features enabled on platform specific dependencies for targets currently not currently being built or ignored or ignored buil dependencies and proc macros do not share features with normal dependencies Dev dependencies do not activate features unless building a Target that needs them like test examples okay in this case I tried to reproduce this myself and it did work as I expected this repository is an example of conflicting features which breaks with any feature resolver feels bad other fails there are a few other crates that also had this problem Aaron a Rangers a Rangers a rangor and aragog oh this is more of those DBS orang Rango DB it's a Rango it's a Rango so it's a r a Rango rust a Rango RS or aragog uh both use maybe async damn inkw well a wrapper for llvm it supports multiple versions of llvm which are not compatible with each other and k8's open API damn fixing once the crate started to gain popularity this issue was opened with maybe Ayn which explains the situation okay here's the problem maybe async would now have to have two feature Flags is a sync and is async the crate would generate functions in the same way but with sync or acing suffixes appended to the identifier so that it wouldn't uh be conflicting for example dang I don't know if this is really winning that much however these suffixes introduce noise and I wonder it would uh one let's see so I wondered if it'd be possible to do it in a more ergonomic way isn't this what uh the whole async question mark thing is supposed to fix in Rust isn't this about to be a a l a language level feature Sabine it is Sabine I I swear this is crazy but I swear there's like this async question flag rust right here I swear there was there was something I read about this it's like a f a potential future that people are talking about uh here oh it's a it's l question mark the other way yeah they're going to be doing these hold on let's let's oh question async doesn't exist Oh I thought this one would H would have it I swear there was one that had uh yeah okay maybe this maybe this goes in more detail about constant ayc yeah it goes into constant ayc we can do these you can do these kind like you can go like hey this thing could it could be a problem anyways a man I love Russ but it's async is uh so difficult to deal with it is it is very it is very difficult to deal with all right let's keep on going we're almost we're almost done here right we're we're we're we're almost done here this is very very interesting however the suffixes would introduce noise yep uh let's let's see I already read that in summary it was about let's see it was too complicated and and I ultimately gave up the only way to fix the edge cases would be to worsen the usability of RS potify for everyone and I'd argue that someone who depends on both async and sync is unlikely we haven't actually had anyone complaining yet so it's a fictional problem is that what I just read let's go unlike request RS potify is a highlevel library so it's hard to imagine a scenario where it appears more than once in a dependency tree in the first place yeah yeah uh this is like the edge of an application right edge of application libraries typically only appear once perhaps we could ask cargo devs for help uh support from cargo RS potify is far from being the first day yes there's a whole closed there's a whole thing going on you can look at all those blah blah blah blah it's it's it's it's this problem that exists in cargo it's crazy currently we have a choice to make between ignoring the cargo reference we could assume that no no one is going to use both sync and async for RS potify at the same time fixing maybe async to add the suffixes to each end point that sounds terrible dropping support for both async and sync code it's kind of becoming a mess and we don't have the manpower to deal with uh with and that affects the other parts of RS potify the problem is that some crates that depend on RS potify like uh NC spot or spotifi are blocking and others like Spotify 2y are async yeah damn this is a real problem you got yourself into this is a this is tough this is real tough this is real tough I know this is a problem that I have imposed to myself we could just say no we we only support async or no we only support sync while there are users in interested in uh being able to use both I feel like if you only support sync you could use the Tokyo blocking threads right you could let somebody do the async part themselves as opposed to supporting both it seems like the better choice honestly with rust is just make sync stuff when you can and just let async be an application Level concern I mean I know it makes the application more inconvenient right but still honestly this sounds really horrible this sounds terrible while there are users interested in being able to use both sometimes you just have to say no yes good job if a feature becomes so complicated to deal with that your entire code base becomes a mess and you don't have the engineering power to maintain it then it's your only choice if someone cared enough they could just Fork the crate and convert it to uh to synchronous for their own usage after all most API rappers and the like are uh only support either syn asynchronous or blocking code Serenity Discord API squeal x uh T oxide telegram are async only for example and they're quite popular I think this is one of the problems of Open Source in general you really have to be careful listening to other people just to be real here for a second listening to other people is really difficult because everybody's going to have what they want to see in a library and what they want to see in a library may not be what you want to build and sometimes you just build a thing for you and what you want if you want it to be Asing just Let It Be async and that's what you want to build now other languages don't have this problem but go certainly does or uh Russ certainly does uh even though it is quite frustrating at times I don't really regret spending so much time walking in circles trying to get both async and sync to work yeah this good actually I I'm sure I was very very educational I was contributing to RS Spotify RS sptify in the first place to just to learn I had no deadlines and no stress and I just wanted to improve a library in Rust in my free time and I have learned a lot and hopefully you do after reading this it sounds like you approach the problem in the same series of steps I would have approached it uh perhaps the lesson today is that we should remember that rust is a low-level language after all and there are some things that aren't possible without a lot of complexity anyhow I'm looking forward to how the rust team fixes this in the future so what do you think uh what would you do if you were a maintainer of RS potify if I was a maintainer of it I would build a sync version I would build a sync version and let you deal with async in the way you want that way I don't enforce say Tokyo right because one problem about writing it in async is often you might have some sort of like Tokyo reference or some nonsense in there that they may not use Tokyo you know what I mean I know everybody uses Tokyo all of this to listen to music all of this in the end to listen to music I have a really important question in your 20 plus years of experience uh would be the natural progression of knowledge uh that you've had over the years I would like to Benchmark please your you're awesome dude I have no idea what that question even means um so for those that are wondering because this term colored function keeps coming up and a lot of people don't know what the term colored function means it's really really simple hey crows thank you very much here's what a colored function means pretend you had some function uh we'll call it red and for you to call Red you just had to call it this way now let's pretend we had another function we're going to call Blue and for you to call Blue you have to call it differently let's just say to call Blue you have to call it this way it's just different right now what's the problem here here uh what's the problem here well anytime you had a blue function you C you can't have a red function with blue functions meaning that if I had my red function right inside of it I can only call other red functions right uh red two I can't call a blue function because it doesn't work in my function so so that means to make this work I actually have to turn it into a blue function right and now I can still call my red functions but my blue functions only work in blue functions and so then I have to do this whole thing going on right here so what what what this really is is this right and so for you to be able to call a blue function you have to wait the blue function right I have to wait for the result to come in so in a wait requires you to change how you call your function and so all asynchronous code all async await code colors functions and once you color a function it becomes really difficult uh for you to uh for you to be able to like handle this code like right once you do that that's this that's the entirety of the problem we just read is in Rust you have this whole problem of red and blue functions and then you have to like choose which one you're doing and you can't mix them they're very difficult but in uh go you don't have that function or that problem in go right you have this beautiful thing where you can go like go you know Funk yourself and do this thing right and blah blah blah blah and then you just return out say a channel right and so this function appears to be synchronous despite executing asynchronous and you just go and do your things right you just don't even care you don't even have to think about it that's what's that's what's neat about goes that it doesn't have red and blue it just has red same stuff in JS no no not same stuff in Js Js has colored functions you have async and non-async changing uh function colors inside a lib wrapper is kind of terrible too yeah it's extremely difficult like it's extremely difficult and rust makes it rust is just Peak difficult this is it this is truly it this is what happens with async right this is what happens with async this is perfect it says I retweeted it but I had I didn't like it I swear the like always breaks this is very very funny anyways the name uncommon Ryan Winchester W uncommon Ryan Winchester W but quite the W might I add the name is the primagen
Info
Channel: ThePrimeTime
Views: 95,072
Rating: undefined out of 5
Keywords: programming, software engineer, software engineering, developer, web design, web development, programmer humor
Id: _lwvoBBqa9k
Channel Id: undefined
Length: 35min 36sec (2136 seconds)
Published: Tue Feb 06 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.