[pre-RFC] linear type modifier

I appreciate the detailed proposal, but I can’t agree with it.

First off, it requires a keyword. Those are pricey and we have reserved a bunch of them already like JS did, very wasteful IMO (I fear we’ll be forever stuck with the likes of be and override). If we replace linear T with Linear<T> or #[linear] T (though we don’t have support for the latter), this becomes more like #[must_use] with stronger semantics.

Even then, I’d have to argue that the usefulness of linear types which can be consumed at will without any restriction imposable by a type, is quite limited. Or that they’re not even linear types!

The drop function is defined as fn drop<T>(_: T) {}, par excellence affine, not linear. If drop(x) works, then let _ = x; and (unless #[must_use]) {x;} also work. Kind of reminds of the “noisy drop” discussions.

I have sketched my own linear type design, it was simpler to implement than this and it worked better for my usecases (specialized consumer functions) - but it wasn’t very well received so I shelved it. I’ll embed it here for future reference:

// std::markers:
#[lang="linear"]
struct Linear;
impl Linear {
    fn consume(self) { unsafe { std::mem::forget(self) } }
}
// std::thread:
struct Thread<T> {
    // ...
    linear_marker: markers::Linear
}
impl<T> Thread<T> {
    fn detach(self) {
        // This can only be done in std::thread because linear_marker
        // is private, and partial moves are disallowed.
        let {/*...*/, linear_marker} = self;
        // Dispose of the only reason self is linear.
        linear_marker.consume();
        /* detach thread etc. */
    }
    fn join(self) -> T {
        let {/*...*/, linear_marker} = self;
        // Dispose of the only reason self is linear.
        linear_marker.consume();
        /* join thread etc. */
    }
}

Since the kinds and related markers are going away (for Copy, Sync and Send) it may make more sense to use a Linear trait here (necessary in some form if you want to allow generics to opt-in to being instantiated with linear types - another important point this proposal doesn’t cover), and impl<T> Linear for Thread<T> in a similar way to Drop. I would then guess that having at least one private field will be the only requirement to restrict consuming a linear type to the module where it is defined (which can then provide a restricted interface, as it is the case with the rest of Rust’s abstraction-building primitives).

1 Like