Skip to content

Instantly share code, notes, and snippets.

@thehowl
Created April 19, 2025 15:18
Show Gist options
  • Save thehowl/0a988272ec6ae931ee6805b7c90d95e9 to your computer and use it in GitHub Desktop.
Save thehowl/0a988272ec6ae931ee6805b7c90d95e9 to your computer and use it in GitHub Desktop.

Privileged Imports

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")
	}
}

ground ideas

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.

Imports

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.

Read-Only import

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.

Read-Write Import

In a read-write import, all code can be called, and assignments to top-level functions will work as normal.

Persistence

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.

Realms

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.

@ajnavarro
Copy link

ajnavarro commented Apr 22, 2025

I like the idea. It clearly defines the boundaries between realms and allows more explicit use cases when changing the owner of a type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment