CppCast - RxCpp and Executors

Episode Date: March 7, 2019

Rob and Jason are joined by Kirk Shoop to talk about the RxCpp library and the future of Executors in C++. Kirk stumbled into an internship at Microsoft in the 90s that turned into contracting... and eventually employment at Microsoft. At Microsoft Kirk sometimes pushed the compiler to its knees in the pursuit of libraries that prevent common errors. In 2013 Kirk joined Microsoft Open Technologies Inc to work on open source. Kirk began investing heavily in rxcpp in the belief that it is a better abstraction for async than the primitives commonly used. Now Kirk works at Facebook with Eric Niebler and Lewis Baker to build async range concepts and algorithms (with coroutines) into the c++ std library. News Kona: A trip report Are C++ Modules DOA 2 C++ Breaking the Rules with Inline Variables and Functions Kirk Shoop @kirkshoop Kirk Shoop's GitHub Kirk Shoop's Blog Links RxCpp ReactiveX CppCon 2016: Kirk Shoop "Algorithm Design For Values Distributed In Time" Sponsors Backtrace Announcing Visual Studio Extension - Integrated Crash Reporting in 5 Minutes Hosts @robwirving @lefticus

Transcript
Discussion (0)
Starting point is 00:00:00 Episode 189 of CppCast with guest Kirk Shoup recorded March 6, 2019. This episode of CppCast is sponsored by Backtrace, the only cross-platform crash reporting solution that automates the manual effort out of debugging. Get the context you need to resolve crashes in one interface for Linux, Windows, mobile, and gaming platforms. Check out their new Visual Studio extension for C++ and claim your free trial at backtrace.io. In this episode, we continue discussing all the news from Kona. Then we talk to Kirk Shoup. Kirk talks to us about RxCPP andast, the first podcast for C++ developers by C++ developers. I'm your host, Rob Irving, joined by my co-host jason turner jason how's it going today i'm doing okay rob
Starting point is 00:01:31 how are you doing i'm doing fine so uh you're on travel this week right i am traveling and i have brought possibly the worst microphone that i could and And actually, seriously, I know for a fact that the microphone array built into my laptop is better than the one that I'm using. But I can't use it as long as I have something plugged into the phono jack, right? If I had a USB headset, then I could possibly switch over to the other one. Okay. And I can't just take off the headphones because that would be back in other problems. Yes. So I apologize to the listeners. It's okay. Hopefully we'll be able to clean it up pretty well.
Starting point is 00:02:09 But you'll be back next week, right? Yeah. Yes, I will be. Okay. Well, at the top of the episode I threw a piece of feedback and usually I'm the one who picks these out from tweets and emails that we get. But Jason, you got some feedback yourself, right? Yeah. And by usually you mean always 188 of the 189 episodes although i don't think we had any feedback on the first one
Starting point is 00:02:32 oh well that's probably true yeah right uh so i got this yeah yeah sure i got this email from one of the organizers of the core c++ uh conference that I'm going to be speaking at in May. Michael Goldstein? I don't know, actually. It's a German-looking name, but I don't know how he would pronounce it as an Israeli.
Starting point is 00:02:58 He says, I just listened to the last episode of CppCast. Thanks, guys, for mentioning the conference on each show. I wanted to make an order with regards to language of the sessions. The initial plan was to have one full English track and one track mixed with English and Hebrew sessions. Eventually, all the local speakers decided that they deliver their talks in English. So besides the single pre-conference workshop, all talks are in English. Okay.
Starting point is 00:03:23 So I'm guessing most people in israeli are fluent with english and hebrew so maybe just makes more sense that way and it'll make all the content available for uh english speakers online i don't know anything about israel in that regards but i'm assuming i know in in the programming world most people know english so right i believe that's what jens has seen with uh with meeting c plus plus he originally had a german track and then dropped it because everyone wanted to speak in english makes sense okay uh well we'd love to hear your thoughts about the show as well you can always reach out to us on facebook twitter or email us at feedback at speakass.com and don't
Starting point is 00:03:59 forget to leave us a review on itunes joining us today is kirk shoop kirk stumbled into an internship at Microsoft in the 90s that turned into contracting and eventually employment at Microsoft. At Microsoft, Kirk sometimes pushed the compiler to its knees in the pursuit of libraries that prevent common errors. In 2013, Kirk joined Microsoft Open Technologies to work on open source. Kirk began investing heavily in RxCPP in the belief that it is a better abstraction for async than the primitives commonly used. Now Kirk works at Facebook with Eric Niebler and Louis Baker to build async range concepts and algorithms with coroutines into the C++ standard library. Kirk, welcome to the show.
Starting point is 00:04:37 Thank you. So 2013, that's kind of when Microsoft was starting to get more serious about open source, right? Yeah. So by that point, I think that the MS Open Tech subsidiary had been around for a year or two. And so I joined in the middle of that. And it was a great experience. I got to work on open source while being a Microsoft employee. What projects did you work on? Do you mind telling us?
Starting point is 00:05:09 Well, we had a port of Redis for Win32. Okay. And we worked a lot with the Dash.js player, which was a JavaScript implementation of the Dash protocol for media playback. Okay. And then there were other brief flings with Blink and Video Codecs. Right. Okay.
Starting point is 00:05:34 Microsoft just announced a couple days ago that they had made Calculator open source. Nice. Which they're getting made fun of a little bit for, but it is all in C++. And so it is kind of relevant to this, although it's in part of it seems to be in that, that dialect of C++ that I can never remember the name of, uh, C++ CLI, the one that that's got, uh, yeah. With like carrots in it and stuff. Yeah.
Starting point is 00:06:02 That would make sense. But then another part of it seems to be written um with the win rt uh findings oh okay okay well uh kirk we have a couple news articles to discuss and we'll start talking more about rxcpp and some other things you work on okay great okay so this first article is uh another kona trip report and these next two articles kind of go hand in hand a little bit. And I definitely want to get your feedback on these Kona trip reports because you were at Kona, right, Kirk? I was. Yeah, so we talked a lot about Kona and all the news that came out of C++20 last week with Peter Bindles.
Starting point is 00:06:49 This article highlighted a couple things for me that I don't think we talked about with Peter. It's from Corentin's blog, and it's a ConeTrip report. He was there as well. And he talks first a lot about coroutines and modules. And I think there's actually a picture here that, if I'm not mistaken, has you in it, Kirk. Yes. The co-celebration when i sit at the end of a table i call it the foot um but one of the things that he talks about with um with both modules and coroutines uh is that they are not going to have any standard library support in c++ 20 which is not something i thought about,
Starting point is 00:07:25 that we're getting coroutines and modules, but if you're looking for anything in the standard library, it's not going to be there. And that kind of made me wonder, I wonder how much adoption there's really going to be over the next few years with C++20 modules in standard library, if there is no standard library support. Yeah, well, as pointed out in the blog,
Starting point is 00:07:47 Lewis Baker has CppCoro. And there's also a growing coroutines library in Folly, also written by Lewis. Okay. That's Facebook's library, right? Yeah, Facebook's C++ library. And so there are a couple of sources of library level code for wrapping up coroutines and using them in a correct way. Lewis is very good with that. And he also has submitted a paper in San Diego to add task, which would kind of be your default return type for functions that were coroutines. There was some bike shedding that initially tried to move that has tried to
Starting point is 00:08:35 move away from task and has picked lazy as the name of this type. I think we're all hoping that we can do better. Better than lazy? Yeah. Yes. Okay. Yeah. Okay. And that's in reference to it being a lazy evaluated function? So laziness and eagerness is the difference between coroutines and futures.
Starting point is 00:09:20 So a future, as soon as you create it, the work is in flight, and then you have to allocate shared state between the promise and the future and resolve the setting of the continuation or the get call with the set value or set exception call, which requires synchronization through that shared state. Whereas with coroutines, you can wait until the co-await keyword has attached the continuation, which is now the rest of the function that the co-await was in, before you even start the work. And by doing that, you don't have to create the shared state and you don't have to create the synchronization because you didn't even ask the producer to start until you had already attached your continuation. Okay. Okay. Is there anything else you guys wanted to highlight about this trip report from Corinton?
Starting point is 00:09:54 Hopefully I'm pronouncing that somewhat right. Well, there's a whole list of all the papers. Yeah. He apparently produced the most papers that were newly introduced at the Kona meeting. Yes, and apologized for it. Yeah, there's some interesting
Starting point is 00:10:14 ones in here that I was not familiar with. For example, if I can find it again, deprecate use of the comma operator in subscripting expressions. So I couldn't, it wasn't clear to me on some of these what the movement was. This one he's hoping for C++23 or something like that.
Starting point is 00:10:38 So that bracketed subscripting expressions can have multiple parameters in them as we plan. Right. And he also has another paper here, which it says he worked on with Christopher DiBella introducing a whole bunch of new views, uh, view all the things. Conceptify all the things.
Starting point is 00:10:58 Right. Okay. Uh, is there anything else we want to talk about with this article or should we move on? Uh, J thread also mentioned in here, which I was, again, completely unfamiliar with. The idea is an automatically joining interruptible thread,
Starting point is 00:11:14 which standard thread does not give us any of those things at the moment. I must say that I'm far more excited about the stop token that is in that paper as well. Right. That is more generally usable across a lot of different concurrency abstractions. And so I'm looking forward to having that in the standard library. Yeah, I just had this conversation with my students like yesterday. We were talking about these things. I'm like, well, you could pass in an atomic bowl into your future,
Starting point is 00:11:45 use that as a stop check and, you while doing continue work whatever and i i got some pushback like what does it have to be an atomic and i'm like only if you want your code to work but yes having like an actual like a type that's i'm assuming it's basically an atomic bull i didn't like read into the details of this but i did also notice it in the paper it's a little more than an atomic bull uh i think originally it was atomic bull and then lewis uh stepped in again and helped um nico get uh a full token with callback support as well. And so the callbacks end up as a linked list as they're attached. And the callback object in there automatically detaches a callback on destruction.
Starting point is 00:12:38 Interesting. Yeah. And so that means that you don't really have to worry about lifetime that much because the destructor will automatically remove you from the list of things. And it's all a lock-free implementation that makes sure that even if the stop signal is racing with the destructor, nothing gets raced or deadlocked. It just cleans itself up properly. And I know from watching Tony Van Eerd's 39-part series on lock-free data structures that it's a very simple thing to do. Yeah, I think there were at least four iterations on that to get it correct.
Starting point is 00:13:21 I'm sure. And I would be willing to put money down on that there will be a fifth iteration. I have a lot of faith in Lewis's code, but yes, it wouldn't surprise me if we have a little bit more to do. Yeah. But, I mean, let the experts do that, basically.
Starting point is 00:13:40 Yes. Okay, and this other article is our C++ module modules dead on arrival and this is on the vector of bull blog and you know he wrote an earlier post before kona called c++ modules might be dead on arrival which caused a bit of a stir in the community and um you know i think i'm definitely happy to see that modules is going into C++20, and this author is as well. And he just talks more about the technical report, which we talked about with Pete last week, that should give some outlines on how modules should actually be implemented that don't belong in the standard report, standard paper.
Starting point is 00:14:22 So were you involved at all in the modules process, Kirk? I was not. No, I was full up on executors and coroutines. Do you have an uninformed opinion you'd like to share? So I'll say that I was initially extremely excited about modules until i understood them better and realized that they would not really help with uh things like range v3 or rxcpp as far as build speeds go and is this because of the heavy nature of these things yeah the goal for uh these is to be a static polymorphism by default with opt-in dynamic or polymorphic runtime polymorphism. And because of that, that means that you end up building up these huge chains of templates that contain, say, a whole bunch of Lambda signatures in them.
Starting point is 00:15:21 And the result is that none of that code could have been pre compiled into like none of the code wrapping those lambdas could be pre compiled, it had to wait until instantiation. And so you don't really get the build, I don't expect to get the same level of build improvement that you would with normal, you know header uh ap uh implementation split right uh libraries yeah that has been my question since i first heard about modules as well as like uh because i've written large header only libraries like i don't know if it would gain anything here but then i have seen some comments that like of course the standard doesn't require it, but some tools may take advantage to basically do PCH or something in these cases. Yes, that will be quite interesting.
Starting point is 00:16:11 I still think that template instantiation in libraries like rangev3 and rxcpp are dwarfing the parsing time. Like, parsers are pretty quick these days, but compilers are still pretty slow at template instantiation. I also just realized, I think for our listeners, I should define PCH as pre-compiled header, which can have an impact on large projects that keep parsing the same headers over and over and over and over again.
Starting point is 00:16:44 Okay, and then this last article is a post on, projects that keep parsing the same headers over and over and over and over again yeah okay and then this uh last article is a post on uh it's called c++ breaking the rules with inline variables and functions and it goes over uh use of the inline keyword and clears up some things that some c++ programmers uh might not be getting right about how the compiler can choose to inline a function even if it's not marked inline, and how the inline keyword, when placed before a function or placed before a variable in newer versions of C++, has very different behavior than what you might be thinking of,
Starting point is 00:17:21 and it has to do with the one-definition rule. That's a great overview of inline in its uses yeah is there anything i didn't you know explain well enough that you want to talk about jason nothing that jumped out to me that is like if you want an overview of what inline should actually be used for yeah that's a good post a good post i guess i would mention that this at no point like it kind of just says well magically in these cases the variables initialized on first use but doesn't say anything about the fact that that does happen atomically safely in a multi-thread environment as of c++ 11 which i think is relevant information for some people that machinery has to come from somewhere right okay So, Kirk, should we start off by just letting you tell us
Starting point is 00:18:07 a little bit more about what reactive extensions are? Sure. So in my time at Microsoft, a team was put together in the.NET realm that started with the, they started with the iterators that are in C Sharp, and they made a mathematical dual of them into an async realm, into something that was useful in an async realm. So instead of having a get method and a next method, you kind of invert that, and you call a next method with the next value and you call and instead of throwing an error you call a value a method with an error it's kind of hard to explain in words but essentially what you end up with is a a callback that is formally defined in such a way that much like iterators, you can compose these things into
Starting point is 00:19:07 algorithms and sources and consumers, like producers, consumers, and algorithms can all interplay and not really have to know any implementation details about anything else in the chain that they've been composed into. And so it ends up being, one of the ways I describe it is you end up being able to build libraries of algorithms much like RangeV3, where RangeV3 will deal with items that are distributed in space, a vector or a list, something that's already local and complete. Whereas this other model allows you to introduce the concept of time. You can have values distributed by time where you don't yet have the next value. And it will just, you know, this iterator pattern will tell you when the next value arrives.
Starting point is 00:20:01 And until then, there's no thread being blocked. There's no get method. It's just nothing's happening until the next value is produced. And how are you notified when the next value is produced? So what you end up with is two interfaces in the core, which is the observable, which has a subscribe method on it. And that subscribe method takes a subscriber, and the subscriber is an observer. And the observer has value, next, error, and done, completed. I'm having to map from a newer library with different names. Okay. So it's next error and completed. And so what happens is that when you pass one of these
Starting point is 00:20:56 subscribers into an observable, the observables subscribe method inside that subscribe method implementation it can call back with the next value or with a terminating error or with a completion signal okay and one of the things that really kind of i mean this is a lot similar to a future in that the future the future as it is today is eager where you create the promise and get the future from it. And then the future might have a then method on it if it had continuation support. If it had continuation support. If it had continuation support. But that backwardness of getting the promise first and then retrieving a future from it is what requires this eager model where you allocate and
Starting point is 00:21:46 synchronize. So the Rx model is inverted from that where you create an observable and that observable will eventually be given a subscriber and only at that point will you start doing work to produce values into that subscriber. But that subscriber ends up having nearly the same surface as a promise. And so that's why it's really an inversion. It's like saying that.then on a future takes a promise because it can receive from.then either a value or an error. And currently we've modeled that in some of the experimental TSs. We've modeled that with actually passing the future in
Starting point is 00:22:33 to the then, the continuation passed to then. And then the code in there has to check, oh, is it an error or is it a value? Whereas in the RX model, you have two completely separate functions for that. And then you have a library that allows you to build receivers from single functions if you want to ignore errors. And so the result is that when you're writing something like transform and you're putting a function into transform,
Starting point is 00:23:07 it doesn't have to check a promise for a value and then transform that value. It just gets the value and it never even gets called in the error case. Okay. So you said how this all started with a group of Microsoft working on what became reactive.net or rx.net.
Starting point is 00:23:27 And my understanding is there have been multiple other languages that have then made the same library. So there's like an RxJava and we now have the RxCPP. What motivated you to work on RxCPP? So I was in a team at Microsoft, and one of the people I worked with in that team moved on to join the team that was working on reactive extensions and built a prototype of Rx in C++. And this initial prototype, everything was a shared pointer to a Vtable object. So your observer was a Vtable, and your observable was a shared pointer to a V table object. So your observer was a V table and your observable was a V table.
Starting point is 00:24:09 And I went and worked with that for a while until I understood the model better and said, you know, this can't stand. So I went back and rewrote the whole thing so that everything is static polymorphism by default, essentially concept-driven. And so there's many implementations of unobservable and many implementations of an observer, and they just have to, like iterators, match a concept in order to have the whole thing compose. So something I'm not clear on, and if you don't mind rewinding in case I missed it,
Starting point is 00:24:47 as your callbacks, is it asynchronous or is it just within the current thread or what? So that's one of the nice things about just calling this a concept where these method definitions are there. It is entirely up to the implementation how it will actually behave. So I could have a
Starting point is 00:25:06 constructor for an observable that has already started the computation just like an eager future does today. And then it has to do the synchronization in order to be able to, when the continuation is attached later, deliver the value or error to it. Or I can do the more default implementation where, say, subscribe actually just directly calls back on the same thread. Or I can have it actually moving off to a different thread to do some work and then calling back from that thread. And so all of these types of implementations are actually possible under the same concepts. Okay. Can we maybe talk about what some of the use cases are where this observable pattern works well?
Starting point is 00:25:51 So it's certainly applied a lot when you're talking about when you're implementing UIs. And so in that world, you have, you know, your main UI thread, and there's whole bunches of events. And you want to be able to attach to those events, and then compose them with algorithms so that you can, say, react to a click by doing some work and then updating the UI. And so you'll have operators or algorithms, like subscribe on or observe on, which will let you control the execution environment for the subscribe path when you're attaching a continuation and the path that the value error completed signals return on independently.
Starting point is 00:26:39 So subscribe on is the startup path, and observe on is the startup path, and ObserveOn is the result path. And you can just specify what execution context you want on each. I had been thinking earlier before you just mentioned that UIs, it's similar to how UIs are often implemented. Are you familiar with Qt's signals and slots mechanism or Boost? Okay, it sounds like it feels similar. So my understanding of QT signals and slots
Starting point is 00:27:11 is that they're essentially single functions. And if you want them to do things like carry errors or have cancellation capabilities when you're working in an async environment, then you really have to compose a whole bunch of stuff around them to make that happen. Yeah, last time I used it. And so the thing about this, the Rx model,
Starting point is 00:27:34 is to try and make sure that cancellation is a first-class citizen and that sequences that are empty or full or have interruptions through error are all first class. And so then you can start building things like retry or repeat so that every time a signal finishes, you can start it up again. Or every time a signal fails, you can start it up again. Interesting. All right. We just mentioned signals and slots.
Starting point is 00:28:01 And when we're talking about news, we talked about coroutines and futures. And you're saying React extensions is kind of like an inversion of futures. And I'm starting to think like with all of these tools available, and they all have kind of overlapping goals in some senses, like how do you know what thing to pick for what problem you're trying to solve today absolutely i i definitely would like to get to a world where you're picking implementations instead of concepts okay i would like to get in a world where we have a set of concepts for async that are the smallest collection of things required to represent callbacks, because those have turned out to be the most ubiquitous and most efficient way to communicate async information. And once we have that set of concepts that we can, you know, conceptually think of as mapped to iterators, Now we've got this world where, yeah,
Starting point is 00:29:05 I can have an implementation that behaves completely differently when I need it to. And now I'm just picking types instead of picking concepts and everything's still composed as well. So what do you think needs to happen for that world to become a reality? So we've begun that work in the past year uh we uh wrote a paper well i wrote a paper for san no for rappers will uh that eric niebler presented and it kind of altered the direction of began to alter the direction of executors oh so you're why executors aren't done yet. My fault. Yeah, so I was concerned that the direction it was going in wasn't going to compose well once we had something like an async iterator. Okay.
Starting point is 00:29:55 And I wanted, you know, in order to build any algorithms over such an async iterator, you have to have a executor concept to pass into those algorithms as a way of creating an injection of a dependency on a particular execution context. That's often required for a particular algorithm that it be able to create new work items on an execution context. And so having a set of concepts that can kind of cross both executors, not just both, but executors and async iterators and audio IO and network IO and all of these places where we have callbacks. We started with the paper that was in flight, which was executors, and began to work with that group of authors. And in Bellevue, right before CPPCon last year, there was a meeting where we went over what's called the sender-receiver design, which is essentially observable observer. We went over that design, some papers related to it, and the vote in the room was that in the future, 23 and beyond, we would be working towards a center receiver model for executors. So you said something, and now I'm trying to remember exactly how it went,
Starting point is 00:31:35 but you were saying, oh, shoot. But you were talking about how you have to choose the execution model for the thing that you're trying to do and inject it into the thing. That was a exact quote. But your quote, I mean, your sentence, and it kind of made me think like whenever I hear executors and I'm 100% uninformed, I haven't looked at anything about them. A part of me was having knee jerk reaction that says, I just want a future. I don't want to have to think about the execution model that I need to inject into the thing, right? Yeah.
Starting point is 00:32:09 Can you speak to that at all? Sure. I'm trying to decide which side to approach that from. Okay. important to the executors authors uh the current papers 0443 uh was to make sure that whatever abstractions they came up with were um functional for both cpu and gpu and extensible into even more exotic execution environments okay fundamentally when you think of an executor, you're just creating a callback that's going to come from a particular execution environment. all of these different concepts around execution, we can create a single concept for callbacks or a very few concepts for callbacks and then have different implementations for things that would go to a GPU and go as algorithms.
Starting point is 00:33:18 So you could have algorithms that wrapped up these concepts in a way that allowed people to interact with execution environments in a more natural way. So the hope would be, for instance, that you'd eventually be able to pass something into the existing standard async as a policy that specified an executor, and then internally it would be implemented in terms of that so you still interact with the async interface but underneath you've been able to inject in a thread pool or theoretically a gpo but that actually turns out to be uh very challenging because gpus actually cannot execute arbitrary code at this point they want to eventually but they cannot now and so that means that when you're creating a function for a gpu you actually have to compile it with a different compiler and the code that you compile there really isn't able to just arbitrarily say and call this function
Starting point is 00:34:18 on the host and that turns out to be a problem when you're building expressions of things where you want to be able to transition back and forth from CPU to GPU. And so we're still working with NVIDIA and SYCL on ways to represent that using the same concepts. So that's a work in progress. We're targeting 23. Okay. So today, standard async takes just a flag for what its execution policy is and you're saying in the future be the option of basically an object or a type or whatever that you want to inject that says do this thing do it asynchronously do it deferred do it on the gpu
Starting point is 00:35:00 theoretically hypothetically or i think probably way more relevant to the average person listening is do it in this thread pool. Yes. Okay. Okay. I want to interrupt the discussion for just a moment to bring you a word from our sponsors. Backtrace is the only cross-platform crash and exception reporting solution that automates all the manual work needed to capture, symbolicate, dedupe, classify, prioritize, and investigate
Starting point is 00:35:24 crashes in one interface. Backtrace customers reduced engineering team time spent on figuring out what crashed, why, and whether it even matters by half or more. At the time of error, Backtrace jumps into action, capturing detailed dumps of app environmental state. It then analyzes process memory and executable code to classify errors and highlight important signals, such as heap corruption, malware, and much more. Whether you work on Linux, Windows, mobile, or gaming platforms, Backtrace can take pain out of crash handling. Check out their new Visual Studio extension for C++ developers. Companies like Fastly, Amazon, and Comcast use Backtrace to improve software stability.
Starting point is 00:36:00 It's free to try, minutes to set up, with no commitment necessary. Check them out at backtrace.io slash cppcast. So you said that executors in C++23 are going to have the, you know, the sender receiver model, which is kind of based on the observer pattern RxCPP. So are you standardizing parts of RxCPP basically? We won't be taking any exact codes or patterns from RxCPP, but we will be taking essentially the set of algorithms. It'll be kind of like RangeV3 where we don't get them all in the first release, but we'll start with the basics, the concepts,
Starting point is 00:36:41 a few library utilities to help build implementations of these concepts, and maybe polymorphic, who knows, wrappers for these concepts, and then a few algorithms that kind of show where we're going. And then over time, we'll fill out the algorithm set to be more like RxCPP's current algorithm set. It kind of sounds like the third iteration of this. You saw the original idea, said, I like this, but had to re-implement it. And now you're saying, now what's the ultimate conclusion of this work? There was a lot of work to try and take the RX model,
Starting point is 00:37:16 which was built for garbage-collected lifetime management, and map it into the C++ model. Of not garbage-collected. Of not garbage collected? Of not garbage collected. And I love that model, but when you get into async, lifetime is the hard problem. Okay, yeah.
Starting point is 00:37:35 Because you don't have any end brace to attach the work to. Right, right. And I'm a staunch believer sadly for j thread that the destructor is a horrible place to join because you're in this unwind path that may be in response to an exception and you've just told a thread that you're going to wait for it in a possibly memory-corrupted state. So I've never been a fan of any sort of cleanup in the destructors in an async operation.
Starting point is 00:38:21 And so one of the things that we're actively researching right now is how we would add essentially async destructors to the language, especially in the context of coroutines, which also... So in coroutines, they've solved the problem one way, but it has a consequence. So the way they've solved it is that they have taken the destruction and modeled it after existing functions. And so what that means is, is that I could have a result, a result that I am ready to go do more work with, but I will never give them that result until I have shut down all of the async resources that were in use. And that was necessary in the synchronous function world. Like, you had to shut down before you could return. But in the async world, you really don't want to join there.
Starting point is 00:39:15 You want to start the next operation on that value, and then later on make sure that you joined the cleanup from the previous function. Right. And so that sort of async cleanup model make sure that you joined the cleanup from the previous function. Right. And so that sort of async cleanup model is something that we're starting to conceptualize. There's a sense in which it's somewhat related to the using keyword in.NET, and that is certainly one of the directions that we've been exploring,
Starting point is 00:39:46 which would be having a using keyword or a co-using keyword in C++ that would essentially invoke this asynchronous destructor on the object that was a parameter to it, you know, in the expression applied to the using. And it would apply, it would make that call at the closing curly brace of the using block. That's one possible world. But the goal is to make sure that you have something prior to unwind outside of an exception path that is a performing async unwind. It's a lot to think about. Yeah. I have been working on RxCPP since 2013.
Starting point is 00:40:28 And Lewis has been working on coroutines for at least three years or more. And we're both like, we've spent the last year mind melding on how to represent both because they're the same. I mean, my way of explaining coroutines is that it's sugar over callbacks. And my way of explaining RxCPP is that it's formalizing callbacks. And so since we're representing callbacks in both, then there should be solutions that are common between them. And once we have them, and there was actually a paper in the post mailing for San Diego that Lewis built where he actually showed a library where a single object was both awaitable and observable, essentially. So you could either call the subscribe method or you could call co-await on the same object. And so we were going to call that, in the paper, the concept for something that is both awaitable and observable is
Starting point is 00:41:42 task. And so that's kind of where we're going, is trying to get something that allows the sugar for callbacks and a pure function implementation of callbacks to coexist and interoperate. And this all directly comes back to executors. Yes. Yes. Yes.
Starting point is 00:42:08 We want executors to play well with co-routines and with, uh, other forms of async. Now, I think our understanding here is that the networking TS is waiting on executors. Can you, do you know,
Starting point is 00:42:23 like how the direction that you're trying to push, uh, executors and executors. Can you, do you know, like how the direction that you're trying to push executors in is going to affect networking TS? Yeah. So Chris Kolhoff, the author of the networking TS and ASIO before it is in involved in the executor's effort and has very clear, clearly stated requirements for what he needs from executors as a part of the networking TS. We, of course, want to go a little bit further. One of the recent changes that he made to networking TS was changing the async completion object to have an additional method called initiate. And the reason that was necessary is because all of those APIs that took everything else that was specifically related to, say, read or write. And what you got back was, at best, an eager future in the case where the async token was indicating that you wanted to return a future. So this became a problem for coroutines
Starting point is 00:43:49 and the Rx model because there was no way to get it to return a lazy object. The work had to have already been started with the existing async token model. And so what the initiate function does is it essentially puts the subscribe method into the async completion token so that you will be able to return from the async read an object that has stored all of the parameters other than the continuation and isn't actually queuing that read until you supply the continuation, which you can do either by co-awaiting or by subscribing. Is there any like overhead implication here from doing this in like a multi
Starting point is 00:44:42 like it kind of in a way feels like multi-phase construction but not really yes okay you can definitely think of of this as a currying operation okay oh that makes it sound better just throw functional programming words in and everything sounds better. I bet it has a monad as well. We try not to use the M word. So yeah, basically you're just taking the continuation parameter and you're delaying applying it and you don't start the work until you've applied it okay all right so a moment ago you said that you want coroutines to work well with executors
Starting point is 00:45:32 but we're getting coroutines in c++ 20 and we're not getting executors to 23 hopefully um is that a problem at all like will we be changing the underlying behavior of coroutines? So the mind meld with Lewis has been quite productive this year. Okay. We went in with different positions than we came out with. Both of us did. And so he went in very happy with the way that coroutines worked. And he came out trying to find ways to make sure that we had a path forward for the things that I wanted.
Starting point is 00:46:15 He saw the value in them. really great work in papers that are published and not yet published in trying to get a path for the existing coroutines, which is a really good implementation of the functionality that it provides, to allow us to do the things that we would like to do in the future. And the easiest way to express the largest difference between a callback and a coroutine is to talk about it in terms of overload sets versus return values. So the thing about a coroutine is that it ends up having this method called async result, or await result, await result, that has a single return value that is the result of the coroutine's computation. Okay, like calling get on a future.
Starting point is 00:47:09 Right. Only in this case, you're calling co-await. But yeah, the result of that expression is whatever is returned by this internal function. And the difference between that and a callback model is that I can have an overload set as the callback. And when I have an overload set, I can actually have an optional, because I might not, I might call with no arguments. So I can have an optional expected of variance of tuples. That's what overload sets map to in the value space. Okay. And no one, no one wants to be dealing with coroutines that return an optional expected of variance of tuples. No.
Starting point is 00:48:06 I can just see in my mind that, like, the call syntax for this would be yes. Well, we'll get pattern matching, so that'll all go away. Oh, sure. But you'll essentially be creating your overload set out of pattern matching. Okay. I can actually, yeah, see that. A little bit better that way, yeah. But the real problem with it, in my opinion, after having played with it,
Starting point is 00:48:30 is that you end up with really bad code gen because we don't have built-in variants or tuples in the language. Yeah, and our compilers can optimize, but only so far when it comes to things like variants, particularly. Right. So we're really good, the compilers are really good at saying, oh, I don't have to instantiate this function in this overload set because it doesn't match any of the things being called.
Starting point is 00:48:53 But there's no way for it to do that in the return value from a coroutine. It actually has to implement a variant and tuple for every single possible combination of values, even if that coroutine instance cannot produce that set. Okay.
Starting point is 00:49:10 And then it has to code gen all of that, and you have to take the runtime cost of copying things in and out of that set of nested value types. It turns out to be a lot of overhead. So one of the things that we would like to get to is to explore our options for moving forward with the TS to a world where it supports returning a larger portion, supporting a larger portion of an overlaid set. So imagine a world in which I can co-weight a variant. Okay. Okay. And if you imagine that world, then you have to start thinking in terms of the rest of the function
Starting point is 00:49:50 being turned into a template method that takes a single argument, which is the result, because you won't know what the type of the result is. Right. Like a generic visitor on the result of the... Yep. Go wait. Okay. So do you... Oh, sorry, go ahead. No, I was just gonna say, so yeah, we got a lot of work pending. Do you see this future world of C++23 where the executors, the coroutines, the futures, the reactive things all live in perfect harmony together? I believe that we will be able to get to a world in 23 where what we have works in harmony together. Okay. But I think we're probably going to be getting this in pieces over a couple of releases, at least I hope so.
Starting point is 00:50:39 I'd rather not do something where we had so much to add at once that people just said no because it was too big right make every release a step forward yeah exactly so i want to come up with things that are smaller pieces and get them going iteratively okay sounds good yeah uh well since we you know i've talked about kona on and off throughout this discussion, was there anything else that you were part of at Kona that you wanted to mention that we haven't already talked about? I was actually pretty inspired, interested with the standard audio paper. Okay. I'm very interested in having more things that interact with the real world, and that's definitely where async starts to shine. And having another example of IO that is async that we can use to show
Starting point is 00:51:39 that we have a complete conceptual design for async in C++ is good for the language, and it's good for the implementers of all the IO abstractions. It's kind of funny to me just now that you just said async IO, and you had pointed out, which I had not really thought about, the fact that Boost ASIO, the async IO library, was the basis for the networking TS. Then I did wonder, does the networking TS include generic asynchronous IO at all?
Starting point is 00:52:16 I'm just trying to still think about how all these pieces fit together, because clearly the audio piece fits with the rest of this. Yes. Audio is definitely a, audio is something that definitely will benefit from conceptual approach to designing the abstractions than the typed approach that was taken by the networking TS. And that is because there are hard real-time latency requirements in audio, and they cannot just use any arbitrary type that's implemented, say, an async queue or
Starting point is 00:52:52 something like that. They have very stringent requirements. And so giving them an abstraction like a concept that they can just implement to their requirements gives them a way forward while also being able to share the same teaching and knowledge and and uh other implementations that do happen to work in that space because they're tuned appropriately for it uh you know and share it across all io all right okay well it's been great having you on the show today, Kirk. We started off just talking about RxCPP. I wasn't expecting us to go down this rabbit hole of already talking about C++ 23 when 20,
Starting point is 00:53:35 the ink isn't even dry yet. But it was a great discussion. Thank you. I really appreciated being involved here. Yeah, thanks a lot for coming on. Thanks so much for listening in as we chat about C++. We'd love to hear what you think of the podcast. Please let us know if we're discussing the stuff you're interested in, or if you have a suggestion for a topic, we'd love to hear about that too. You can email all your thoughts to
Starting point is 00:53:56 feedback at cppcast.com. We'd also appreciate if you can like CppCast on Facebook and follow CppCast on Twitter. You can also follow me at Rob W. Irving and Jason at Lefticus on Twitter. We'd also like to thank all our patrons who help support the show through Patreon. If you'd like to support us on Patreon, you can do so at patreon.com slash CppCast. And of course you can find all that info and the show notes on the podcast website at cppcast.com.
Starting point is 00:54:25 Theme music for this episode was provided by podcastthemes.com.

There aren't comments yet for this episode. Click on any sentence in the transcript to leave a comment.