package main
import (
rwboards "@gno.land/demo/boards"
rboards "gno.land/demo/boards"
ravl "gno.land/demo/avl"
)
func Panics1() {
rboards.NewBoard(...) // panics, as it modifies staet
}
func Render(r string) {
// proxied render, with no risk of side-effects
// even if called through maketx call
return rboards.Render(r)
}
var (
ourRealm = chain.CurrentRealm()
s int
)
func OnlyAsReadWrite() {
s = 0
// we now this has definitely been called as a read/write function.
}
func OnlyAsReadOnly() {
if chain.CurrentRealm() == ourRealm {
panic("this function can only be called as a read-only function")
}
}
There are two connections that can be made to any given package: one that can be considered "safe" in most scenarios (memory allocation, side effects, coin spending).
Most imports should happen as read-only, even of other apps that are actually meant to be realms, unless the intent is to explicitly write to the realm. It should be OK to even have a rw import and a read import if necessary, so that only the write functions are called in a "privileged" manner.
If a function is exported in a package, then it should generally be considered safe to be used as an API, both read-write and read-only, and expected for others to do so. One of the two cases can be prevented, though.
The aim is to implement most of the ideas and constraints from the interrealm proposal without the introduction of a new built-in function.
There are two kinds of import paths: read-only and read/write. A read-only import requires that no modification happen on real values at all when its functions are executed.
When a function is called through a read-only import, any assignment to a real value panics.
Read-only imports may modify real values if they're given through parameters or pointer receivers. Calls to initialize a banker panic. Accessing a real banker panics.
Calling a function will keep being read-only, even if accessed through a read-write import, unless it is in the same realm as the current realm. If a function is referred through a closure, the FuncValue must be unreal.
In a read-write import, all code can be called, and assignments to top-level functions will work as normal.
A realm may not persist a value already stored in another realm (no "escaping"). It may persist a way to quickly reference it, like a string ID.
A realm may not persist closures returned from packages imported as read-only.
A realm boundary is crossed:
- When calling a function on a Read/Write import.
- When calling a method on a real receiver, != the current realm.
- When calling a closure declared in another realm, imported as a read-write import.
There is a new package, realms
, which provides some utility mechanisms:
realms.Attach(any)
Attach the given unreal value to the calling realm.
Must be a pointer.
realms.AsConsumer(f func())
Execute f() as a cross-realm function, to call functions
in the same realm as a consumer.
realms.Owner(any) Address
Determine the owner of the given value.
This can allow to implement further object safety checks if a realm needs them.
I like the idea. It clearly defines the boundaries between realms and allows more explicit use cases when changing the owner of a type.