# Weekend discussion: actor\* for motoko

**URL:** <https://forum.dfinity.org/t/weekend-discussion-actor-for-motoko/75715>\
**Category:** Motoko\
**Tags:** Discussing, community-consideration\
**Created:** [September 19, 2026, 9:37pm UTC](https://forum.dfinity.org/t/weekend-discussion-actor-for-motoko/75715 "2026-09-19T21:37:16Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![skilesare](https://sea1.discourse-cdn.com/flex023/user_avatar/forum.dfinity.org/skilesare/32/5609_2.png) [@skilesare](https://forum.dfinity.org/u/skilesare)\
**Post date:** [September 19, 2026, 9:37pm UTC](https://forum.dfinity.org/t/weekend-discussion-actor-for-motoko/75715/1 "2026-09-19T21:37:16Z")

</div>

# `actor*`: many addressable actors inside one canister

_Experimental, in the Moxzi compiler. I’d like reactions to the shape of it before it settles._

## The gap

You’re building something with many independent entities — accounts, rooms, agents, ledgers,  
inventories. Today Motoko gives you two choices, and neither fits:

- **A `class`.** Cheap and simple, but an instance is invisible from outside the canister. It has no  
address, nobody can call it, and it’s just heap state you index into.
- **A canister per entity.** Addressable and independently upgradable, but you pay a canister’s cost  
and operational weight for every single one.

`actor*` sits between them: an instance that lives inside one canister’s heap, but has its own  
principal, its own Candid methods, and its own upgrade story.

## The whole thing

```motoko
import Actor "mo:local-actors/Actor";
import Principal "mo:core/Principal";
import Array "mo:core/Array";
import Error "mo:core/Error";
persistent actor World {
  // `creator` is an ordinary constructor argument the application chooses to keep -- nothing
  // the language provides. (The language-level `Actor.container(me)` is the container, World.)
  public persistent actor* class Counter(creator : Principal, initial : Nat) = me {
    var n = initial;

    // Internal: a computation. Callable from anywhere in World with `await*`; never a message.
    public func internal_add(amount : Nat) : async* Nat { n += amount; n };

    // External: the actor pattern. `msg.caller` is the real caller of the container message.
    public shared(msg) func add(amount : Nat) : async Nat {
      if (msg.caller != creator) throw Error.reject("only the creator may add");
      await* internal_add(amount)
    };
    public query func read() : async Nat { n };
  };

  var counters : [Counter] = []; // held directly, and they outlive an upgrade
  public shared(msg) func create(initial : Nat) : async Principal {
    let c = Counter(msg.caller, initial); // the creator is the message caller
    counters := Array.concat(counters, [c]);
    Actor.id(c) // the outward spelling of a reference
  };
  public func total() : async Nat { // local composition: inline, one segment
    var t = 0;
    for (c in counters.vals()) { t += await* c.internal_add(0) };
    t
  };
};

```

Three things to notice:

1. **It’s declared and used like a class.** `Counter(msg.caller, initial)` constructs one; you hold  
instances in ordinary values like `counters`.

2. **It has an address.** `Actor.id(c)` is a principal you can hand out, store, and later turn back  
into a reference. A stale or retired one fails closed rather than silently hitting something else.

3. **Methods come in two kinds** , and this is the core of the design:

## Callable from outside

The container’s Candid interface is generated for you:

```auto
service : {
  Counter_add: (id: principal, amount: nat) -> (nat);
  Counter_read: (id: principal) -> (nat) query;
  create: (initial: nat) -> (principal);
  total: () -> (nat);
}

```

A subactor’s external methods show up as `Class_method(id, args…)`. Anything that can call a  
canister can call a subactor — dfx, an agent, another canister — by passing the principal it was  
handed. `msg.caller` inside `add` is the real caller of that message, which is why the  
authorisation check in the example does what it looks like it does. The compiler also emits a small  
client module so a calling canister writes `Counter(service, id).add(5)` rather than spelling the  
façade by hand.

## It survives upgrades

The example above is already the durable form: `Counter` is a **`persistent actor* class`** and  
`counters` is an ordinary stable variable. After an upgrade the instances are still there with  
their state, and the principals you handed out still name the same ones. Callers outside the  
canister do not have to be told anything, and you do not rebuild the population from a side table  
of identifiers.

Adding a method to the class does not disturb that. If the class’s _fields_ change you give it a  
migration function, exactly as you would for an actor, and every live instance is migrated.  
Instances can carry `system func preupgrade` and `postupgrade` of their own.

## What it costs

A call into a subactor is about **2,900 instructions** against about **280** for a plain class  
method — roughly 11x — and it allocates nothing per call. Each live instance costs about **744  
bytes** on top of its own fields.

So the honest advice: **if all you want is encapsulated state that rolls back with the message, use  
a `class`.** It’s an order of magnitude cheaper and it’s the right tool. Reach for `actor*` when you  
need at least one of:

- an address you can hand out and validate later,
- entities that outlive an upgrade with their own migration,
- entities callable from outside the canister.

It suits a large, mostly-idle population well: a hundred thousand instances is roughly 75 MB and  
costs nothing until called. It does not suit a hot loop that touches every entity every tick — for  
that, a class and a flat array will beat it comfortably.

## Status

Experimental and behind a flag, in Moxzi — a Motoko compiler that self-hosts. Not in upstream `moc`.  
The example above is compiled and run as a test fixture, and so is every claim this post makes about  
it; the post is generated from that fixture, so the code here is code that ran.
