-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Immediate questions that come to mind #5
Comments
Could you reframe the title to be somewhat more productive? These are useful questions but the tone is really offputting. |
Fair enough. Changed the title |
Rust macros are
For example, the introduction of
If 1 and 2 can still theoretically be solved by the ingenuity of some crate author, 3 is, dare I say, almost hopeless without some leadership structure, which this initiative can serve as. I will leave the practical difficulties of 1 and 2 to actual experts. |
Been thinking about this a bit more from a different angle: parametricity. Generics are a way to implement parametric polymorphism. Or in other words, functions whose "core" behaviours are invariant under a certain input. When you have a function such as fn get_something<A>(map: &HashMap<String, A>) -> Option<&A> {
map.get("something")
} this function is parametric in However, the genericity in this proposal is explicitly not parametric in this way, the intent is precisely to branch on the specialization, which makes this "genericity" more akin to C++ template specialization, than to true parametric polymorphism. To clarify, we can look at how "polymorphic effects" could look like in Rust. In functional languages like Haskell, you can have functions such as fmap :: Functor f => (a -> b) -> f a -> f b or filterM :: Applicative m => (a -> m Bool) -> [a] -> m [a] In these functions the effects ( To have the above in Rust, we need "higher-kinded polymorphism", i.e. a way to create a generic out of the So how would this look like for this proposal? Well, it would be quite awkward to implement, but it would clarify e.g. my 2nd question in the original post. To address the "non-effectful" version of a function we would need something like the fn mapM<A, B, M: Monad>(f: fn(A)->M::App<B>, list: Vec<A>) -> M::App<Vec<B>> {
let mut result = vec![];
for a in list {
let b <- f(a); // Monadic bind, which can substitute for e.g. await or ?
result.push(b);
}
monadic_return result // No idea what this would look like
} The struct Id<A>(A); and the Anyway, this is all to say that to me, generics are not the right syntactic construct to implement polymorphic behaviour that branches on specialization, as this kind of behaviour doesn't abide by parametricity. |
Hi, thanks for asking questions! I'll do my best to answer them, but please keep in mind that this is a work in progress, and what I'm sharing is our understanding so far.
Sure, in the blog post we highlighted multiple examples of people authoring crates which support both async and non-async Rust. We expect this feature would be helpful for all those crate authors - but also for most of the stdlib.
I'm not sure I follow, can you elaborate?
As @louy2 mentioned in #5 (comment), macros are fundamentally limited in their functionality. They are just simple token expansions, which can't really interact with the type system in the way that we'd want to here. That means that for example things like propagating "asyncness" across multiple calls is hard. And balancing that with good ergonomics, diagnostics, and performance is nigh impossible. As we mentioned in the post, there are existing attempts in the ecosystem to provide async polymorphism entirely through proc macros, but these very clearly run into these limitations:
For an example of people's experience attempting to implement async polymorphism using proc macros, see: [1], [2], [3], [4], [5].
This seems like a rephrasing of the third question. The answer is the same: macros are insufficient to handle this, so we're looking at integrating it into the type system instead. I hope that mostly answers your questions! |
Ok so this clarifies question 1 somewhat, thank you! Looking at the examples in more detail however makes it even harder to understand what the intention of the proposal is. Take the first example,
Taking a look at postgres now... and just by looking at the dependencies of the "sync" crate we can tell that it's also just a sync facade on top of an async implementation. Ok, perhaps a "real-life" example is a weird thing to ask, given that the feature should provide a completely new mechanism for handling the sync-async fiasco. So let's try to take an existing async function and see how the sync version would look like. From pub async fn query_one<T>(
&self,
statement: &T,
params: &[&(dyn ToSql + Sync)],
) -> Result<Row, Error>
where
T: ?Sized + ToStatement,
{
let stream = self.query_raw(statement, slice_iter(params)).await?;
pin_mut!(stream);
let row = match stream.try_next().await? {
Some(row) => row,
None => return Err(Error::row_count()),
};
if stream.try_next().await?.is_some() {
return Err(Error::row_count());
}
Ok(row)
} Ok so let's assume for now that we somehow come up with a syntactic construct that abstracts over the bind operation Does this clarify my 2nd question in the original post? |
So for some context here: I'm also a member of the Rust Async WG, so I can speak at least to the intent our group has. In the case of If the database driver chose to implement itself in terms of keyword generics, the method could choose to either return a sync or async iterator, depending on which mode it was compiled in. Regarding the use of We expect that keyword generics would make supporting this even easier, and would remove the existing downsides of wrapping async APIs in |
So how would the keyword-generic version of |
I think we need to differentiate between "combinators" and "primitives". "Primitives" are those directly implementing To that end, I don't think the keyword-generic version of Possibly we can have a simple single-threaded local executor block the primitives, and make that the blessed blocking specialization, or the default blocking specialization until the author supplies one. The status quo in Rust is keyword and conversion driven higher-kinded polymorphism, with only built in higher kinded types, that is: |
Likely exactly the same as the existing async variant, with the addition of the keyword-generic param being carried. There are some restrictions though: generic async code can't use async-only concurrency operations, so limitations definitely apply. And the language is currently still missing primitives like async closures, traits, drop - so the code would likely change based on that too. But assuming async reaches feature parity with non-async Rust, then going from async -> maybe async should mostly only require making the async keyword generic.
Yep, that's right. We're definitely wondering whether we could expose both the sync and async variants using a single definition, something along the lines of: async<A> trait Iterator {
type Item;
async<A> fn next(&mut self) -> Self::Item;
}
To clarify: we expect concrete types to be able to compile down into their sync counterparts. Take for example |
Now that is a surprise to me, since I have been thinking about only the Excuse me, but you don't have to answer those questions immediately. But do those questions indicate I am on the right mental track? That is a very reasonable user story, and one I probably would like to have, but it does make the theory side a lot harder to comprehend for me. I was only thinking about types on which the keywords would directly operate. But now that you have mentioned, I have realized that indeed the infection of |
async
andResult<>
are not intrinsically, fundamentally different to the non-effecting variants? Kind of what this question asks, only applied to.. well, any actual function that properly uses these effects. At some point in the.await
chain there is aFuture
which is gettingpoll()
ed. At some point in aResult
function there is anErr()
constructed. How is this reconciled with the genericity?The text was updated successfully, but these errors were encountered: