News for hackers. Stuff that matters.
Re:Await in Rust by naasking on Monday August 12, 2019 @11:13AM (#20676130)
> Synchronous or asynchronous is something up to the caller to the decide – not to the function itself.

async/await aren't strictly necessary, but to avoid them you need one of:

1. a sufficiently smart compiler with whole program compilation, or

2. to compile two versions of every function, an async variant and a sync variant, or

3. every async wait captures the whole stack, thus wasting a lot of memory.

Async/await is basically a new sort of calling convention, where the program doesn't run in direct style but in continuation-passing style. This permits massive concurrency scaling with little memory overhead, but there other tradeoffs as per above.

Async/await makes perfect sense for Rust which wants to provide zero-overhead abstractions.

Re:Await in Rust by pron on Monday August 12, 2019 @01:30PM (#20677712)
None of the things you mention is necessary. In fact, we're adding user-mode threads (based on delimited continuations) to the JVM without doing any of them. Remember that every subroutine must already be compiled into a state machine or you wouldn't be able to return to it. I.e., every subroutine is already compiled in just the right way to be suspended already. The reason some languages run into problems are:

1. They may not have access to the representation the subroutines store their state, as it may be platform dependent (e.g. if you're targeting LLVM you may not have access to that representation).

2. They may not track their pointers, and so find it hard to move chunks of stack around if they have internal pointers into the stack.

3. They may want to enable some specific optimizations in the special (and rare, especially when IO is involved) case of a monomorphic calls to shallow coroutines. This requires that the representation of the subroutine's state be available as a high-level node for the compiler to change.

Re:Await in Rust by AaronFriel on Monday August 12, 2019 @01:35PM (#20677773)
The JVM doesn't ever lose access to the bytecode, but Rust compiles down to machine code. The garbage collector and heap-oriented memory allocation without borrowing means you don't have nearly as much "stack" to worry about, and copying references is always OK.

For a statically compiled language that produces machine code and prefers stack allocation and has strict constraints on copying references, it is not so easy.

Re:Await in Rust by pron on Monday August 12, 2019 @01:37PM (#20677801)
Bytecode is irrelevant, but yeah, as I wrote, internal pointers into the stack do make moving stacks around difficult.
Re:Await in Rust by Rusky on Monday August 12, 2019 @01:55PM (#20678035)
Reusing the stack frame layout for a continuation state block is a really neat approach!

Internal pointers into the stack aren't really the reason Rust can't take this approach either, though, unfortunately. Async frames are "pinned" once they start running anyway so the ecosystem is designed around not needing to move them. Async callee frames are, in a sense, contained in their caller's frames- this makes async recursion tricky, but in exchange each static call graph uses a single allocation.

The real issue is basically naasking's #1- not that Rust loses its IR or that it's platform dependent, but that LLVM simply doesn't have the infrastructure to work with stack frames directly. A sufficiently motivated+funded team could add it but Rust doesn't have that luxury.

In fact, this is also the reason that C++20 coroutines individually heap-allocate their frames. No compiler had the infrastructure or was able to put in the work in time. There are also some thorny problems around phase ordering here- stack frame layout typically isn't determined until very very late (to enable optimizations) but program-accessible types in both C++ and Rust need to know their sizes very very early. Java can sidestep this by being slightly higher level.

Re:Await in Rust by pron on Monday August 12, 2019 @01:57PM (#20678056)
Java sidesteps this because we generate concrete machine instructions and have control over the low-level platform-specific representation. In other words, because our backend is lower level, not higher level. But yeah, if you don't have access to the low level representation -- like Rust and Kotlin -- you don't really have a choice except to wait for your backend to give you that access. Sometimes waiting is a good approach, though. I'm not even sure you need control over the stack representation; it's sufficient to know what it is, which you could maybe do even on top of LLVM (but I don't know LLVM well enough to say for sure).
Re:Await in Rust by Rusky on Monday August 12, 2019 @02:09PM (#20678181)
Yeah, exactly. I believe you're correct that you just need to know what the stack looks like, not really change it much if at all. Any tweaks you may need to make are probably already available for things like split stacks or green threads.

The problem is that LLVM doesn't even let you see the stack layout until very late, and then only as debug info. You could probably hack something on top but it would probably wind up looking a lot like C++20, with a lot of extra allocation, or like Rust, with all the stack frame layout optimization duplicated into the frontend.

Re:Await in Rust by AaronFriel on Monday August 12, 2019 @01:47PM (#20677927)
It isn't irrelevant for point #2 above yours, which is that the JVM could rewrite and run/JIT two versions of every function. The JVM monomorphizes plenty already, does it not?
Re:Await in Rust by pron on Monday August 12, 2019 @01:49PM (#20677957)
It could compile two versions of every function on demand, but there's no need to (although it could be an implementation choice). AFAIK, every compiler in the world already compiles functions into state machines that are suitable for delimited continuations. What would the other version be for?
Re:Await in Rust by naasking on Monday August 12, 2019 @03:28PM (#20678994)
I'm not convinced they are suitable as-is in all compilers. Continuation capture can only be done at safe points similar to GC. The compiler and runtime need tight integration, which isn't a property of every compiler in the world. LLVM took a long time to integrate intrinsics to handle GC, for example.
Re:Await in Rust by pron on Monday August 12, 2019 @03:31PM (#20679024)
"Physical" (i.e. non-inlined) subroutine calls are normally such safepoints (although I probably exaggerated when I said "every compiler in the world"). They need to be because a subroutine is a continuation. Upon return, it must be able to restore its state, otherwise it wouldn't be a subroutine. If you're doing any IO, there must such be such a physical call (and a safepoint) precisely where you need it -- at or before the actual IO.
Re:Await in Rust by comex on Monday August 12, 2019 @07:42PM (#20681337)
It depends what you mean by "continuation capture".

If you just want to suspend some code, subroutine calls will do – but so will any other instruction, as long as you save the values of all registers. That's what the kernel does, after all. Using the subroutine call boundary lets you save a bit of work by saving only the callee-save registers, but it doesn't make that much difference. The problem is that you still need a stack, which prevents coroutines from being lightweight enough.

If you want to move the stack, like Go does... then just like with a GC, you need to track which registers and which stack offsets contain pointers at a given point in the program. That does require some sort of safepoint, which could be a subroutine call. But that's all irrelevant from Rust's perspective, because Rust's low-level pointer model is fundamentally incompatible with moving the stack. In Go, pointers into the stack are themselves only stored on the stack and in registers – never on the heap – so the runtime can easily find them all and adjust them. If you could store pointers into the stack onto the heap, it might still be possible to adjust them, albeit at higher cost, if you had a GC-managed heap where the runtime knows where all the pointers are. But Rust doesn't even have that. Tracking all the pointers in the heap is impossible for multiple reasons:

- Rust programs don't have to use any particular runtime-provided heap; they can ask the OS for memory directly and store pointers there.

- Pointers aren't necessarily stored in an easy-to-maintain way: e.g. Rust supports tagged unions, where the interpretation of one word in memory as either a pointer or integer depends on the value of another word.

Oh, and Rust lets you convert pointers to integers (useful for hashing purposes), so moving pointers behind a program's back would have visible side effects in any case.

So moving the stack is out. That leaves two possibilities:

- Segmented stacks;

- Calculating the required stack space statically.

LLVM can generate code with segmented stack support [1], and Rust used to enable that option. It's not that bad, but it does require every function to begin by checking whether there's enough space left for its stack, and call a runtime function (__morestack) if not. That adds a measurable overhead to function calls. It also requires using a custom thread-local storage slot to track the current stack base, which means you can only call the function on threads where the runtime set up that slot, not any random C thread. That's a problem if you're writing a library meant to be used by C code, which evolved into a major use case for Rust. For these reasons, Rust nixed segmented stack support sometime before 1.0. Re-enabling it is one hypothetical reason why you might want two versions of each function.

As for calculating the required stack space statically, that wouldn't require an alternate code generation mode. But as you were discussing in another subthread, the design of most compiler pipelines makes it very difficult to expose that information to the frontend. Even if that weren't a problem, merely making it theoretically possible to statically calculate stack usage requires imposing severe limits on the program. You can't make dynamic calls where you don't know the callee, and you can't use recursion at all (because then you might need infinite space). And if only a subset of code is async-compatible... then you end up with the equivalent of colored functions anyway. So even if LLVM's pipeline could be wrangled into doing what's necessary, it would only be a small improvement over the higher-level async/await implementation that Rust currently has. (As an alternative, you might be able to create a sort of hybrid mode where only dynamic and recursive calls use segmented stacks, but that would still require an alternate code generation mode.)

The nail in the coffin is that Rust also targets WebAssembly, where you can't do any of the nice low-level things that you can on real machines. This has also been a blocker for guaranteed tail call elimination. I hate it.

[1] https://llvm.org/docs/SegmentedStacks.html

Re:Await in Rust by pron on Monday August 12, 2019 @07:57PM (#20681433)
> The problem is that you still need a stack, which prevents coroutines from being lightweight enough.

Async/await also needs the same logical stack, it just captures it frame by frame. Continuations can do the same. But as you say, Rust has some particular constraints that make this hard.

> then just like with a GC, you need to track which registers and which stack offsets contain pointers at a given point in the program

Well, you don't need to track all the pointers, just pointers into the stack.

> ... The nail in the coffin is that Rust also targets WebAssembly ...

But yeah, I mentioned some reasons for why it could be hard for some languages -- internal stack pointers and lack of control over the backend are two of them.

But I find it problematic that backend problems force feature design today. If a language wants to be popular 20 years from now it needs to have the patience not to bend to its backends' limitations, particularly as it has few competitors in its niche. So if a language's designers are absolutely certain that async/await is the right design for them for the next few decades, then that's great, but if they decide they want to design a language for the next few decades based on the limitations of WebAssembly in 2019, then that's something I like less. Sometimes there's no choice and a language/feature has to ship, but this is a feature that the world can and has lived without.

Re:Await in Rust by pcwalton on Monday August 12, 2019 @05:49PM (#20680412)
We have that implemented in Rust too. It's a user-mode thread implementation called mioco. It's not used very much because the performance tends to be worse than 1:1 threads.

This is a lesson that we all learned during the NPTL/NGPT era [1] and have subsequently forgotten and will have to relearn.

[1]: https://akkadia.org/drepper/nptl-design.pdf

Re:Await in Rust by pron on Monday August 12, 2019 @06:15PM (#20680640)
So it's better to bet on something whose lesson is yet to be learned? :)

Anyway, I don't think anyone has forgotten those lessons, but they're not very relevant to modern managed runtimes. (also mioco seems to be single-threaded)

Re:Await in Rust by pcwalton on Monday August 12, 2019 @06:56PM (#20680987)
> So it's better to bet on something whose lesson is yet to be learned? :)

Async I/O isn't anything new, and it's stood the test of time. Nginx isn't some new experimental technology.

> Anyway, I don't think anyone has forgotten those lessons, but they're not very relevant to modern managed runtimes. (also mioco seems to be single-threaded)

They're still very relevant. Linux can spawn hundreds of thousands of kernel threads.

Anyway, we used to have an multithreaded M:N runtime in Rust, but it was removed because the performance was worse than 1:1 in practice. M:N was moved out of the standard library into the mioco library. Now it's unmaintained, because nobody actually wants M:N threads in Rust—an interesting outcome to be sure!

Re:Await in Rust by pron on Monday August 12, 2019 @07:03PM (#20681040)
> Async I/O isn't anything new, and it's stood the test of time.

What do you mean? User-mode threads also use async I/O. They do just what async/await does, only without the syntax. I mean that async/await hasn't stood the test of time just yet.

> Linux can spawn hundreds of thousands of kernel threads.

Yeah, not very active ones, though. And their stacks are never really uncommitted. And the scheduler is badly optimized for many of the very uses you want in a server. On the other hand, you can spawn many millions of active fibers, schedule them how you like, and play with their state and its representation in many interesting ways (like moving a running fiber from one machine to another).

> Now it's unmaintained, because nobody actually wants M:N threads in Rust—an interesting outcome to be sure!

Just to make sure, when people say "M:N threads" they can mean a lot of very different things. In Java, they'll behave like async/await, only without the syntactic constructs. Stacks and stack frames are moved, can shrink and grow, can have various representations etc., and the scheduling is entirely pluggable. You can schedule the continuations manually, or in a scheduler of your choice, shared, or not, with ordinary tasks.

Re:Await in Rust by Rusky on Monday August 12, 2019 @07:47PM (#20681364)
A clarification: Rust async/await stacks do not move or change size. They are fixed to the size of a static call graph and are pinned in place. (Going beyond that can be done with explicit heap allocation, but it's extremely rare in practice.)

This is, from what I can tell, their main advantage in performance over other implementations. (In Rust, at least- the performance story may easily be different in garbage collected languages like Go or Java.)

Another caveat: Rust's claims to have implemented M:N in the past must be tempered a bit by two factors. First, Rust had to use segmented stacks, which Go moved away from and which Loom seems to be avoiding, precisely because they're so expensive. Second, Rust had to dispatch all IO APIs dynamically based on the currently selected runtime, which Go avoids by not having two runtimes, and which Loom can probably avoid via its scheduler implementation and/or JIT devirtualization.

Re:Await in Rust by pcwalton on Monday August 12, 2019 @10:55PM (#20682390)
Early Rust used M:N exclusively and didn't need dynamic dispatch for I/O.
Re:Await in Rust by Rusky on Tuesday August 13, 2019 @11:19AM (#20686658)
...but a large part of the reason to ditch M:N was the overhead it added to 1:1-style IO in a language quickly moving toward the C++ space.

They didn't decide to move off of M:N when it was exclusively M:N.

Re:Await in Rust by pcwalton on Tuesday August 13, 2019 @05:34PM (#20690517)
I was in the meetings where the decisions were made. The decision to add 1:1 in the first place was because of performance problems with M:N.
Re:Await in Rust by ngrilly on Tuesday August 13, 2019 @03:38PM (#20689453)
> In Java, they'll behave like async/await, only without the syntactic constructs. Stacks and stack frames are moved, can shrink and grow, can have various representations etc., and the scheduling is entirely pluggable. You can schedule the continuations manually, or in a scheduler of your choice, shared, or not, with ordinary tasks.

When you write "Java", you mean a specific implementation of the JVM, like the JVM reference implementation in OpenJDK, or you imply that all JVM implementations must support this to be called Java?

Edit: Is your comment related to Project Loom, aiming to add fibers and continuations to Java?

Re:Await in Rust by pron on Tuesday August 13, 2019 @08:02PM (#20691469)
Yes.
Re:Await in Rust by naasking on Monday August 12, 2019 @03:16PM (#20678863)
Delimited continuations are a more restricted form of my #3. The captured stack is smaller, but still much larger than what's strictly necessary, and in the pathological case, I still think it can be up to the full stack size.

Unless you have some approach I haven't heard of, in which case, please provide a link!

Re:Await in Rust by pron on Monday August 12, 2019 @03:27PM (#20678986)
Delimited continuations (I'm talking about one-shot, or non-reentrant continuations, AKA "stackful" coroutines) don't require more memory than async/await, as they capture the exact same state. That's the approach we're taking in Java, but I don't think it's particularly novel.
Re:Await in Rust by naasking on Monday August 12, 2019 @03:39PM (#20679111)
One shot delimited continuations capture strictly more state than a simple async/await implementation. The latter only captures bindings that are referenced after resumption, thus naturally implementing a sort of liveness analysis that discards unnecessary state. You could of course do the same thing during regular compilation, but it's not strictly necessary, and the interaction with GC means this state could potentially live much longer.

That said, the extra object headers from heap-allocated activations will make up some of the difference.

Re:Await in Rust by Rusky on Monday August 12, 2019 @04:09PM (#20679417)
Why would this be the case? Normal stack layout and register allocation already discards things that are not live across function calls. (With some potential wiggle room because that's not the primary goal, I suppose?)
Re:Await in Rust by ithkuil on Monday August 12, 2019 @04:28PM (#20679608)
I guess that the savings obtained by a more aggressive spilling optimizations are more appealing in case of long running async state. In any case real workload measurements should be the only judge.
Re:Await in Rust by pron on Monday August 12, 2019 @03:43PM (#20679170)
This liveness analysis isn't strictly necessary for async/await, either. You can do it in both cases or in none. In any event, it is not a matter of storing more stack, but of the level of optimization in the stack representation. Interaction with a GC brings its own complications as well as opportunities.
Re:Await in Rust by naasking on Wednesday August 14, 2019 @09:43AM (#20695454)
You're right that it's not strictly necessary, but as I said, it falls out naturally from computing the required continuations. This optimization doesn't necessarily happen naturally when compiling direct code, depending on your intermediate IR of course, ie. say, if your IR is already continuation-based.

It'll be interesting to see how this plays out on the JVM though.

Re:Await in Rust by hinkley on Monday August 12, 2019 @03:02PM (#20678730)
I was about to say we are essentially talking ourselves back into green threads but I think you may be right about CPS.

However, the paradox you have to manage here is this: if you make this sequential/parallel determination pretty high up in the code so that there is a lot of sequential code right after an async part (async can call sync all it wants), then very little of your code is altered, but now you have the green threads problem - one long CPU-bound task that doesn’t yield.

At least with Rust, if you’ve partitioned your data properly then there can be several threads of execution running tasks on unrelated data, but in Javascript you will be punished for trying to consolidate the async bits in this fashion.

Re:Await in Rust by gpderetta on Monday August 12, 2019 @12:29PM (#20676960)
4. Pass the incoming continuation as an explicit parameter to the async function and, as long as it never escapes the function (which is equivalent to the async keyword case) or (recursively[1]) any inline child function, the CPS transformation can be performed without full whole program analysis. If the continuation escapes beyond the ability of the compiler to track it, fall back to 3. Annotations can be used to guarantee the 0 overhead optimization.

[1] true non-tail recursion is a problem of course, but it is ok for the compiler to give up if it can't statically prove that the recursion depth is less than a documented maximum.

edit: rewording

Re:Await in Rust by naasking on Monday August 12, 2019 @01:06PM (#20677419)
It sounds like you're suggesting programming in explicit CPS/callback style, which is doable, but doesn't match the goal of the initial post which was to program in direct style and let the callee determine whether it should execute async or sync.
Re:Await in Rust by gpderetta on Tuesday August 13, 2019 @07:52AM (#20684829)
Oh, not at all, I want the compiler to do the CPS transform for me (same as for yield/await) and then hand me the incoming continuation as a first class lexically scoped parameter.

For example: convert an internal iterator to an external one (in pseudo C++, yes, yes, I know):

  // This is a bog standard template function. Cont can be anything 
  // here as long as it is callable. It literally does the same thing 
  // as for_each
  template<class Cont, class Iter>
  Cont inside_out(Iter begin, Iter end, Cont yield){
     // as long as yield lifetime is confined to the scope of 
     // inside_out; when Cont 
     // ends up being some compiler internal (possibly unique) magic  
     // type it should be possible to CPS convert both inside_out and 
     // for_each. Both rust and C++ monomorphize templates anyway so 
     // instantiating dedicated CPS variant is not a problem.
     std::for_each(begin, end, [&](auto&& x) { yield(x); });

     // for various reasons we need to return the continuation here. A 
     // proper language would tail call to it.
     return yield; 
  }

  ...
  // this is basically call/cc. It uses magic to get the current 
  // continuation, then  invokes its first argument with it and the 
  // remaining arguments. The current function need to be 
  // Note: template functions are not first class in 
  // C++, but we take some liberty here.
  auto cont = continuationify(inside_out, my_vec.begin(), y_vec.end());

  while(not cont.done())
     std::cout << cont();
if inside_out was not templated, but instead Cont was type erased (a trait object in rust parlance), then continuatinify would need to allocate a dedicated stack and fall back to good old userspace context switching (the compiler is welcome to pierce through the type erasure layer and optimize anyway, but it would not be a guaranteed optimization).

Rust is close enough to C++ that it would work in the same way. I've been planning to write a paper for the C++ committee to add fire to the current coroutine controversy and proposing something like the above (which would unify stackless and stackful coroutines).

Re:Await in Rust by a1369209993 on Monday August 12, 2019 @04:48PM (#20679839)
I think they're suggesting programming in implicit CPS/callback style. Eg:

  int bar(int i) { return i+1; }
  /* bar takes a implicit return address in [esp+0] */
  noreturn foo((*ra)(int),int x)
    {
    /* where (*)(RetType) is a return address */
    int y = bar(x); /* x86 call pushes eip */
    ra return bar(y); /* pseudo-tail-call (push ra) */
    }
  int quux(int x)
    {
    (*ret)(int); ret = (return); /* get implicit RA */
    foo(ret,x);
    } // unreachable
You'd also need to adjust the stack and/or frame pointer though, which is probably going to be the hard part.
Re:Await in Rust by jjtheblunt on Monday August 12, 2019 @01:17PM (#20677548)
typing "async" and "await" are themselves overhead; this contrasts with what the Go compiler abstracts away, isn't it?
Re:Await in Rust by naasking on Monday August 12, 2019 @03:30PM (#20679010)
Go takes approach #3 that I described.
Re:Await in Rust by __david__ on Monday August 12, 2019 @04:47PM (#20679816)
Go has a really neat stack allocation technique that makes #3 extremely non-wasteful compared to C and Rust threads. Unfortunately Rust cannot use the same trick as is is llvm based and llvm doesn't support that technique.
Re:Await in Rust by kibwen on Monday August 12, 2019 @06:34PM (#20680809)
> Unfortunately Rust cannot use the same trick as is is llvm based and llvm doesn't support that technique.

This is a bit mistaken; ancient versions of Rust (but not so ancient that they weren't using LLVM) were, like Go, everything-is-implicitly-async, and Rust used the same strategy as Go to manage stacks (to the extent that the old language reference listed Go in its (rather long) list of precursors, specifically calling out its split stack approach).

The ultimate reason that Rust switched away from Go's approach is for interoperability with C. If your stacks are the size of C stacks, and if your language doesn't feature a pervasive runtime, then interoperability with C is nearly trivial, and this was something that Rust dearly desired (and has benefited greatly from, IMO).

Ultimately one should not look at Rust's chosen approach and assume that it represents some grand rebuke of alternative approaches to asynchronicity. If you have different constraints, then a Go-style approach is lovely. For a language at the level of Rust, different tradeoffs may dominate.

Re:Await in Rust by Const-me on Monday August 12, 2019 @07:45PM (#20681347)
> If your stacks are the size of C stacks, and if your language doesn't feature a pervasive runtime, then interoperability with C is nearly trivial

in .NET stacks are different, the runtime is relatively large, yet interoperability with C is nearly trivial. From my PoV at least, I never worked on compilers, only used them a lot.

Re:Await in Rust by kibwen on Wednesday August 14, 2019 @02:41PM (#20698611)
Interesting, though note that when I say "trivial" I'm not referring to the amount of effort it takes on behalf of the programmer using the feature, but the amount of work that needs to be done (and hence CPU time that needs to be spent) when crossing the boundary from one to another and back again (which is often invisible in the source and implicitly inserted by the compiler/runtime). In Rust there is no cost to traversing this boundary (the only hypothetical cost being that inlining is more difficult (though still technically possible, miraculously) across language boundaries).
Re:Await in Rust by Const-me on Wednesday August 14, 2019 @07:33PM (#20701099)
> the amount of work that needs to be done (and hence CPU time that needs to be spent) when crossing the boundary from one to another and back again

That amount is really small for .NET. See this doc https://docs.microsoft.com/en-us/cpp/dotnet/calling-native-f... it says “PInvoke has an overhead of between 10 and 30 x86 instructions per call.” That’s barely measurable. Close to the cost of a mispredicted branch. 4x cheaper than a single cache miss.

That’s if you do it correctly, i.e. only marshal value types or arrays/pointers of them, don’t use custom marshallers, etc. Also it’s important to specify correct attributes. To call `int func(const sStruct&)` C++ function, you should specify `[In] ref sStruct` in C#.

Re:Await in Rust by bryanlarsen on Tuesday August 13, 2019 @10:24AM (#20686106)
It's easy to call C from .NET, but is it easy to call .NET from C? In a cross-platform manner?
Re:Await in Rust by Const-me on Tuesday August 13, 2019 @12:10PM (#20687174)
Yes, and yes.

Declare a delegate in C#, apply [UnmanagedFunctionPointer] attribute specifying calling convention (on Linux you’ll likely want CallingConvention.Cdecl), pass to native code specifying [MarshalAs( UnmanagedType.FunctionPtr )] in the prototype of the DLL/SO function which accepts that pointer.

This is even more convenient than doing that in C. In C, function pointers are stateless, typical design pattern is pass accompanying `void* context` for the state. Not needed in C#, that delegate can capture whatever it wants, the runtime will do it’s marshalling magic, generating a native function and somehow associating it with the captured state.

The only caveat is lifetime. Function pointers can’t be retained by native code, they’re too simple and don’t have a ref.counter. You must ensure the .NET delegate is not garbage collected while it’s referenced by the native code, or the app will crash.

Re:Await in Rust by bbatha on Monday August 12, 2019 @05:40PM (#20680324)
Go still needs to fallback to heavy stacks for c-ffi. In fact that’s the main reason rust dropped stackful coroutines, because you lose zero-cost ffi to c.
Re:Await in Rust by Arelius on Monday August 12, 2019 @05:09PM (#20680037)
Do you have any links or details about this technique? It sounds interesting and I'd like to learn more.