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).