Hi,
I spent a portion of yesterday fighting the effect system in rustboot as
it was ... "misclassifying" (to put it mildly) types by their effect and
subsequently allocating and freeing them incorrectly. This is because at
present the effect of a type determines its storage class (GC memory or
non-GC) and things generally go sideways when you treat one class of
memory as the other. I eventually gave up and disabled the GC entirely
in the runtime (which is less serious than it sounds; it wasn't actually
working anyways).
I would, however, like to solve this problem in a more systematic way,
and I've been thinking in recent weeks how to address it. I have a
two-part proposal I'd like to run by the audience here and see if anyone
has opinions.
Part #1 of the proposal is to split the concept we're currently calling
an effect in two pieces: the "effect" concept should have a concept I'm
calling a "stratum"[1] split off and handled independently. The stratum
of a type is the class of storage it belongs to: "immutable, shareable,
pure" vs. "local, mutable, non-shareable". Where we currently say that
"every type has an implied effect", the revised statement under this
proposal would be "every function has an implied effect, every type has
an implied stratum".
Effects would retain the qualifiers "io" and "unsafe". Users could
continue to lie about the effect of a function using "auth" clauses in
the crate file. Writing to a non-local mutable field would be considered
an io action for the sake of effect calculation -- after all, mutating a
PRNG is more or less observationally indistinguishable from "doing IO"
-- but effects would strictly describe *code*, not data. If you make a
function into a first class value, of course, that value will have a
function type and that function type will have an effect. But the type
as a whole won't have an effect; only a function has an effect, and the
effect occurs when you *call* the function. You can pass the type around
all you want without worrying about its effect. Effect checking would
remain useful for helping users enforce purity and safety in their code,
of course.
The "state" qualifier would be changed to denote the stratum of a type,
and would no longer be subject to "auth" clauses; users would not be
allowed to lie about the stratum of a type. This is important, because
as it stands presently the ability to lie about state means that
*everything* that touches a mis-classified stateful value is not only
"likely unsafe" (in the "crash the system" sense) but also very
difficult -- almost impossible -- to make safe: you would have to
co-ordinate with every other reader and writer in manipulating refcounts
and malloc/realloc/free actions, and ensure that everyone agreed on
statefulness any time critical combinations of those actions occurred.
This is not something likely achievable on a whole-program basis. IOW at
the moment it's likely that if there's a single lie in your program
about "state", the whole thing is liable to crash. We're not hitting
this *much* at present only because we don't use state much and we've
been lucky to be using it in not-as-crashy ways (mutable non-box slots
and such). But in general this needs to stop happening; users have to be
honest about which storage stratum a type belongs to.
That's part #1: effect => effect+stratum, and stratum is always enforced.
Part #2 of the proposal is a bit juicier: add a third stratum back in,
"gc". We had this briefly a year or so ago, called "cyclic" back then,
but I think "gc" is a pithier qualifier. We merged the concept into
"state" when devising the current effect system, but I suspect that was
a mistake.
Under proposal-part-#2, the state and gc strata would both still be
task-local, but splitting state from gc would have three interesting
implications:
- State values could be frozen and thawed, reasonably, to and from
stateless values. State means acyclic-and-mutable, after all, so
in the singly-referenced case this would even wind up a no-op. This
is the easy case of freeze/thaw, and it'd be formally denoted in
the type system. This would provide a great utility for two idioms
we currently have no good way to support: freeze-and-apply-predicate
and freeze-and-transmit-over-channel.
- State values could also have a our structural comparison definition
extended to them. Again, acyclicality wins here.[2]
- State objects could have destructors. Acyclicality, again. Yay!
No more telling users to make artificial immutable sub-objects :)
So .. part #1 of the proposal seems necessary to get us back to
"remotely safe", and part #2 has a lot of exciting prizes associated
with it. Does anyone approve-of or object-to either part? I'm thinking
of having a go at them shortly, as the current logic in rustboot around
all this is quite a mess.
-Graydon
[1] Does anyone have a better term than "stratum" to describe the
storage category of a type? I'm almost partial to "heap" or "pool", but
that is both slightly misleading and also makes for sentences with
strange grammar: "the heap of a type". Yuck.
[2] we could even extend comparison to gc types if we say "gc types are
compared by address". It would be a little unpredictable though; I'm
tempted to define a "std.util.addrcmp[gc T](&T a, &T b) -> order" that
compares anything by address, along with a memcmp that compares by
memory-content, and make the "built-in" operators <, =, etc. structural
on acyclic types and errors when applied to a gc type. Safer, no?
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev