---
# System prepended metadata

title: 'Meetup 2024: Linear types'

---

---
title: "Meetup 2024: Linear types"
tags: ["T-lang", "design-meeting", "minutes"]
date: 2024-09-10
url: https://hackmd.io/W50y43HnRQifRNeoGcVUag
---

# Meetup 2024: Linear types

## what we think we want ("more or less")

* types you can create and which you must move
* one way to move is to deconstruct into field values
* another common way is to call a `self` method (which can in turn deconstruct)

one thing to clarify: async drop would desugar in, Niko's opinion

## what tyler wants

* scoped async tasks
* better io-uring, DMA, and similar APIs
* APIs where you have to decide how to dispose (e.g., detach or cancel)
* async drop probably

## What josh wants

- transactions that must be either committed or rolled back, but can't be dropped
- scoped async tasks if we get those for free along the way

## what niko wants

tyler + drop-with-parameters + guarantee-stuff-will-execute

(e.g. requiring initialization for direct memory access (DMA))

oh and btw it's annoying to have to do `std::mem::take()` in `Drop`

## must-move

In the below, the distinction between "Destruct" and "Drop" is meant to be that Destruct represents a failure to move/dispose of the value on the non-unwinding return path (while `Drop` encodes the more general case of a destructor that is run for both unwind and non-unwind paths). (See further discussion regarding panics in sections below.)

```rust!
mod dma {
    pub struct DmaHandle {
        f: DmaHandleInner    
    }

    // MustMove opts out of drop glue (aka !Destruct).
    impl MustMove for DmaHandle {
    }

    impl DmaHandle {
        pub fn dispose(self) {
            let DmaHandle { f } = self;
            operating_system.cancel_request(f);
        }
    }
}

fn foo() {
    let dma = DmaHandle::new();
    
    // ERROR: dma not moved
}

fn foo() {
    let dma = DmaHandle::new();
    dma.dispose();
}

fn generic<T>(t: T) {
    // `T: Drop`
}

fn baz() {
    generic(DmaHandle::new());
    // ERROR: T does not implement Destruct
}

fn generic<T: MustMove>(t: T) {
    // error
}

fn passthrough<T: MustMove>(t: T) -> T {
    t
}

trait Dispose: MustMove {
    fn dispose(self);
}

fn fails_to_dispose<T: Dispose>(t: T) {
    // ERROR: T does not implement Destruct
}


fn dispose<T: Dispose>(t: T) {
    t.dispose();
}
```

## what will be annoying?

```rust
// by itself can't really use MustMove with things like this

trait Iterator {
    type Item;
}
    
impl<T> Iterator for Vec<T> {
    // requires: T: Destruct
    type Item = T;
}
```

### how does you get async-drop?

key point is that MustMove will ensure you never pass an async-drop value into a sync fn that then drops it; the MustMove constraint will keep that from happening.

sugar target for async-drop 

### control-flow operations

`?`, `break`, `return`

partial answer:

do-finally, defer

### panics

what do you do with a panic?

abort?

lint, suggest defer?

can decide per type?

customize it?

### etc

## use patterns

* ABSOLUTELY must call
    * DMA that is not cancellable
* Absolutely must call but can recover
    * 
* Must call on happy path (but on panic, not so important then)
    * Database transaction
    * Structured concurrency (need to explicitly detach or cancel so there are no surprises in the happy path)
    * Decrement counter (but datastructure is not unwind-safe; recovery on panic is a no-op)
* Can drop
* Force:

```rust
#[non_exhaustive]
pub struct CallEnd { }

impl MustMove for CallEnd { }

pub struct Context { }

impl Context {
    fn begin(&mut self) -> CallEnd {
        CallEnd { }
    }
    
    fn end(&mut self, r: CallEnd) {
        let CallEnd {} = r;
        
        // ...
    }
}

```
## edge cases and things that are hard

### panic

c.f. boat's post about [try-finally](https://without.boats/blog/asynchronous-clean-up/)

### backcompat for associated types

Solutions...

* Can have trait transformer that relaxes these implied bounds, like `MustMove Iterator`
* Can do the same thing as `Pin`

```rust!
trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}

trait DerefMut: Deref {
    fn deref_mut(&mut self) -> &mut Self::Target;
}

// this would break if we changed
// Target to be MustMove (grr)
fn compare<D>(boxed: D, other: D::Target) -> bool
where
    D: Deref,
    // ERROR no `D::Target: MustMove` bound
{
    *boxed == other
}
```

```rust
fn swap_ref<R: DerefMut>(a: R, b: R) {
    std::mem::swap(&mut *a, &mut *b)
}
```

Digression: Could effects provide an escape hatch to get us out of the above morass with `Deref::Target` implicitly being `!MustMove`:

What does `MustMove Deref` mean?

```rust
trait MustMove Deref {
    type Target: ?Sized + MustMove;
    ...
}
```

Associated effects/bounds mapping:

```rust
trait Deref {
    bound E = MayMove; // Default effect.
    type Target: ?Sized + Self::E;
    ...
}

struct Droppable;
impl Deref for Droppable {
    type Target = Droppable;
    ...
}

struct NotDroppable;
impl MustMove for NotDroppable;
impl Deref for NotDroppable {
    bound E = MustMove;
    type Target = NotDroppable;
    ...
}
```

```rust
trait Deref {
    trait TargetBound = MayMove; // <-- associated trait aliases are "easy-ish"
    type Target: ?Sized + Self::TargetBound;
}

fn foo<trait T>() {
    // ^^^^^^^ these are awfully hard, have to 
    // infer values for them,
    // pick impls based on them,
    // etc
}

fn some_generic_code<
    D: Deref<trait TargetBound = MustMove>
>(d: Box<D>) {
    let value = d.into_inner();
    // ERROR: must move value
}
```

```rust
pub enum Option<T: MustMove> {
    Some(T),
    None
}

impl<T: MustMove> Option<T> {
    pub fn map<U: MustMove>(
        self,
        value: impl FnOnce(T) -> U,
    ) -> Option<U> {
        match self {
            Some(t) => Some(value(t)),
            None,
        }
    }
}

fn something<X: MustMove>() {
    let v: Option<X> = None;
}
```

#### sketching out a pointer-as-proof mechanism

```rust
let x: Transaction = get_must_move() defer {
    x.dispose()
};
let r: &move mut Transaction = &mut x;

impl Transaction {
    pub fn 
}
```

### working out async drop



### unforgettable

```rust
trait Unforgettable /*!Leak*/ {}
```

^ May want `Unleak<T>`: https://zetanumbers.github.io/book/myosotis.html#the-unleak-wrapper-type

* can't use with `forget`
* can't put into an `Rc` or `Arc` (because cycles)

advantages:

* "less" (achieves fewer goals, notably not async-drop or transactions)

disadvantages:

* "less" (does less stuff)
