owned this note
owned this note
Published
Linked with GitHub
---
title: WG-async open discussion 2023-09-28
tags: WG-async, open-discussion, minutes
date: 2023-09-28
discussion: https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async/topic/Open.20discussion.202023-09-28
url: https://hackmd.io/JWIp_2rJRgmfn5axJUbsVg
---
# Open discussion: September 2023
This is the doc for open discussion within wg-async about various design questions and other topics. Please feel free to propose a topic below.
On the day of the meeting, we'll do a quick poll to sort the topics by interest and then go through them one by one. If you have a brief (under 5 min) introduction prepared for the group, we'll take that into account as we prioritize the topics.
###### tags: `open-discussion`
Attendance: TC, tmandry, Zach Mitchell, eholk, yosh, vincenzo
Leave discussion topics below.
https://blog.yoshuawuyts.com/linearity-and-control/
---
## Purpose of `linear fn`
tmandry: Why do we need to mark functions, and not just arguments / type parameters, as `linear`?
yosh: I'm trying to page this back in. In general like, I'm pretty convinced this interpretation of linearity is *not* what we want, so I've not really thought about it much since writing the second post.
yosh: I believe the main reason is that futures are weird. An `async fn` is not just a function, it's also a type containing the actual computation's state machine. So when we say `linear async fn`, that not only governs the arguments passed into the function - but also the function itself.
yosh: `Fn{,Once,Mut}` have a very similar property: if we take a closure as an argument somewhere, then we have to know somehow whether that closure is trying to uphold the linearity guarantees or not. And functions can be passed as closures, we need a notation for functions too.
yosh: We have this problem in reverse for `unsafe fn`. Where if you have an `unsafe fn`, you can't actually pass it as a closure anywhere because we don't have `UnsafeFn` traits.
tmandry: You want closure values to be linear if they capture linear types.
yosh: Yes.
tmandry: It feels different from `async` and `try` effects though in that it doesn't affect the control flow of the function (e.g. `map`).
TC: Do free functions need to be marked `linear`?
yosh: If we ever want to pass it as a function argument
```rust
fn foo(f: impl Fn) {}
unsafe fn bar() {}
foo(bar);
fn foo(f: impl UnsafeFn) {}
fn bar() {}
foo(bar);
fn foo(f: impl LinearFn) {}
fn bar() {}
foo(bar);
```
TC: But we can pass a `Fn` somewhere that expects an `UnsafeFn`, etc.
yosh: Yes.
---
## Destructor arguments
tmandry: One of the benefits I've had in mind for linear types is that it would allow explicit destructors that take arguments, e.g. a context that's needed for destruction. So maybe not all linear types should be `Drop`?
yosh: I think that's a valid thing to want, and that sort of touches on the two different interpretations of linearity:
1. Linear types as a type-system version of `#[must_use]`
2. Linear types as "Drop is guaranteed to be called"
yosh: Being able to pass arguments into the destructor works really well with the first variant. But that interpretation of linearity has the downside that it leads to pretty different usage patterns than what we're used to in Rust today. Feedback on Niko's earlier post on linearity showed that, where things like closures run into a lot of issues.
yosh: Instead what I hope we might be able to do is take the "will Drop" interpretation of linearity, and over time give it more capabilities. Maybe using context/capabilities we can find a way to pass arguments into destructors?
eholk: I kind of like that linear types make destructors a convention or design pattern, like constructors are in Rust, rather than necessarily something special.
tmandry: I think 2 is the thing I've been calling `?Leak`/`!Leak` in my head. Skeptical of being able to use contexts/capabilities for destructor args because it's saying you *couldn't* destruct the value without the context... hm, might work?... but I'm not sure.
yosh: I think 2 is way more valuable than being able to pass arguments into the destructor.
---
## Linear Drop and owned references
eholk: The proposal to have the linear version of `drop` take `self` probably won't work for pinned data (admittedly, drop is wrong for pinned data already), because a `self` arg is semantically *moved* from the callers frame into the `drop` function's frame. I've seen a few people discuss something like `&owned T` references, which it seems like will be necessary here.
yosh: do you have a link to discussions about `&owned T`? I haven't heard of that before I think?
eholk: No, but here's a quick example of how `&owned` would work:
```rust
fn consume(x: &owned Box<i32>) {
mem::drop(*x);
}
fn main() {
let x = Box::new(42);
consume(&owned x);
println!("{}", x); // error: use of moved value
}
```
Basically, `&owned` passes the obligation/right to drop the value pointed to by the reference, so it is considered moved after passing it somewhere else, even though the value stays in the same place in memory.
yosh: oh, so it's like _logical move_ (ownership has changed) vs _physical move_ (the address has changed)?
eholk: Exactly!
yosh: innnnnnterestingggggg - I'll need a bit to process this I think haha
yosh: ohhhh, could this be used for like `alloca` etc? I believe it's called "box constructors" or something? Or in-place box. Ha, I forget. Placement new!
tmandry: I could see it working for `Pin`. `Pin<&owned T>`.
yosh: `Pin` is must not move
---
## Why is it not possible to write scoped async blocks?
zmitchell: Just for my understanding, I'm not clear on the first example in the post e.g. that it's not possible to write something like scoped threads. What is the safety concern that's preventing it?
yosh: Gankra's post linked at the start covers this in-depth. But the tldr is that when you call a regular function, nothing else can happen concurrently with it on the same thread. You call a function, and it runs until it returns. That's a way to hold onto references without relying on destructors.
yosh: Async functions don't run when called, they run when `.await`ed. Meaning we have space in between where you could have a reference and `mem::forget` it, which will throw the system off. We need a way to guarantee nothing else can be done with the references while they're being held in the closure, and we don't have a great way of doing that in async Rust right now. Does that... make sense?
zmitchell: Yes, thanks