--- title: wg-async meeting 2023-08-17 tags: deep-dive, wg-async, minutes date: 2023-08-17 url: https://hackmd.io/G6ULofyXSIS4CK9u-jwYRg --- # Agenda * Initial issue: https://github.com/rust-lang/rust/issues/111546 * Autoderef PR https://github.com/rust-lang/rust/pull/111773 * [Analysis of Rust issue #42940](/83sY9XQjSiK79QdnqK0F3Q) # Attendance Present: tmandry, TC, yosh, eholk, vincenzo # Autoderef on `.await` PR TC: We discussed this in the meeting on Monday. There seem to be tradeoffs here. At a minimum, it probably needs a crater run. vincenzo: Looks like that the reason is that we could simplify the API: from: ```rust! assert_eq!(shared.as_ref().await.id, 4); ``` to: ```rust! assert_eq!(shared.await.id, 4); ``` Sure why not? I am not able to see what is the impact of possible api that relay on `as_ref()`? ## Compatibility tmandry: Can this break existing code? Is this a change we need to consider over an edition? vincenzo: We should be careful about this, but if it's compatible we should do it. ## Do we feel this just because syntax tmandry: There's an argument from consistency with method dispatch. TC: I wonder if we would feel the same way if we had gone with prefix `await` or postfix space syntax. yosh: I think we would. Striving for async Rust to be like sync Rust, it could make sense. ## "just add a `.await`" yosh: https://github.com/rust-lang/rust/issues/111546#issuecomment-1557577198 ```rust // sync fn johns_id() -> Option<u32> { USER_IDS.get("John Doe").copied() } // async, current fn johns_id_no_autoref() -> Option<u32> { USER_IDS.force().await.get("John Doe").copied() } // async, proposed fn johns_id_with_autoref() -> Option<u32> { USER_IDS.await.get("John Doe").copied() } ``` ## Implicit async iterators Autoref on await would let us do: ```rust let mut iterator = AsyncCountingIterator::new(); let first = iterator.await; let second = iterator.await; ``` instead of ```rust let mut iterator = AsyncCountingIterator::new(); let first = iterator.next().await; let second = iterator.next().await; ``` ## IntoIterator? tmandry: The other consistency angle I'm thinking of is `into_iterator`. Do we do it there? ```rust let foo = vec![]; // Calls <Vec<()> as IntoIterator>::into_iter(): for x in foo {} // Calls <&Vec<()> as IntoIterator>::into_iter(): for x in &foo {} ``` TC: To summarize, when the language uses `into_iter`, as in `for x in y` syntax, it doesn't do `autoderef`. Since `Iterator` and `for` is a similar effect to `Future` and `await`, this is a strong analogy. tmandry: Agree with that summary. The original example in the issue was: ```rust use core::future::IntoFuture; use futures::future::{ready, Ready}; struct Foo; impl IntoFuture for &Foo { type Output = u32; type IntoFuture = Ready<u32>; fn into_future(self) -> Self::IntoFuture { ready(2) } } #[tokio::main] async fn main() { assert_eq!(Foo.await, 2); } ``` ```rust (&USER_IDS).await ``` tmandry: People on the lang team have in the past "threatened" to add `.ref` to make that nicer. yosh: A different way to reason about it could be we should tweak how `into_iterator` works. (Not actually proposing we should though, but it's an option.) tmandry: It does make me think here, "why couldn't the user just do an `impl IntoFuture` for Foo"? yosh: [That seems to work?]( https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f4a947542b4c01973d7a37df960eef27) eholk: that consumes the `Foo` though. In the example on the issue the thing being awaited is a static so we don't want to (can't?) consume it. tmandry: Because of postfix `.await`, it's more of a pain to explicitly take a reference. Some lang team members have talked about `.ref` or similar. But we should maybe put more weight on the method dispatch analogy based on this. TC: What would be the tradeoffs of doing autoderef on `for` loops? tmandry: It would be confusing. eholk: With `for` loops there's a convenient place to put the ampersand. TC: It seems maybe unprincipled to make a decision based only on the syntax that we chose for await rather than its semantic similar to other things in the language. TC: Do any of the downsides that tmandry mentioned about `for` apply to `.await`? yosh: Are there any general statements we can make about postfix syntax? TC: E.g., what if we had postfix `.for(..) { .. }`? How would we feel about that and about the tradeoffs that tmandry raised there? tmandry: It does feel fine to lean on the syntax for deciding how to look at this. eholk: `.await` feels like a method call. It's not, but it feels that way. TC: Interesting aside, during the discussions about choosing the `await` syntax, the fact that postfix dot looked like a field access or method call was one of the downsides people noted. tmandry: It seems like we need to do a crater run. It's definitely doing to need an FCP from the lang team. TC: Do we think that t-types will have a feeling on this? tmandry: Yes, that's a good point, we should collect feedback there. It's be a good stance to say that we want this but that we're cautious about problems. TC: +1 yosh: Thinking about what you said, TC, about it not being principled.... I do think we want principled designs. If we decide that `.await` should do deref, then we should decide that all postfix syntax should probably do that. We should take that into account. That's definite 100% RFC material and lives on the lang team. TC: +1 tmandry: +1, there should be some principle. eholk, vincenzo: +1 TC: Postfix `match` syntax... we couldn't do autoderef for that, that would be weird. tmandry: Right. yosh: Is the binding mode thing on `match` related to autoderef? TC: Not really, it follows a different set of rules. eholk: Are these under discussion on the language side? tmandry: We have discussed postfix `match`. yosh: I know of two examples where postfix `match` is being considered. Here are two examples: ```rust // for-loops for match in my_iter { x => {} y => {} } // try statements try { .. } match { .. } ``` tmandry: That's different than what I think of with respect to postfix `match`. I think of `.match { .. }`. ## Short reprise of `Future::map` tmandry: We don't really have time to dive into the second issue. TC: Maybe we could discuss the items from last week for the people who weren't here. TC: Here's that meeting: https://hackmd.io/LfWcsTSYTPK5TuORJ4l22g (Discussion about `Future::map`.) TC: I did have a thought about this after the meeting last week... TC: Maybe we should just add this method to a separate trait (that could later be implemented by other things like `Option`). So rather than: ```rust pub trait Future { fn map<U, F>(self, f: F) -> Map<Self, F> where F: FnOnce(Self::Output) -> U; } ``` We would add: ```rust trait MapOnce<T> { type Wrapped<U>; fn map<U, F>(self, f: F) -> Self::Wrapped<U> where F: FnOnce(T) -> U; } ``` And we would add an appropriate blanket impl. (Some discussion followed.) (The meeting ended here.)