CppCast - Coroutines
Episode Date: July 19, 2017Rob and Jason are joined by Gor Nishanov to talk about the C++ Coroutines proposal. Gor Nishanov is a Principal Software Design Engineer on the Microsoft C++ team. He works on design and stand...ardization of C++ Coroutines, and on asynchronous programming models. Prior to joining C++ team, Gor was working on distributed systems in Windows Clustering team. News Verdigris: Qt without moc Trip report: Summer ISO C++ standards meeting A C++ review community Future Ruminations Gor Nishanov @gornishanov Gor Nishanov's GitHub Links CppCon 2015: Gor Nishanov "C++ Coroutines - a negative overhead abstraction" CppCon 2016: Gor Nishanov "C++ Coroutines: Under the covers" Wandbox: Coroutines with Ranges Compiler Explorer: Coroutines clang demo Sponsors Backtrace Hosts @robwirving @lefticus
Transcript
Discussion (0)
This episode of CppCast is sponsored by Backtrace, the turnkey debugging platform that helps you spend less time debugging and more time building.
Get to the root cause quickly with detailed information at your fingertips.
Start your free trial at backtrace.io slash cppcast.
CppCast is also sponsored by CppCon, the annual week-long face-to-face gathering for the entire C++ community.
Get your ticket today.
Episode 110 of CppCast with guest Gore Nishina recorded July 19th, 2017. In this episode, we talk about some of the news
out of the Toronto ISO C++ meeting.
And we talk to Gore Nishina,
Principal Engineer at Microsoft.
Gore tells us about the C++
Coroutines proposal. to C++ Coaching's Potokal.
Welcome to episode 110 of CppCast, the only podcast for C++ developers by C++ developers.
I'm your host, Rob Irving, joined by my co-host, Jason Turner.
Jason, how are you doing today?
I'm doing pretty good, Rob. How are you doing?
Doing good. How'd your training go?
I think it went well. I got some great feedback, and maybe I'll see which one I'm going to plan to do next in the future here.
Okay, sounds good.
Well, at the top of every episode I'd like to read a piece of feedback and
we got a lot of feedback last week.
So, Jason,
normally we get pretty good
feedback on Twitter and Facebook and
everything and we did get
at least one listener saying, you know, he always
listens to CppCast on his way to brunch
and he said
he enjoyed this week's topic about
cute without what makes cute
bad for C++ devs and said it was
enlightening as hell
but we got some other mixed feedback as well
and I think we kind of stumbled on
a little religious war
with cute devs
there were a couple who
I guess,
were upset that we didn't press the Copper Spice developers
on some of the changes they were making.
And a couple different listeners pointed us to this article
that does a comparison between Qt, Copper Spice,
and this other library that you can use with Qt, which is called
Vertigris. Okay. Yeah, so I'll put a link to that in the show notes. And if you are interested in
reading about another library that works with Qt, it looks like this one changes the way...
It gets rid of mock, but it still
creates the meta object generation
at compile time.
So it handles the mock problem a little bit differently
than Copper's Pice, is my understanding.
So if you're interested
in that, we'll put this link in the show
notes, and maybe
we'll talk more about that topic in the future.
Yeah.
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 cpcast.com.
And don't forget to leave us a review on iTunes.
Joining us today is Gor Nishinov.
Gor is a principal software design engineer on the Microsoft C++ team.
He works on design and standardization of C++ coroutines and on asynchronous programming models.
Prior to joining the C++ team, Gore is working on distributed systems in the Windows clustering team.
Gore, welcome to the show.
Hey guys, great to be with you.
So, you know, I guess, how did you end up on the compiler team?
Well, if I track down what I've been doing, I was always jumping between the languages things and doing
some production at some point, getting frustrated with the tools that I'm using
and going back. So I think initially in Russia, I was on the cross
compiler team that were doing software
compilers partially for space industry.
Then after the Soviet Union fell apart,
I started doing random software,
like, for example,
Ink It Forward WordPerfect.
You know, back in the 90s,
we had pen computers,
and Microsoft had Pen API.
So you can take a WordPerfect document
and then scribble on top of it with your
pen, and then you can
send this augmented document.
Then I went back to
school, RPI,
at Rensselaer Polytechnic Institute
where Alex Stepanov and
Maser collaborated on the STL.
It was all infused with
C++, so in
1996 I thought I learned how to use C++ properly.
So I went back into the product team.
So it was a Windows NT team.
And after applying C++ extensively for 15 years, I got frustrated.
So I decided to go back.
I mean, future, stoop future is what triggered me.
That, like, it's so wrong.
I have to go and start working on that.
Oh, well, you know, now I need to know
if we should wait until the actual interview portion
so you can tell us all the ways that std future is wrong,
or if you want to tell us now mild and calm. But that's what prompted me, at least.
Yeah, the asynchronous programming and tools that are not sufficient enough in C++ that I decided to go and try to help.
Well, I will bring up StudeFuture in a little bit, but I've got to ask, while we're still talking about your bio,
what was it like working in the space industry and compilers involved and that kind of thing?
I was young, I didn't know. I simply was a writing compiler
front-end, was modular, and also worked on the register allocator in our
middle, or close to the
back-end. But then there were people who actually middle or close to the back end.
But then there were people who actually communicated
and were sending the data.
I never interacted directly with the space industry.
Okay.
So what year was that that you were working on that,
if you don't mind?
Early 90s, maybe end of 89, 90, 91.
Okay.
I was just thinking about automatic register allocation
and how the register keyword
has never really been terribly useful in my opinion.
Well.
Was it useful back then in the late 80s, early 90s?
Or were you still just doing what you wanted to do?
I was doing a modular
front-end.
Okay.
So modular, modular, modular.
It did not have
any register keyword.
It's the register allocator
that is doing lowering
after
you're done with the syntactic sugar.
Right. Okay, well, Gore, we've got a couple news articles to talk about. after you're done with the syntactic sugar.
Right.
Okay, well, Gore, we've got a couple news articles to talk about.
We'd love to get your thoughts on these,
and then we'll dig more into coroutines, okay?
Sure.
Okay.
So this first one is Herb Sutter's trip report from the Toronto ISO C++ meeting
that just finished up a couple days ago.
And it sounds like this was a pretty eventful meeting.
The big headline that came out of it is that the concepts TS has been merged
into the draft of C++ 20.
Gore, did you make it to this one?
Oh, yes, yes.
It was a very exciting meeting.
So concept, for example, for a while there was a big
struggle to get them in but people
were disagreeing about some of the
natural
syntax for them and they wanted
to have more to type
and eventually
deciding to
separate
the natural
syntax from the rest of the proposal.
Yeah, and as of now...
It started moving forward,
and Andrew Sutton and Richard Smith and Core
was frantically carving out the proposal,
because I think it's relatively unusual
that such huge change was done on the working paper during the week.
So they carved, then they healed the wounds, they re-reviewed the code,
and it was approved for merging into C++20, or at least the working paper that targets C++20. Then, you know from the article and from
the other reports is that
we got four TSS
out. We got three
techno-specifications,
network and coroutines,
and ranges, and
finally modules moved
into the PDTS stage,
which is preliminary draft
techno-specifications. So a lot of stuff was moved during this moved into the PDTS stage, which is Preliminary Draft Techno Specification.
So a lot of stuff was moved during this meeting.
It was fantastic.
So for our listeners who are familiar with concepts,
if I understand correctly, in the proposal there was three different ways
with which a concept could be introduced,
and now we've removed some of them or something?
Yeah, we removed two.
So essentially, there is a full syntax,
which looks like a template syntax,
but additionally, you can say requires,
and you put concept names in places
where you had type name or a class, right,
in the template introducer.
But additionally, there was
a natural syntax or function-like
syntax where you can simply write a function
and instead of saying
int, you can say a number
or something like
that, and it will
be essentially a template function with
restricted template parameter
to just the number concept. So that was removed.
And then there is another version of introducers where you have some
relationship between the concepts which was
slightly more complicated but still less
cumbersome than the full template syntax. It was also removed.
So we right now have only one,
the biggest one, and then
people agreed that they really want the
short syntax as well. It'll just
take a little bit longer to get there.
Okay, so you think the short syntax
will make it in after it stays in the TS
a bit longer and maybe has some of the language
changed a bit? Oh, I really hope
so. I think people initially
want to have big in-your-face
syntax, but after using it for a while, they get tired of it. They're like, okay, I know it's a
concept. Okay, okay, let me just write it simply. So I think that that will happen. Just from the
usage, people would start using with the heavyweight syntax, and they would want a lighter syntax at some point.
Was there anything else, maybe not related to coroutines,
that you wanted to call out from Toronto that was big news?
Oh, yes. There were a few little things.
One of them I liked was designated initializers.
Have you seen those?
Yeah, I did see that.
Yes.
I was curious about the syntax.
How do you do the dot member name
instead of just member name equals whatever value?
Well, it came from C.
So there were three design goals
for Genesic-leaded initializers.
And one of them improved compatibility with C
because if you look at Linux kernel, it's all
over the place. So everybody's using this
dot field name notation.
So because
of that goal, we went
with exactly the same syntax as
C.
But it is so much better
than C.
Really.
Let's see what you guys think.
So in C, these designators, they're optional.
So you can use on half of your members.
You can say a name,.a,.x.
And then others, you just put numbers or whatever values are. In Z++, you either use
normal initialization
or you use designated initializers.
You cannot do half and half.
So if you
choose to say.x
equals 5, you cannot just say
3, 4,
4y. If you go
that way, you go all the way.
Another cool difference from C is that
in C you can do them in random order. That's one. And second,
evaluation order of the expressions that you put there
is unspecified.
Well, in C++ we're saying
they must be in the order of declarations.
And the order is left to right.
I think it's wonderful.
Okay, okay.
So we're thinking about the fact that objects are going to be initialized in the order that they're declared inside the struct,
regardless of how we did the initialization, designated initializers, right? But you're enforcing it by saying the designated initializers
must be in the same order they're declared, correct?
So, okay, then why don't we go and fix class initializer lists
so that they are required to be in the order that they're declared also?
Well, one step at a time, but I think we put a foot in the door with this one.
So essentially, these initializers,
I really love them in a way how they cleaned up
the same facility which came from C.
And there are other little tweaks
like the one I described, which are very nice.
Okay.
That's interesting. I'm going to have to play with that.
Do you know if any compilers currently support that syntax?
Clang.
Clang does? Okay.
I assumed it had been tested in some compilers already.
Well, yeah, the proposal came with Richard Smith and Chandler
in the list of authors,
so I assume that they actually implemented before proposing
right okay
okay this next thing we have to talk about
is another proposal
coming out of meeting C++
and this is for a C++ review
community and I thought this was
a pretty interesting idea
basically if you
want to you know if you make a library and you want to get some feedback on it, there's a new
subreddit set up, which is cpp underscore review. And you can make a post about your library there.
And this isn't starting until August. We'll have to see how it actually works. But
you'll be able to go on and look at this person's library, submit feedback, and hopefully the library authors get some good feedback.
And at the end of the process,
you'll maybe get a meeting C++ review badge for your library
to show that you were successfully reviewed.
I thought that was a neat idea.
Yeah, I agree it's a neat idea
because the boost process is big and difficult.
Not everyone wants to be a part of Boost.
So just having some way to say that, yes, this library meets some minimum set of standards or something, it's a good idea.
Yeah, and not every library should be a part of Boost.
You might just have a very small library that just doesn't fit in there.
Or a very big library that you don't want to
try to go through the effort.
Right.
Well, I'm wondering how people
will be able to donate
time to do those
things. For example,
in open source community like Clan,
you do it by barter, right?
You review somebody else's
patches they review yours or maybe you bribe them with gifts and other offerings or something like
that uh but uh since uh since this certification process is not geared to any particular project
uh i am just curious how it will work out i I mean, it's a great idea. I just,
I want to see how it goes. Sure. Right. Yeah. Hopefully some people are interested in,
you know, giving their time to it. Okay. And then we were talking about futures earlier.
Sean Parent wrote this article on, titled Future Ruminations. And he's kind of going into a deep dive about his thoughts
about futures and the future of futures.
And since we talked with you, Gore,
about how that's how you got involved in asynchronous programming,
I was wondering if you had any thoughts on this.
Oh, it's a wonderful article.
There are a lot of good things there
about cleaning up the interface
to the future,
treating it as the handle to work
and consolation, which is very important.
But I'm not sure
whether futures
will be something that we will
be actually using in the future.
So I do not see the future for the future.
But I'm trying to avoid puns, but they just happen.
With the future, it's very easy.
But essentially, you can have several ways of writing your asynchronous code.
One is using callbacks, right? And that is not very convenient, at least for some people. They hate writing state machines.
Others probably would use coroutines, which are the same state machine simply written in an imperative fashion. So if you use coroutines, you don't
need full functional futures. You just need some kind of ability to explain what happens
after you resume the coroutine, and we can talk about it later. So composition of your
code using future with dot, and other operations,
I'm not sure there will be use cases for that.
That's some pretty strong words.
Well, yes, because there is a much more powerful construct like a reactive string.
I'm not sure whether you have seen RxCPP or reactive extensions in Java, JavaScript.
But essentially, those are streams of values
that appear in time.
So imagine the timeline, right, going to the right.
And then you have zero or more values
appearing at some point in time.
And then in the end, your sequence either never terminates,
so values keep coming, or it terminates with an error,
or it terminates because there is no more things.
So for those reactive streams, there is algebra.
There are hundreds of operations,
and you have extremely expressive declarative
programming model where you're describing what you want to do and you can see you
know programs which is very difficult to write normally takes five six lines to
write with those reactive strings so I think that this will be the two extremes
for people who want to write declaratively, reactive streams would be something that people would compose in a similar way how ranges TS works.
With ranges, you have those little combiners and you compose multiple actions that transform your streams or iterables.
And in the end, you get something else.
But it's a nice, it's a high-level description.
So you don't deal with implementation details.
You just say how you compose those operations.
And similarly, with reactive asynchronous streams,
there are actions you can do, and there are a lot of them.
So I think in the future, we will see those two extremes.
If you want simply to write
as in code and express your code procedurally
as the
procedural function
do that, do that.
If something else, do this or else
and then sometimes wait
on a bunch of activities and then do that
so that will be expressed like a normal function
but with co-await
as the coroutine.
And on the other end,
we will have declarative programming with reactive stream.
Future just not expressive enough.
It is a workaround for the absence of coroutines.
So I do not think that futures will be something that we will be using.
So the reactive streams, are they dependent on coroutines
also, or is this a completely separate proposal?
It's a completely separate proposal, and they can
interact with coroutines, but they're independent.
So usually a coroutine would be on the consuming side.
For example, in coroutines we have for a wait statement, which allows you to iterate on a synchronous stream.
So that was put there with the noise that reactive streams will come.
So the coroutine could be on the consumption side of the reactive
stream or on production side
of the reactive stream.
Or you can even have an element where you
co-await something and then yield.
So you
will talk about that maybe
in the coroutine part of the discussion.
But you can
mix a weight and yield in
the same coroutine. And the result is a synchronous stream
because we can suspend and then yield values,
but in a synchronous fashion.
We do not do it in a block in a synchronous fashion.
So coroutines will interact with those asynchronous streams,
but those two programming models, procedural and declaratives,
they are both very useful
because they can express different things
in a nice, concise fashion.
And those are the two things that will, I think, remain.
I'm not sure what is the future for the future.
Again, no pun intended.
It's just that, how else would you say it?
Right.
Okay, so since we're talking about
await and yield and everything,
for listeners who haven't heard
or aren't too familiar with the coroutines proposal,
could you give us an overview?
Sure.
So at the high level,
coroutines are functions
which get two more operations.
So normal functions,
they have two operations,
call and return.
That's all you can do. There is a call syntax, you know,
f, parens, and the new pass some parameters, and then inside of the function body, you have a return statement that
you can provide a value to return back to whomever called you.
So coroutines get two more. They have a point
where a function execution can be suspended
and control returns back to its caller.
And then there is a way to resume the coroutine.
And how you resume it depends on the semantic of the coroutine.
So in C++, unlike in other languages,
we decided to make coroutines open-ended.
The language does not define
the semantic. I mean, eventually
in the library we will get coroutine
types that have some
defined semantic, but
as it stands today, coroutines
are just a language facility
which allows you to annotate
where in the function
you can suspend
and give
an ability, a glue,
to add in library abstractions
that will explain what happens
when coroutine is suspended and resumed.
For example, the simplest one would be a generator.
It's a coroutine which produces
potentially infinite lazy
sequence. So from the outside
it looks like an iterable.
So you say generator of
int, f, and then whatever is
inside. So
you will use it the same way
like you use any
iterable that has begin and end.
You can go to your range-based
for, you can give
it to any standard algorithm, which takes
begin and end. With
ranges, it's even easier. You just pass
this coroutine, which is an input range,
to any algorithm which expects
range. And then, whenever
your algorithm does plus-plus
on an iterator, it
jumps back into the coroutine
and continues from the point where it was stopped.
So it is a way to write a state machine,
but in a procedural fashion.
So you write your code and say,
co-yield some expression.
When you reach co-yield expression,
that value that is being yielded
will become available to an iterator which
consumes the result of the coroutine at star, at the reference iteration on the iterator.
And when you do plus plus, you jump back into the coroutine right at the point after it
was yielding something.
And it'll continue.
So this is...
I'm sorry, I thought there was some question.
No, go ahead.
Okay.
And this is similar to what we have in Python,
I think now in JavaScript, in C Sharp, in Dart,
and in many other languages.
So it's not unique in that respect.
What is unique in C++ is how efficient they are
and that they are fully open.
You decide what does it mean to yield.
We don't have a prescribed way, prescribed generator.
You can plug in your own if you dislike what we offer in the standard
and right now just to give an ability to people to experiment
and come up with the awesome generators
that we will standardize in the future
we don't even offer you a generator
but it takes only 15 lines to write
so maybe 50 if you include the iterators
and a lot of comments.
So this is one use case.
Another would be an asynchronous task.
So it's before you use callbacks
or maybe future.then is that you are saying,
I'm going to call the sync API.
And when it continues,
and when, for example, I'm trying to connect to a socket.
And once the connection is established, you will resume the coroutine from that point.
So it allows you to write code as if it was synchronous.
The only exception is that at those points where your synchronous code would block,
you would use co-await to signify that you want, at this point,
to suspend the coroutine.
And then, in co-await mechanics,
there is a way to explain to the compiler
how to resume the coroutine once the task is finished.
And then, so far, we looked at two different cases.
A simple asynchronous task and a generator.
But you can mix and match, because it's you who defined the semantic of those.
So you can say, if your function contains both yields and a weight,
then the abstraction it represents would be an asynchronous stream,
which can produce values, but it can suspend in between producing the values.
Interesting.
I wanted to interrupt this discussion for just a moment to bring you a word from our sponsors.
Backtrace is a debugging platform that improves software quality, reliability, and support
by bringing deep introspection and automation throughout the software error lifecycle.
Spend less time debugging and reduce your mean time to resolution
by using the first and only platform to combine symbolic debugging, error aggregation, and state analysis.
At the time of error, Bactres jumps into action, capturing detailed dumps of application and environmental state.
Bactres then performs automated analysis on process memory and executable code to classify errors and highlight important signals such as heap corruption, malware, and much more. This data is aggregated and archived in a centralized object store, providing your team
a single system to investigate errors across your environments. Join industry leaders like Fastly,
Message Systems, and AppNexus that use Backtrace to modernize their debugging infrastructure.
It's free to try, minutes to set up, fully featured with no commitment necessary.
Check them out at backtrace.io slash cppcast.
Now, I want to clear up something, if that's all right.
You've mentioned ranges several times,
and I think that you're saying, like,
just a normal begin-end iterator pair like we're used to currently,
but how does this interact with the ranges TS proposal?
I mean, the ranges TS that's presumably coming up also.
It interacts wonderfully.
Casey Carter written adapters, which allows to plug in coroutines into ranges.
And it's even available on a wand box.
So I can send you a link so you can see how Coroutine
plays with ranges. Because
I think in classification that
Casey did,
Coroutine is an input
range,
because you can only
read from it. You can never go
back.
And essentially, whichever
algorithm can work over the input
ranges, you can
give to that algorithm the coroutine
itself. And then
the algorithms would
pull the values out of the range, and
every time you pull, you jump inside
of the coroutine and continue
from the latest yield point.
So we'll definitely have to
share that link in the show notes
if you can give us one.
Yeah, sure thing.
Definitely something our listeners would want to see.
So what's the current status of the coroutines proposal?
Was there any big news for coroutines out of Toronto?
Well, it was published as a technical specification.
We have now two implementations,
one in Clang, another
in MSVC. And Clang we missed 4.1 by just, I think, a week. So it will be in whatever
the release comes after 4.1. But hey, if you can build your clang from trunk, go ahead.
And Eric Fizili here already
added the library support, so
we are complete there.
You need to grab both
the clang and
the library that comes with it.
But if you don't want
a library, that's
fine too, because
it's just one little file that is required for coroutines to work.
So there is this little file that defines the glue which links language support with coroutine abstractions that you want to provide.
And it's a very small file which links those two together.
You can just cut and paste it. You don't have to even have an entire library.
So have you been involved in both Visual Studio
and the Clang implementations of coroutines?
Yes.
Okay.
Yes.
Especially in Clang and all of EM
because there, yes, I did probably most of the work.
In MSVC, I had help.
Okay.
I thought I had heard that in LLBM.
Hold on.
Richard Smith provided initial implementation of the parsing of the coroutines,
but then he got busy with something else,
so I plugged in the back end
it's where the heart of the coroutine is
and added whatever was missing
in the front end to work
with the back end implementation
and the good part is that now LLVM itself has coroutines
which is in the back end
thus people in Rust and in Python and other languages
which target LLVM IR can get the same benefit
or as efficient coroutines within reason in their language as C++.
Yeah, that's what I thought that I had heard,
and you just confirmed that it's actually an LLVM IR thing.
It's primitive in LLVM, I guess, is to coroutine.
Yes.
So there is a bunch of intrinsics,
and you can describe your coroutine in pure LLVM,
and then LLVM will actually turn your function into a coroutine.
And the reasoning behind is that we wanted
coroutines to be very efficient.
And initially I thought
I could completely implement
coroutines in Clang because it has this
wonderful AST and you can party
on it and rewrite, change
it, but
after some experimentation, we
ended up roughly
with the same decomposition into backend and frontend as in MSVC,
where coroutine is fully represented in LLVM IR as a function,
and it travels through the optimization pipeline as a function.
And the reason for that is optimizers are very good at optimizing functions.
They are not very good at optimizing state machines.
So essentially, we describe coroutine as a normal function.
It is optimized like a normal function.
But just before it is eligible for inlining into its colors,
then we do the transformation of a coroutine into a state machine so we delayed as far as we could
the transformation so that we get
most inline optimization
opportunities as possible
Can you speak to how efficiently MSVC is also able
to do this?
I am not going to talk about that.
Because, well, the reason is
our initial implementation of coroutines in MSVC,
the purpose was to verify the glue,
to verify the model.
So we focused just to make it work.
So that was the prototype, and Clang got all of
the benefit of knowing how the original
implementation was done.
No,
it's not a prototype.
It's a production
version. It's just that we optimized
for different things.
We were trying
to prove the overall design,
but it's still usable.
It's just not as efficient as we would like to yet.
But in LLVM, we went into a different approach.
We just said, okay, let's see,
can we get it optimized the way we like?
And that was the focus
because the rest of the coroutine design
was already verified and embedded in MSVC,
so we focused just purely on the optimization opportunities.
Okay, so we've brought up this optimization
and all this several times now.
So how much overhead is there in using coroutines
and in adding the subtraction?
Depending on the scenario, it could be zero.
For example, in CppCon last year, I showed that generator optimizes into a constant.
If you are iterating over generator over a fixed range.
And even though, in general case, coroutines involve heap allocation,
for common cases we eliminate those heap allocations. So the rule is very simple.
As long as the lifetime of a coroutine is fully enclosed in the lifetime of its caller, we place the
state of the coroutine onto the caller's frame.
So thus, if you have a generator, you know, 4x over some kind of fancy generator, then
you loop over it.
That entire coroutine is purely on the stack.
If you use structured programming, and by that I mean that you break your big function into smaller functions,
even though you don't even call them more than once, it's the same thing.
Now we're going with asynchronous tasks. So if your asynchronous task is broken into subtasks,
which are broken into further subtasks,
and you simply call a wait on that subtask,
again, lifetime of the subtask is fully enclosed in the lifetime of a task.
So it becomes simply a state,
it's is local variable
on the caller side
and then all of the inlining
works like normal
so for a whole
bunch of scenarios we are
making them zero overhead
okay
since you have coroutines
in MSVC now
do you know if anyone's internally using coroutines in MSVC now,
do you know if anyone's internally using coroutines?
Yes. Has it changed how they're programming?
Are programmers at Microsoft really enjoying them?
Yes, yes, yes.
We had several teams using them in production.
We also have external developers using coroutines, for example.
You know Tidal?
There is a music service Tidal.
Some kind of music streaming something.
And the unofficial Tidal streaming app is written with coroutines.
And you can see on GitHub and search for Coia Wait, and you will find them.
And inside, we have several teams using the coroutines and also modern CPP.
I think you talked with Kenny Kerr some time ago.
Well, it's all based on coroutines.
Not only on the use side, but also on the production side.
So to generate the projection, he uses generators.
But on the consumption side, when people write async code,
well, entire WinRT is asynchronous.
So without coil weight, it's extremely painful to work with it.
So it does sound fair to say
that this is proven and
you're using it.
And so are you expecting
that we'll see it merged into
C++20 like
Concepts was soon?
We have merged
criterias and one of the
part of the merged criteria, one
implementation not tainted by me.
So one coroutine implementation,
which is done from the technical specification wording
as opposed to, you know, from talking to Gore,
what does it mean?
And to a degree, EDG might count as that Edison design group.
But ideally,
we would love GCC to implement it too
so we can compare
and see who is faster.
That, I think,
is an overlooked...
We have the tendency to complain
about not all the compilers being up to conformance
in particular categories,
whatever. But I think it's one
of the key strengths of C++, personally, that people complain about I think it's one of the key strengths
of C++ personally
that people complain about
without realizing it's a strength
that having these competing implementations
verifies that the spec makes sense
and verifies that we have solid compilers.
Right.
So I think to streamline TS process,
last meeting in Kona,
there was a discussion of having a merge criteria.
And once merge criteria are met,
it's nearly automatic merge process.
Not sure whether it will work in this way,
but maybe.
So in the case of a coroutine, criterias are one independent implementation,
a bunch of tests so that people can verify implementation against the tests, and adapters
so that whatever is already in the working paper interacts nicely with coroutines. So
if we have future.then in the working paper of the standard,
then we'll have adapters that will work with coroutines.
And we will throw in a generator just in case.
Does the spec make any overhead guarantees?
Or maybe to phrase it a different way,
is the test suite that you're working on
going to have anything that tests the overhead of the implementation?
I think we will add one just for the kicks
because it is a bragging right.
We cannot demand it to be that fast or that good,
but it would be fun to compare compilers.
Right.
It strikes me as one of the few core language features
that at least because,
maybe just because I have a hard time
wrapping my mind around what exactly it's doing,
that is difficult to actually demonstrate
why it's a zero overhead.
If that makes sense.
Well, just go to Godbolt and see what's going on.
Well, yes, always can do that.
Yes.
You've mentioned the coroutine-specific keywords a couple times,
like co-wait and co-yield.
Is the co- underscore going to be the final language?
Because I know if we watch one of your videos from two years ago at CppCon, you were just saying await and yield.
Yes, I fought valiantly.
But I couldn't.
It essentially started with yield.
Yield was the problematic keyword.
Okay.
And some people felt that return is too confusing.
And then for consistency, how about we'll add core everywhere?
And now there is actually a benefit of having a core return
because unlike other languages like C Sharp, Dart,
and others that have similar await and yield keywords,
they all require some kind of tag on a function.
For example, in C Sharp, it's async.
In Dart, it's sync star.
In Rust, they're talking about fn star or something like that. So that allows them to give a different
meaning to await or yield keyword within the coroutine because they know the
context. In C++ coroutine is just a function that has correturn, co-yield or
co-await. You don't have anything extra on the function signature or a function definition.
And that is part of the design
because we wanted to have seamless interaction
with the rest of the language.
We did not want to have a different corin convention
or change the type system.
So from the outside, coroutine is a normal function.
It is undistinguishable from any other function.
It's only the implementation.
Oh yeah, yes. For example,
assume that
dot then in some
form will appear in the future.
In the future.
And
your function says
std future of int
f blah
semicolon.
You have no idea whether it's
simply rendered as a coroutine or
as a normal
traditional function. It's simply
implementation detail. You can flip back
and forth.
So why did I go there?
Oh, I know, I know, I know. I was
going for explanation
why correturn is actually useful.
Sometimes you just want to write
a do-nothing coroutine, essentially,
that it never suspends.
Maybe you just started typing it.
So you can say correturn semicolon,
and boom, your function is a coroutine.
Or maybe correturn space 42. So, thus, function is a coroutine. Or maybe correturn space 42.
So
thus you have a coroutine
and it doesn't require extra tag.
So that particular usage of
correturn made me less
annoyed with co underscore.
Okay, so what
is the difference between
a function that always returns
the same value and a coroutine that always returns the same value?
I mean, like, if you just did co-return 42, like you were just saying.
Well, the function will be identical.
Because coroutine is simply, no, that way around.
Function is a special case of coroutine.
Okay.
So coroutine can do everything that a function can do,
but better.
It can do more things.
But when it is doing exactly the same thing as a function,
it is a function.
Okay.
And that is something that I am trying to,
let me see, break consensus.
So coroutines were absolutely awesome
60 years ago, in 58,
because they were understood
as a better function, as
more general
form of a function. But then
after our goal 60,
stacks came around, and
block-scoped variable came around.
And those were hard to implement
without extensive compiler support.
So people took an easy route.
They implemented coroutines on top of user-mode scheduled threads.
And that is a much heavier abstraction than a function.
And at some point, people start calling user-mode scheduled threads or fibers.
They start calling them coroutines.
Then they start disambiguating them with, oh, it's a stackful coroutine,
and whatever is not a thread, it is a stackless coroutine.
But essentially, those are two different things with very different costs of usage costs.
And right now with compiler-supported coroutines,
we're bringing those good coroutines of the old,
which are generalization of functions back. And usage of coroutines as a cheaper thread is less.
Okay, so if
you today, when you're writing code, do you just
use coroutine,
coreturn, you know,
like, do you take
advantage of the fact that coroutines are
a better function and just use them
everywhere?
No, because usually because there is
potential suspension involved,
you
probably have a different type
that we use normally.
So if it's a generator,
you likely use some generator type
and you want to yield.
So
if it's future,
if you used to write a function
that returns a future,
you can switch to using coroutines trivially.
But if your function never suspends,
why do you want to return a future of int
or some equivalent of future of int
as opposed to plain int?
Right, okay.
You were talking about threads a moment ago.
Is there any interesting interactions
between coroutines and threads that we should know about?
Well, if you implement an asynchronous coroutine,
the one with square weight,
which is trying to represent some async activity,
well, it's your choice or the choice of the library writer
whether you will be resumed on the same thread some machine activity, well, it's your choice or the choice of the library writer whether
you will be resumed on the same thread as the one that got suspended or a new thread.
And you have to be careful there.
And some people prefer to be resumed on the same thread and that can be encoded in the
await adapters, which tells the compiler what to do when await happens.
But it might be surprising.
TLS is one of the things which might switch when you go to a different thread.
So if you read something from TLS, when you resume slightly later, you will be reading
from a TLS for a different thread.
So for our listeners who are familiar, you're referring to thread local storage.
Yes. So you're saying if you have a thread local object in your
coroutine, you access that, somehow return it, whatever,
and then the next time your coroutine is resumed, it's from a different thread,
you're going to now have a different local state that you weren't expecting to see, effectively.
Yes, you will be consuming
the thread local storage from
whatever thread is currently running.
Interesting.
Which is actually wonderful.
Which is wonderful, because
let me tell you what happens
when it's the other way around.
So, like,
you know there are fibers, there is
boost coroutine,
so compilers, in some architectures, thread local access is expensive.
So they would go and cache an address of a TLS page in some of the registers,
which are preserved between function calls. And then imagine that
you suspended
that function
in a stackful manner, which essentially
means you have no idea that you
got suspended, because some function
10 levels down
didn't suspend.
And then you resume the coroutine
on a different thread.
So now that register points potentially into some other memory.
So it is actually very dangerous not to do what happens in the C++ coroutines with regard to thread local storage.
Right.
So essentially with things like boost coroutine, if you resume your stackful coroutine on a different thread,
it is undefined behavior if you touch thread local.
Like you should not touch thread local at all.
Okay.
So I just want to make sure I understood one thing.
If you're running a GUI application and you call a coroutine
and you want to make sure
you resume on the UI thread, is that something
you can do? Yes.
And what does that calling syntax
look like?
It's all the same. It's call await
on something. And since
it is the library that
describes to the compiler
what happens when you call await,
you can do things like you capture the current execution context,
which threads you are running,
and then when you resume the coroutine,
you will simply say resume on that thread.
But it depends on your environment.
So coroutines were made to work with arbitrary runtimes.
So as long as you can express that in your runtime,
you can teach coroutines to interact gracefully with it.
So coroutines come naked.
It's just a compiler feature.
But by design, it was meant to plug in
to whatever asynchronous runtime you have
or UI runtime.
So do you think that coroutines are going to fundamentally change
the way we interact with C++?
Only if you do async stuff, maybe UI.
And it will be easier to write iterables.
So, like lazy sequences, it's another use case.
But it's mostly, I think, those two.
Asynchronous programming and lazy sequences.
But otherwise, you'll be doing exactly the same way like before.
Well, maybe one more.
Coroutines also allow you to write clean code without exceptions, because
every await point can be customized to work like if error go to end. So, for example,
if you have expected of t type, which expressed either a value or an error, you can make so
that when you co-await on expected of t, if it contains a value make so that when you co-await unexpected of t,
if it contains a value,
the result of the co-await expression
will be just t,
whatever that value is. But if
it is an error, it will propagate
as the
return value of the entire function,
of the entire coroutine.
So this is another use case.
Interesting. Yeah, it was, I think, of entire core routine. So this is another use case.
Yeah, it was, I think,
we had five design goals.
Scalable, efficient,
interact with C gracefully,
open-ended, meaning customizable to whatever on time.
And the last one was exceptions are optional.
Interesting.
Definitely some people who would be interested in that.
Well, like driver writers
we didn't want to make
coroutines tied to the
exceptions in any way
we can interact with exceptions just fine
but you don't have to
and because of that property you can actually
express something like
exception like nice
clean control flow
because all of the error
propagation will be handled
under the covers by the library
machinery that you explain
to the compiler what happens
when you go and wait on expected of T
or maybe an optional of T
or whatever you want.
Okay.
Well, Gor, I think we may have gone over everything.
Is there anything else you wanted to bring up
before we let you go?
Well, one thing.
Rob is actually doing the intro to the CppCast live
because I thought it's always the recording
because it was so, so, so identical every time.
And now I'm seeing it live.
It is incredible.
Well, Gore, thank you so much for your time today.
It's been great having you on.
Thank you, guys.
Thanks for joining us.
Thanks so much for listening in as we chat about C++.
I'd love to hear what you think of the podcast.
Please let me know if we're discussing the stuff you're interested in. Or if you have a suggestion for a topic, I'd love to hear what you think of the podcast. Please let me know if we're discussing the stuff you're interested in.
Or if you have a suggestion for a topic, I'd love to hear about that too.
You can email all your thoughts to feedback at cppcast.com.
I'd also appreciate if you like CppCast on Facebook and follow CppCast on Twitter.
You can also follow me at Rob W. Irving and Jason at Leftkiss on Twitter.
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
is provided by podcastthemes.com.