Building a mainstream language is extremely expensive these days, to the point that probably only a handful of companies can attempt it. I work in Scala which teeters on the edge of mainstream, and it's not the language itself that needs major backing, it's all the other stuff - IDEs, library repositories, build tools, profilers. I would love to leap into e.g. Idris, which does have linear types, but who's going to write an IDE for it? Even Rust struggles with this.
And it's very hard to retrofit linear types onto an existing language, if you can even get a quorum in favour of doing so. Haskell is trying, and I wish them well, but it's not going to be easy, and again they're barely mainstream.
The Rust language server seems really good. What have you been missing there?
On June 27, 2016, Microsoft announced a collaboration with Red Hat and Codenvy to standardize [Language Server Protocol]'s specification
— https://en.wikipedia.org/wiki/Language_Server_Protocolvs
initial
Date: 2017-12-21 22:25:45 +0300
— https://github.com/rust-lang/rust-analyzer/commit/a63222cd24...1. In RC and tracing GC languages, a linear object would need exactly one special "linear" reference to it, which would be responsible for eventually "consuming" (though not deleting) the object by handing it to its final destination function. (You can tell, I'm trying real hard to not say destructor here)
2. In single-ownership languages like C++, Rust, etc. we've been conflating the destructor to handle two things: fulfilling the object's final purpose, and handling exceptions/panics. It seemed convenient at the time, but it also prevented linear types.
But honestly, I think we were just stuck in a local maximum. There was no reason to change those points because we didn't know about higher RAII, and we didn't know about higher RAII because those two points prevented linear types.
But with more of us exploring this new area (Vale, Austral, maybe even Mojo!), I daresay we'll one day see linear types and higher RAII entering the mainstream. Especially if someone can come up with a better name!
PS. Congrats! https://verdagon.dev/blog/easter-egg-notes
Well, from a transactional point of view, a good design for C++ classes is to have implicit destructors (i.e. a conventional C++ destructor) rollback any change, while explicit ones would commit. This works very well with exceptions.
What C++ lacks is the ability to statically prevent you from calling an explicit destructor twice, but should be doable, for example, in rust.
* Do you assume trivial moves? Because that's not a safe assumption. Related, many examples only really work for local variables, and otherwise rely on special language object holders or something, which even if they can also be implemented in user code often break safety elsewhere.
* What, exactly, happens to your objects when a task unwinds?
* What do you do about leaked cycles?
* Inheritance is no longer a simple part of the language. And trust me, you do want inheritance.
It's not that these questions don't have possible answers (a few of which are touched on in the linked article), but there are no easy answers.
Even if a language really wanted to have linear types in reference-counted objects, I don't think we'd see cycles in practice. Generally, for a reference-counted object to contain a linear type, we'd need exactly one reference to be the special "linear reference" (think a linear flavor of unique_ptr) which has ultimate responsibility for "consuming" (albeit not deleting) the contents. Making a unique_ptr cycle is much harder than making a regular RC cycle, so I'm not too worried, even if it is theoretically possible.
Re: trivial moves, I'm not sure what you mean. The system works through the entire program, not just local variables. Also, the `onException` method would also handle panics. Hope that helps!
You don't need RC to have a cycle - it already occurs with a pair of structs that refer to each other using `mut Option[Unique[T]]`. Rust seems to have settled for "document it aggressively, and otherwise ignore it" ... yet code very similar to this is actually very useful, so (with a few extra layers of nesting) it often does appear spontaneously in the wild. But the whole point of linear types is that you can no longer just say "leaks are okay I guess".
Moving even a small 64KB buffer isn't actually trivial even if your type system thinks it is by composition, but most linear-ish type systems seem to aggressively use destructuring (possibly via pattern-matching) which relies on moving the members. Not to mention all the other actually-useful things C++ move constructors can do (update peer objects, for example). While "move-by-default" is a definite winner, "trivial-move-only" is not. The question of "what exactly happens so I can move out of a language-defined Option[T], leaving None ... vs how can a user-defined variant type choose to do something similar" is significantly complicated by nontrivial moves.
I don't miss it.
> languages where primitives are special
Isn't the object-primitive separation a design flaw? (e.g. c++ can have lists of int, but Java cannot. In Java, sometimes a boolean has two values and sometimes 3, etc.)
> root of the hierarchy
Why a hierarchy? All beginner Java devs at some point attempt to put their domain objects into some kind of hierarchy before realising the futility of it. Especially in game dev.
Sometimes I want to operate on objects which can quack(), and sometimes I want to operate on objects which can toBytes(), and sometimes I want to operate on objects which can do both those those things. But why must they have a common ancestor? Why can't they simply exist in disjoint families?
> I'm increasingly convinced that it is a useful idea to have an explicit `Object` (or `BaseObject`, to deal with languages where primitives are special) class at the root
This is one of those things I dislike about Java. Object has a bunch of included behaviours that I do not want. equals() and hashCode() have the wrong default behaviour, and toString() isn't much better. I would much prefer the compiler to block me if I try to equate two things which haven't defined equality, rather than slip in its own implementation.