Live data from Hacker News

Zero-cost futures in Rust

aturon.github.io

261–270 of 348 posts

Re: Zero-cost futures in Rust

#261

Earlier quoted context omitted.

In other words futures are not as composable as one would hope. John Reppy's CML seems like a much better toolbox which gets composability without pretension. Vesa Karvonen (whom I understand has worked on the MLTon compiler) has offered an excellent delivery in Hopac for C# and F# complete with a slew of combinators: https://github.com/Hopac/Hopac I'm not aware of anyone offering an alternative superior to an inform…

> I'm not aware of anyone offering an alternative superior to an informal CSP yet which seems to be the reason why Go and Clojure have picked it as well for their concurrency model. What about Quasar [0] on the JVM? 0 - http://docs.paralleluniverse.co/quasar/

But Quasar does seem to offer go-like channels? I'm unclear what kind of combinators are provided but those could be implemented.

Re: Zero-cost futures in Rust

#262

Earlier quoted context omitted.

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…

No program beyond the most trivial will meet these requirements. The moment you call any function indirectly you lose.

Re: Zero-cost futures in Rust

#263

Earlier quoted context omitted.

> The main advantage of the go model is that both asynchronous and synchronous operations are identical There is a bit of misunderstanding on your part. There are no asynchronous operations in that go model, everything is synchronous. There is no event loop underneath, despite what some people claim. And this is absolutely not an advantage in a shared memory environment. Instead it forces you to do synchronization to…

> There are no asynchronous operations in that go model, everything is synchronous. You are the second person saying this, and i have to admit i am really confused by this statement. From my understanding, golang does not expose an asyncio interface, but this doesn't meant that golang runtime doesnt perform io operations asynchronously. So golang expose async operation through a synchronious interface, which is what…

> From my understanding, golang does not expose an asyncio interface, but this doesn't meant that golang runtime doesnt perform io operations asynchronously.

This is also true for an OS kernel.

Re: Zero-cost futures in Rust

#264

Earlier quoted context omitted.

Right, there may be a lot of back and forth marshaling between using thread pools and not depending on whether the library you're using is futures based or not. Maybe you use one library that is futures-based and one that isn't. Maybe the library you use is mostly non-blocking except for one use of sleep() or another esotorically blocking call. It's just annoying and prone to error. Most people may not even be aware…

> Right, there may be a lot of back and forth marshaling between using thread pools and not depending on whether the library you're using is futures based or not. So just like if you use cgo. You can't get away from having to deal with the issue entirely; the most you can do is to punt it to the FFI layer. There is the question of how much of the community is using blocking vs. nonblocking I/O, to be sure, but Go has…

I see your cgo analogy but at the same time it's much less pronounced there since the programming interface is the same, the programmer is supposed to assume everything will work as it should (even if it doesn't always). In this case it's a different programming interface and I think that stresses the issues.

Regarding your comment on preferentially having control over blocking/async code. I think that's right. At the same time, some C++ programmers would say that they prefer having to think carefully about how memory is managed in there program (say, for the benefit of fast no bounds checking). C++ draws a line, Rust draws a line, Go draws a line, Java draws a line, and Python draws a line. These lines are somewhat about technical superiority and somewhat about programmer identity/preference but they are mostly about domain-specific constraints and necessary tradeoffs. This futures-based approach will be sufficient (if somewhat inconvenient) where Go/Erlang can't be used, e.g. where GC pauses are absolutely intolerable.

Re: Zero-cost futures in Rust

#265

Earlier quoted context omitted.

System call context switching is cheaper than and different from thread/scheduler context switching.

OK, but that's irrelevant in this context because Go has to context switch either way.

It can also switch on memory allocations and function calls as well. I'd expect a large fraction of the context switches come from those events and don't go through the kernel.

Re: Zero-cost futures in Rust

#266

Earlier quoted context omitted.

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

I don't think I get that without an example. Do you know of any simple examples?

Gankro gave an example, but here's a better way to think about associated types:

Think about them the same way you think of a method. Methods are "associated functions". If you implement a trait on a type, it can only have one version of a trait method, not two. Similarly, it can have one associated type, not multiple.

With a generic trait the trait itself is generic; there are multiple "versions" of this trait so you can implement it multiple times for different parameters and different methods.

A concrete way to think about this is overloaded methods. Rust doesn't have the regular kind of overloading, but it does have the Fn traits.

https://doc.rust-lang.org/core/ops/trait.FnOnce.html

(ignore the rust-call stuff)

The trait is essentially:

    trait FnOnce {
        type Output;
        fn call_once(self, args: Args);
    }
Note that it has both a type parameter and an associated type.

Now, we might have a type Foo which is `FnOnce`. This means that it takes a single integer in, and outputs a bool. We can overload it by also implementing `FnOnce` and `FnOnce`. This means that it can also take in a char and return a bool, or take two integers and output a char.

Now, given these impls, I can try to overload it with `FnOnce`. However, we can't. Because this means that the function will return either a book or a char when you feed it two ints. This is not how we want functions to behave, and this is why Output is an associated type. For a given trait implemented on a given object (in this case FnOnce implemented on Foo), there is only one output type. But there can be multiple versions of the trait implemented by changing the generic part. So, while an overloaded function may take in multiple different input types, each set of inputs has only one possible output type.

Of course, Rust doesn't forbid having it the other way around -- we could make Output a generic parameter too, and have overloaded functions which can have different output types for the same input (and need type annotations to choose). But we don't want FnOnce to work that way, so we don't have it like that.

Similarly, given an iterator, there is only one type it can produce. We don't want there to be types which are iterators over multiple things.

On the other hand, the trait PartialEq has a single type parameter. This is because we want you to be able to test equality between many types.

Re: Zero-cost futures in Rust

#267

Earlier quoted context omitted.

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…

No program beyond the most trivial will meet these requirements. The moment you call any function indirectly you lose.

Yes, but indirectly means "&Fn", but not "&F where F: Fn".

And a whole program doesn't need to conform, only individual threads. Presumably the ones you want to make a lot of.

(And if a little dynamicism was required, its expense could be paid for at the use, by creating a fresh necessary-sized stack at that point. But I'm probably opening up old split-stack wounds, sorry)

Re: Zero-cost futures in Rust

#268

Earlier quoted context omitted.

> 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. You can do all of that.

OK, make an HTTP request, and if it fails, wait 2 seconds and retry. After 10 times, give up.

Timeouts are covered here. http://alexcrichton.com/futures-rs/futures/index.html#exampl...

As for repeating something multiple times, you would either repeat the chain 10 times or write your own future combinator. Probably writing a custom future combinator would make the most sense here; actually, such a thing ought to be built-in to futures.rs.

Re: Zero-cost futures in Rust

#269

Earlier quoted context omitted.

> 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.

It's my understanding that the alternative green thread runtime used stack swapping. What I'm referring to here is the same futures method under the hood but transparent to the user. It's also my understanding that there were unrelated engineering constraints that caused it to be unwieldy, such as binary size. I believe it's possible to provide an alternative runtime without it necessarily affecting the main configur…

> What I'm referring to here is the same futures method under the hood but transparent to the user.

CPS transforming the entire program is possible in theory, but if you want the same zero-cost behavior you will run into the same issues I outlined elsewhere: higher order control flow analysis will be necessary, and it will fall down a lot.

Re: Zero-cost futures in Rust

#270
post #67

Earlier quoted context omitted.

Does the need for this arise, in some sense, because Rust doesn't offer classical inheritance and so no mechanism to indicate co/contra variance?

Rust does let you communicate variance (it's very important for lifetimes). Variance is determined based on a type's composition, and PhantomData can be used to specify variance that doesn't immediately follow by providing an "example" of what it should behave like. The only limitation of this approach is that you can't override the compiler's reasoning -- if it sees evidence for covariance, and you provide more evid…

For anyone else wondering what the deal is with "smaht pointers": https://www.reddit.com/r/rust/comments/3404ml/prepooping_you... I think I first saw the term on that thread but didn't stay long enough to see the explanation. It was driving me nuts! :)
Post reply on HN