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]
@keean wrote:
Remember I had stated that your design proposal which you described as inspired by Ada, seemed to be similar to what I was a thinking about for a more localized (optional) borrowing without lifetime parameters for the cases where we don't need GC. One of my points of distinction was we also need optional GC (hopefully rarely so most temporary objects are freed without GC).
I still have a problem with my proposal in that there is no way to construct an iterator with a factory method by borrowing a reference to the collection without using something like a lifetime parameter to signify that the output shares the lifetime and mutability alias with the input collection. I can invert-the-control and put the code that wants to operate on the iterator inside of the iterator factory's nested block by inputting that code in as a callback function so that the constructed iterator is never returned. But this would be less composable because it would conflate the traits for different types of iterators the caller might want to employ together in the same algorithm.
Your proposed idea of allowing the caller to specify the preallocated reference for the return value doesn't solve the lifetime and mutability aliasing tracking. Rather it only solves them if everything is unboxed+cloned and is an optimization where the return value doesn't have to be cloned when assigned by the caller.
Thus afaics, I must support some way to track lifetime and mutability aliasing from inputs to outputs. But afaics there is no need for lifetime parameters, only an annotation on the borrowed input argument (GC-only input arguments
::Type
are never tracked) of the procedure (pour
andproc
) signature indicating the input tracks to the output for lifetime and mutability aliasing. I propose<-Type
, so for the implementation declaration (i.e. not in traits) that isx <-Type
.A question remains should
pure
functions allow tracking an input to an output? Is the aliasing of the lifetime and mutability a side-effect that impacts the relevant purity invariance?Note as I wrote before GC instances are always
alias
or immutableval
(because if they werevar
their lifetime is tracked via borrowing thus no need to make them GC), so the lifetime tracking (which also tracks the mutability aliasing) for an iterator factory would be applicable only when the caller is supplying a non-GC input reference for the collection.One of the big distinctions compared to Rust is I am proposing there are no moves (except for moving to a different OS thread), thus there is no need to track lifetimes from input to output for the case of moving, e.g. when adding an element to a collection. In my proposal, the add method takes a GC-only input argument for the element to be added or it takes an unboxed
*Type
which cloned (copied) to the collection and is a borrowed argument:Note that the implementation doesn't need to be type annotated because the types can be inferred from the trait declaration:
For non-boxed case, we can add new element types to the first-class union: