Open evincarofautumn opened 8 years ago
Here’s an alternative design, which seems better:
If a variable is mentioned multiple times, it needs the clone
trait. If using the default clone
implementation, the value is copied implicitly with memcpy
. If using a custom clone
implementation, or if a field has a custom clone, then each appearance of the variable (except for one?) must be followed immediately by a call to clone
(or perhaps a special copy
operator, so we have more information). Primitives would all be implicitly copyable.
As in the mini-kitten reference typechecker, it would also be possible to allow explicit moving with e.g. a move
operator, which would cause a variable to go out of scope.
If a variable is mentioned zero times, it needs the drop
trait. Even though I like the idea of explicit copy constructor calls, I’m loath to require explicit destructor calls.
This will be easier to implement if we do a transformation that we need to do anyway: lifting the relative (De Bruijn) indices of locals into absolute indices. With this, we can track non-nested variable lifetimes, and also allocate/deallocate space for all locals on function entry/exit.
In the new compiler, there is a “linearisation” pass that introduces calls to copy constructors (when a variable is used multiple times) and destructors (when a variable is not used):
This is intended to support smart resource-management types in the common vocabulary, such as:
File
—a file handleShared<T>
,AtomicShared<T>
,CyclicShared<T>
,IncrementalShared<T>
—reference-counted smart pointers with different counting and reclamation strategiesList<T>
,Cow<T>
—copy-on-write data structuresMutex<T>
—a mutex-protected resourceGc<T>
—a garbage-collected pointerThese traits might have types like:
But this raises a few issues:
+IO
.This is a lot of machinery, and I’m not sure how to deal with the last point. There are a few possibilities:
with (+IO)
. This is basically the same as (2), but explicit.None of these is ideal.
In addition, destructors interact poorly with tail-call optimisation. A call in tail position might not be TCO’d if there’s an implicit destructor call after it, which breaks the expectation of guaranteed TCO.
So the question is: do I keep this and try to work through whatever issues arise in practice, or do I scrap it in favour of some more functional approach to resource management?