Why can't you PROTECT individual cells in an array?

You can PROTECT a variable:

>> x: 10

>> protect 'x

>> x: 20
** Access Error: variable x locked by PROTECT (see UNPROTECT)

And you can PROTECT an array pointed to by a variable:

>> array: [<a> b #c]

>> protect array

>> append array ["d"]
** Access Error: series read-only due to PROTECT (see UNPROTECT)

>> array: [<q> r #s]  ; variable holding array not protected

>> array
== [<q> r #s]

...but you can't PROTECT a cell resident inside an array:

>> array: [<a> b #c]

>> protect 'array.2  ; this doesn't work

>> array.2: "z"
; you'd expect an error here if it did work

>> array.3: 1020
; you'd expect no error here if it did work

R3-Alpha didn't have the feature, and Ren-C hasn't added it. Why?

Making This Work Would Add Significant Complexity

Your first thought (well, mine) would be that this is about efficiency. e.g. if you were going to write something like clear block, the existence of PROTECT'd cells inside that block would mean you'd have to visit all the cells before knowing if it was okay to clear it. Other code that already has to visit the cells in order to copy them would still pay a little more too... like take/part.

But that's not where the real problem is. Paying a little more for mutating operations is only slightly relevant. The real problem has to do with a pointer to a cell not being able to answer the question of its own mutability--since it can't speak for the container's protection status.

In more detail:

Ren-C's clever management of immutability uses the C const designator. What the const means on a series is that the series may be mutable, but hasn't had a runtime check yet to flip it over to being readable. And what the const means on cells is that the cell was extracted from a series that may be mutable, but hadn't had the runtime check applied.

This way, casual code that just messes around reading cells and series can be written easily, using const pointers and not paying for runtime checks. And you can be certain of not skipping the runtime checks in the places where you want to get mutable access. (R3-Alpha was full of bugs from "just forgetting".)

The problem with getting a granularity of cell-level mutability is that right now, there's just one runtime check that gives you a mutable cell pointer...done at the array level:

Cell* tail;
Cell* cell = VAL_ARRAY_AT_ENSURE_MUTABLE(&tail, block);

There are multiple checks done, here. The BLOCK! cell passed in may or may not carry a CONST cell bit--which is to say, that particular reference to the BLOCK! might be const (even if the underlying array hasn't been PROTECT'd). And then, there's the protection status of the array...which can also become protected during enumerations of the array, or if it is executing currently in the evaluator.

If this code doesn't trigger a failure from discovering a form of mutability, then the non-const Cell* is given back.

But then...the code will increment that cell pointer to find other cells in the array. And incrementing a mutable pointer gives you another mutable pointer.

It may seem we could make Cell(*) in the C++ debug build a smart pointer class that gives you back a Cell(const*) when incremented. And then say that there's a way to promote a const cell reference to a mutable cell reference with a runtime check.

But this would be deceptive. We already know that if you have a const reference to a cell that lives in the middle of an array, that a runtime check of the containing array for that cell hasn't been done yet.

In other words: once you have your hands on a cell pointer, it's too late to ask about that cell's mutability. The knowledge is up to the container.

Trying to solve this would be a mess. e.g. making a kind of Cell(const*) that was the size of two pointers, but held a reference to the array the cell was resident in...in case you wanted to do the runtime check asking for a promotion to a Cell(*).

I've been moving in the other direction...paring away complexities in the source where possible. Something like that doesn't belong in the codebase--especially to implement a feature that I can only vaguely imagine uses for.

So Don't Expect Cell-Level PROTECT...Ever

Every now and again I've looked at the flag and thought "why does this apply only to variable cells?" So I wanted to hammer out the reasoning, and confirm that it wasn't just about performance.

From my point of view, it's about creating a situation of a flag that needs to be checked, and which the code has no (reasonable) systemic way to enforce that checking.

1 Like