To get the env return a reference, to avoid copying an env that contains 500 functions and 100 variables that are hashes (database tables).
To allow returning a reference to the env without garbage collection, the env can't be reset. (= env (hash)) should return an error.
This isn't the same as one-reference-only, since there could be many references pointing to the env, one-per-thread. But all env references point to the same object the language booted with: the original env. It's many refs to one object only.
To get some symbol, make a copy, similar to MVCC and RCU.
To get h!b!c if h is on the stack do a nested get: (a) get ref to h (and free h's free ref later), (b) get ref to b (and free the ref later), and (c) copy c (and free it later). If h is the env make a copy of h!b.
To set h!b!c do the nested get from (4) and then set h!b back.
If an assignment isn't to a global variable (if h is on the stack) then no locking is needed. Set in place without a copy.
If an assignment is to a global variable (if h is the env and b is a global variable), have transactions.
In a distributed setup submit transactions in batches to a coordinator, for deterministic transactions like Calvin/FaunaDB, and replicate the global state.
(There are claims of a better solution.)
The global env hash table is marked as global. A variable inside it that is a hash table is not marked as global.
Other object types, like strings, symbols, etc. don't need this mark. Only the global env hash table needs to be marked.
Any assignment of a global variable is a transaction.
Except if in a BEGIN/COMMIT block, where the transaction block is larger.
Within BEGIN/COMMIT the thread can use local variables that are on the stack. These variables are not global: they're not part of the top-level (public) environment.
To not support global variables is to not support a top-level (public) environment: no globals = no db. It's an easier problem. It's a subset of the full problem.
Threads that want to be stand-alone private databases can have their own hash tables declared on the stack.
Variables that persist on disk (eg hash tables) can be either global (in the top-level) or local (stack-based, per-thread).