Live data from Hacker News

Zero-cost futures in Rust

aturon.github.io

231–240 of 348 posts

Re: Zero-cost futures in Rust

#231

Big downside is now you will have a dichotomy of functions that block using futures and functions that block at the OS level and no sane way to intermix them. Rust essentially becomes two languages. Async/await sugar doesn't fix this. Would be great if functions could be written in a general way for both IO models and users could select the implementation at their convenience.

> Would be great if functions could be written in a general way for both IO models and users could select the implementation at their convenience.

We tried this with a compile-time switch between 1:1 and M:N threading in earlier versions of Rust and the results pleased nobody. It was slow, complex, and unwieldy.

Re: Zero-cost futures in Rust

#232
This is cute. This is clever. Whether or not it's too clever time will tell. A year ago, I noted that Rust was starting out at roughly the cruft level C++ took 20 years to reach. Rust is now well beyond that.

All this "futures" stuff strongly favors the main path over any other paths. You can't loop, retry, or easily branch on an error, other than bailing out. It's really a weird syntax for describing a limited type of state machine.

I'm not saying it's good or bad, but it seems a bit tortured.

Re: Zero-cost futures in Rust

#233

This is cute. This is clever. Whether or not it's too clever time will tell. A year ago, I noted that Rust was starting out at roughly the cruft level C++ took 20 years to reach. Rust is now well beyond that. All this "futures" stuff strongly favors the main path over any other paths. You can't loop, retry, or easily branch on an error, other than bailing out. It's really a weird syntax for describing a limited type…

What's your definition of "cruft," and where does it appear in Rust?

I will be the first to admit that Rust is not perfect, but that adjective does not come to mind. I am of course incredibly biased.

Re: Zero-cost futures in Rust

#234

Earlier quoted context omitted.

> This requires that you either know the size of the stack up front ... generally not possible without being conservative Well, you are writing the compiler. Sure you'd be up against the halting problem, but relatively few functions are (non-tail) recursive. Perhaps the unwieldiness of a large stack is better attributed to the feature of unbounded recursion (and FFI into "uncharted territory") than the feature of gre…

It would require higher order control flow analysis like k-CFA, which would certainly fail to produce a bounded stack size on any nontrivial program. The futures library is the control flow analysis. Because it uses the type system instead of higher order control flow analysis, it actually achieves precision.

I was thinking of a much lighter analysis, based on some optional restrictions. If a function:

- is not recursive

- does not call function pointers (ie trait objects)

- allocates only fixed-sized objects on the stack

- only calls functions with known stack requirements

then its stack requirement should be known, no? It feels like with these requirements, one can still write many programs (threads, really). And if one goes beyond the restrictions, then they just pay the cost of having to guess a large stack size.

Stack size analysis would be helpful for other applications as well, like embedded platforms.

Re: Zero-cost futures in Rust

#235

Earlier quoted context omitted.

> Also in Rust. Most apps that aren't servers don't want async I/O, and it causes a lot of problems when you need high-performance FFI. For example, in Servo async-everywhere would be a big problem for WebRender, which needs to be able to call OpenGL extremely quickly. I don't understand why this is the case. Since async/await allows the compiler to transform the code into a state machine, why would it be not be able…

Because async-everywhere usually means pushing blocking FFI calls over to a thread pool, which would be unacceptably slow for e.g. OpenGL.

"Usually" is a funny word, but I see what you are saying.

I guess if you are in a single-threaded event loop scenario (ala nodejs) you could get away with it somewhat, but as soon as you introduced multiple threads of execution all bets are off.

That's unfortunate.

We have a fairly large codebase written in .NET. We wouldn't mind porting it to CoreCLR, but we'd have to migrate everything to async/await. The red/blue nature of method signatures makes this look like almost a complete rewrite. Given the difficulty of this migration so far, and the nature of the change, it's certainly caused us to explore other options and we've already rewritten a significant chunk of the code in Go.

Moving a large codebase from a single color model to a dual color model really sucks. I hope Rust can lock this down sooner rather than later otherwise a lot of people are going to feel pain down the road.

The good news is you have a good static type system. I cannot even begin to imagine migrating a Python codebase to async/await...

Re: Zero-cost futures in Rust

#236
post #74

Earlier quoted context omitted.

Yeah, using closures in zero-cost abstractions (like iterator or now future adaptors) does make compilation slow. LLVM isn't used to this kind of code, and takes time optimizing it. However, now that we have MIR we can optimize this on the Rust side first, before the type info has been lost. This was one of the reasons MIR was added :)

I'm... curious about why you think LLVM isn't used to the sort of code that results from these sort of zero-cost abstractions: C++ code is also very aggressive about templates that inline away to nothing, and modern C++ especially has many similar abstractions to Rust (cf. closures, and the range proposal). Rust isn't special in that particular regard. Maybe you mean LLVM is very low-level and thus it sees everything…

Yeah, I meant the latter, sorry.

Re: Zero-cost futures in Rust

#237

This is cute. This is clever. Whether or not it's too clever time will tell. A year ago, I noted that Rust was starting out at roughly the cruft level C++ took 20 years to reach. Rust is now well beyond that. All this "futures" stuff strongly favors the main path over any other paths. You can't loop, retry, or easily branch on an error, other than bailing out. It's really a weird syntax for describing a limited type…

Cruft? Coming from C++ I find it quite the opposite.

Re: Zero-cost futures in Rust

#238

Earlier quoted context omitted.

Because it's an associated type ( https://doc.rust-lang.org/book/associated-types.html ), not a regular generic parameter.

Aren't generic types with one associated type isomorphic to 'normal' generic types? In that sense, it is still odd.

Not exactly. A given type can have infinite implementations of iterator (by implementing for different A) but only one impl for iterator

Re: Zero-cost futures in Rust

#239
post #219

Earlier quoted context omitted.

Easy and error prone. Sometimes libraries pretend to be async and accidentally are sync. Consider some library that in the normal case just does some pure computation, but logs to syslog or something on some error condition. If you use that library in an async context, it could work fine most of the time, until you hit some unexpected situation where it happens to make a network request to some syslog daemon and bloc…

> It's also the case that often async libraries depend on some sync library and so they have their own worker pool. You can easily have many libraries with their own worker pools all using more resources then they need. The Rust story will not be complete without a canonical single implementation of a thread pool that everybody doing async I/O uses for blocking tasks. > For example, if you have some async worker that…

I hope your optimism that a single kind of thread pool will service all applications is well founded. It seems like people would want to specialize them much like they want to specialize their memory allocators. The Rust team has a really great track record of innovation and technical excellence so I look forward to the design that will accomodate that and hope the ecosystem buys off on it.

Go does limit the number of threads and will crash the process if it goes over. It's also very rare to have CGo call back into Go multiple times versus libraries juggling adapters between async and sync in my experience. It's also easy to have your library have a limiter on the number of CGo calls you make, but less easy to limit the number of tasks you throw into a thread pool because you don't have the option to block. (edit: I think you can just store the closure on the thread pool and schedule it eventually at the cost of writing your own scheduler and perhaps requiring allocations?) I have a feeling that a similar crashing solution won't work in the Rust community, and what to do when the limits are hit will be punted upstream. My main point is that there are many subtle details in solving the "colored function" problem.

I don't think all M:N systems have the debuggability problem becausd the runtime has a single canonical representation of what is running: the stack traces. Since the entire ecosystem bought into the runtime, you don't have any fracturing of representations. If you're optimisitc that the entire ecosystem will buy into whatever mechanism you have to do async work, then this can be solved, but I believe that's already not the case (people hand code state machines) and is unlikely to stay the case as time goes on.

Post reply on HN