So Rust has global references ownership rules, which only allow one (borrowed) mutable reference to the same object to be active in the current block scope. The objective is to prevent race conditions on the object. But from the discussion we had on the forum, a DAG tree of disjointness is not a general model of imperative programming and afaics in the general case requires dependent typing (which is known to exclude Turing-completeness). Thus I abandoned Rust's global borrowing rules, because it seems like a pita of false promises that is rarely (20% of the time?) applicable.
But I proposed a more localized use of borrow tracking which I also referred to in later posts{1} that afaics can help free temporary objects without relying on GC in many cases.
I don't intend to use my proposal to prevent race conditions due two or more borrowed mutable references existing in the same block scope (which as I stated above Rust prevents and which I think is a PL design error). In the case where all these borrows are "outer-only" immutable, my proposal can guarantee the owner has an exclusive mutable copy of the reference w.r.t. modifying the polymorphic type of the reference. This I think can be useful for the case where we want to assume the owner has the only copy of a reference to the collection in its block scope, so that my first-class unions idea can be used to add new element types to a collection without needing to require that the collection is an immutable data structure (since immutable data structures are generally at least O(log(n)) slower). In other words, we can force immutable access on a mutable data type every where except for the owner's block scope, so this gives us the best of both mutable data structures and immutability for prevent race conditions.
I was thinking about how asynchronous programming (e.g. yield
) impacts my proposal. For example, in JavaScript yield
of a Promise
will allow the current thread to run another stack which was waiting on a Promise
event which has been fulfilled and is at the top of the event queue. In this way, JavaScript is able to achieve concurrency without the slower overhead and bugs of synchronization primitives such as mutex guards. But this form of asynchronous concurrency can't create race conditions in my proposal, because the stack is not made reentrant, i.e. each asynchronous DAG of execution is on a separate stack. And my proposal will not pass borrowed references between threads of execution (in either form, neither OS threads nor asynchronous DAG of execution).
So I conclude my proposal is robust.
{1} [quote=https://users.rust-lang.org/t/inversion-of-control-resource-management/5737/41] 3: GC every where except ability to use borrow checking to track lifetimes of temporary objects to free them without burdening the GC in many cases. And copying some of v8's techniques for improving the robustness of handling temporary objects for those cases that fall through to the GC. [/quote]
[quote=https://users.rust-lang.org/t/inversion-of-control-resource-management/5737/51] Remember upthread I mentioned the idea of possibly employing borrow checking in a very limited way (that would be silent no annotations in most cases) which didn't extend its tentacles into a total ordering, yet afaics might enable to declare some references to be unique (non-aliased) mutable, so that these could be compile-time deallocated (not given to the GC) and afaics these guarantees would also eliminate the need for the immutable restriction on adding elements to collections under my proposed complete solution to the expression problem.
Thus I think the GC would be entirely out of the picture when interface to FFI in that case, but I need to dig into the details to be sure. [/quote]
[quote=https://users.rust-lang.org/t/inversion-of-control-resource-management/5737/37] I do roughly envision a possibly very significant advantage of compile-time checking the borrowing of memory lifetimes local to the function body, if that can help offload from GC the destruction of temporary objects and if it won't require lifetime parameters. And that is because thrashing the GC with temporary objects appears to be one of the most significant weaknesses of GC. That is only enforcing a partial order, and not attempting to compile-time check a global consistency which can't exist. In short, I don't want a type system that silently lies to me very often. [/quote]
[quote=https://users.rust-lang.org/t/inversion-of-control-resource-management/5737/28] That is why I am pondering about localizing the borrow checker to just function bodies, so that we can get some of its benefits without having lifetime checking grow tentacles into everything where it can't even model general semantics. [/quote]
@shelby3 wrote:
So the above changes to:
When we add a new data type to the first-class union of the self type of a collection not using an immutable data structure such as linked list (where for example we can return a new constructed head with a new type), then the compiler needs to enforce that this operation is only allowed when the caller holds the exclusive reference (non-aliased) to the collection. Instead of a
modifies
keyword as previously proposed, the method which adds elements (allowing a new data type to be added to the union type) should return the new quantified collection type, andself
should be the return value. The compiler can note that the self type is being cast to the new type, allow the modification and enforce that the result value is assigned by the caller to thevar
that holds the exclusive reference to the collection.Contrary to what I wrote previously for #3, we can't infer that assignment of a newly constructed instance of a collection requires an exclusive ownership of the reference, because a) we don't know whether the intent is to ever call the method mentioned in #1, and b) such method may be a trait and we don't know all the trait implementations in advance of using them. Do we need a syntax to indicate that a reference should be an exclusive mutable reference, which will exclude it being a GC reference? Well such a reference can be borrowed (just like all non-GC and GC references can) but distinguished from other non-GC references because it not allowed to be assigned to be to another variable in the same scope. Well this is analogous to the C restrict keyword, so it is what we'd probably prefer to be the default. So we want a syntax to indicate that a mutable non-GC reference can be aliased. Note that this syntax would never be used with
val
because they are immutable so aliasing them is harmless (if we never allow an immutable reference to be moved to a mutable reference). Thus I proposealias
instead ofvar
when aliasing is allowed.Note the compiler should check to enforce no aliasing for
var
. Thus C style pointer addition and subtraction will only be supported for a borrowedalias
and only when the referenced data structure is a non-resizeable array.A borrowed reference remains the non-annotated
Type
and a GC reference (hopefully rarer) will be::Type
. So when we write these as a function definitions (not just a signature declaration in a trait), we writex: Type
andx ::Type
respectively. This terse syntax is chosen to cut down on the noisy verbosity, as well to enforce a consistency of spacing style to improve readability. Having the compiler check the spacing and the single or double colon, is a double consistency check against typos. When writing a local GC variable and we want to infer the type, we write for examplealias x ::= ...
. Note that GC instances are always eitheralias
orval
, nevervar
becausevar
implies we can track the lifetime, so useless to make it GC. Note from #2 that borrowing is orthogonal to aliasing in that borrowed references can alias if for example all of them (including the source borrowed from) arealias
(orval
). Other scenarios include source isvar
and the borrowed references in a nested block scope arealias
. Thus the way to construct multiple mutate iterators from avar
reference to a collection, is to create a nested block scope (or function which is also a nested blocked scope) toalias
the reference.We've added three types of subroutines:
pour
,proc
, andpure
. Input arguments are alwaysval
for pure functions and they may not be optionally prefixed withvar
noralias
. Input borrowed arguments forpour
andproc
arevar
by default and may be optionally prefixed withalias
orval
. Input GC arguments forpour
andproc
arealias
by default and may be optionally prefixed withval
but nevervar
.