CppCast - RxCpp and Executors
Episode Date: March 7, 2019Rob 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)
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
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.
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
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.
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.
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
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.
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?
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.
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.
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.
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,
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,
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
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.
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?
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
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.
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.
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,
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,
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.
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.
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.
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.
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.
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.
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.
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,
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
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
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.
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
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
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
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,
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.
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.
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,
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
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?
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.
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
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,
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.
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,
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.
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,
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.
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.
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
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
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
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.
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,
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,
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.
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.
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.
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,
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.
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
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.
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,
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
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
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
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.
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.
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.
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,
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.
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.
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
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.
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
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?
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
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,
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
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.
Theme music for this episode was provided by podcastthemes.com.