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]
For
fold
,map
, and the inversion-of-control iterators, we would still need the third of the aforementioned bulleted variants of mutability in the case where we don't just want to replace elements (e.g. sort them ormap
in place), but mutate the elements safely checking that we won't mutate the collection in an incompatible way. We want to distinguish the second of the aforementioned bulleted variants of mutability from the act of replacing instances of a type parameter.The benefit of inversion-of-control iterators compared to factory constructed iterators, since the iterators are borrowed they can't leak out of the encapsulation. With factory constructed iterators, we need to track returned lifetimes.
But inversion-of-control iterators not only will require complex inlining to be efficient, they also have a more complicated composability.
I am thinking instead we could keep iterator factories and make the assignment of
self
to returned instance a special case, so then we don't need generalized lifetime parameters. We only need the compiler to check thatself
is an argument of a constructor whose instance is the result value. We could annotate the result type with#
s and/or suffix withfn(...):Type:!TraitBound1|TraitBound2|...
to indicate to the compiler that we wish the result to be a borrow onself
. So then the caller is forced to borrow it. Note the relevant edit I made:@shelby3 wrote:
Note that the
#
and/or!TraitBound1|TraitBound2|...
restrictions don't prevent our code from invoking some code that would interfere with for example a sort by instancing new iterators. And we can't have our iterator factories restrict the caller from calling iterator factories. The algorithm (e.g. sort) will need to require that the function signatures it accepts as input callbacks, include the iterator factory trait on the!TraitBound1|TraitBound2|...
list. This is what makes this solution work wherein Rust can't enforce such a restriction if there are overlapping mutable slices and thus Rust is unsafe in that case. Perhaps all algorithms can be implemented most efficiently without overlapping mutable slices? I doubt it! General imperative programming doesn't conform to a DAG tree of disjointness. Thus my model appears to be superior to Rust's. I think I should post about this on the forum.Edit: thinking more about this, the
#
restriction is absolutely required, because otherwise without Rust's global mutability tracking, there is no way to stop an element instance from having a reference to the collection and mutating the collection bypassing our!TraitBound1|TraitBound2|...
restriction. So I can't think of any safe way to mutate the elements of a collection in place. Even cloning would not be safe, thus#
is not sufficient unless it requires pure functions turtles all the way down!Tangentially I think it is also important to note that result values will normally be
fn(...): Type
when the result value was not explicitly constructed as a GC instance (which isn't necessary unless the body of the function stores a copy of the reference as a side-effect). Yet the caller can assign to GC instance, meaning the caller will call one of two versions of the same function, one which inputs the stack location to build the instance and the other where the function allocates on the GC heap.