diff --git a/.sources/VERSIONS b/.sources/VERSIONS index 39be1de1..34c852bf 100644 --- a/.sources/VERSIONS +++ b/.sources/VERSIONS @@ -38,5 +38,5 @@ # appear in docs before they ship to users. # ------------------------------------------------------- -motoko v1.16.1 01aee06 +motoko v2.0.0 1bd3011 internetidentity release-2026-09-25 64ba1637 diff --git a/.sources/motoko b/.sources/motoko index 01aee069..1bd30111 160000 --- a/.sources/motoko +++ b/.sources/motoko @@ -1 +1 @@ -Subproject commit 01aee0697be8c5afc0cd2dd046198b32a279dd81 +Subproject commit 1bd3011186ed6ee3d12c12735acb9ffd1e6f475b diff --git a/docs/languages/motoko/base-core-migration.md b/docs/languages/motoko/base-core-migration.md index b9e05710..d9f2e774 100644 --- a/docs/languages/motoko/base-core-migration.md +++ b/docs/languages/motoko/base-core-migration.md @@ -86,7 +86,6 @@ The following modules have been **removed** in the core package: - `AssocList` - Use `Map` or `pure/Map` instead - `Buffer` - Use `List` or `VarArray` instead -- `ExperimentalStableMemory` - Deprecated - `Hash` - Vulnerable to hash collision attacks - `HashMap` - Use `Map` or `pure/Map` - `Heap` - Use `Map` or `Set` instead diff --git a/docs/languages/motoko/fundamentals/actors/actors-async.md b/docs/languages/motoko/fundamentals/actors/actors-async.md index 3388e0b9..b3c4624a 100644 --- a/docs/languages/motoko/fundamentals/actors/actors-async.md +++ b/docs/languages/motoko/fundamentals/actors/actors-async.md @@ -223,7 +223,7 @@ Customers place orders at a pizza restaurant, but the chef can only make one piz import Array "mo:core/Array"; import Text "mo:core/Text"; -persistent actor PizzaParlor { +actor PizzaParlor { var orders : [Text] = []; public shared func placeOrder(order : Text) : async Text { @@ -289,7 +289,7 @@ Use `async*` and `await*` carefully. In Motoko, a regular `await` is a commit po ### Example ```motoko no-repl -persistent actor class (Logger : actor { log : Text -> async () }) { +actor class (Logger : actor { log : Text -> async () }) { var logging = true; diff --git a/docs/languages/motoko/fundamentals/actors/compatibility.md b/docs/languages/motoko/fundamentals/actors/compatibility.md index 382ff27d..51be818a 100644 --- a/docs/languages/motoko/fundamentals/actors/compatibility.md +++ b/docs/languages/motoko/fundamentals/actors/compatibility.md @@ -11,7 +11,7 @@ When upgrading a canister, it is important to verify that the upgrade can procee - Breaking clients due to a Candid interface change. `dfx` checks these properties statically before attempting the upgrade. -Moreover, with [enhanced orthogonal persistence](./orthogonal-persistence/enhanced.md), Motoko rejects incompatible changes of stable declarations. +Moreover, thanks to [enhanced orthogonal persistence](./orthogonal-persistence/enhanced.md), Motoko rejects incompatible changes of stable declarations. ## Upgrade example @@ -21,8 +21,8 @@ The following is a simple example of how to declare a stateful counter: ``` Importantly, in this example, when the counter is upgraded, its state is preserved and the counter will resume from its last value before the upgrade. -This is because actor variables are by default `stable`, meaning their state is persisted across upgrades. -The above actor is equivalent to using an explicit `stable` declaration: +This is because actor variables are persisted across upgrades, by default. +The above actor declaration shows the field without an explicit qualifier, which is the same as writing nothing at all: persistence is the default. ```motoko no-repl file=/count-v1stable.mo ``` @@ -120,7 +120,7 @@ This version is neither compatible to stable type declarations, nor to the Candi - The change in the return type of `read` is also not safe. If the change were accepted, then existing clients of the `read` method, that still expect to receive integers, would suddenly start receiving incompatible floats. -With [enhanced orthogonal persistence](./orthogonal-persistence/enhanced.md), Motoko actively rejects any upgrades that require type-incompatible state changes. +Thanks to [enhanced orthogonal persistence](./orthogonal-persistence/enhanced.md), Motoko actively rejects any upgrades that require type-incompatible state changes. This is to guarantee that the stable state is always kept safe. @@ -133,9 +133,7 @@ In addition to Motoko's runtime check, `dfx` raises a warning message for these Motoko tolerates Candid interface changes, since these are more likely to be intentional, breaking changes. :::danger -Versions of Motoko using [classical orthogonal persistence](./orthogonal-persistence/classical.md) will drop the state and reinitialize the counter with `0.0`, if the `dfx` warning is ignored. - -For this reason, users should always heed any compatibility warnings issued by `dfx`. +Users should always heed any compatibility warnings issued by `dfx`. ::: @@ -179,16 +177,18 @@ The code for the migration function is self-contained and can be placed in its o The migration function takes a record of stable fields as input and produces a record of stable fields as output. -The input fields extend or override the types of any stable fields in the actor's -stable signature. -The output fields must be declared in the actor's stable signature, and have types that can be consumed by the corresponding declaration in the stable signature. - -* All values for the input fields must -be present and of compatible type in the old actor, otherwise the -upgrade traps and rolls back. -* The fields output by the migration -function determine the values of the corresponding stable variables in the -new actor. +* Each input field names a stable variable of the old actor. Its value must be +present and of compatible type in the old actor, otherwise the upgrade traps and +rolls back. The input type is checked against the old actor only: the new actor +may declare the variable with a different type, or not at all. +* An input field is consumed: its old value is not transferred to the new actor. +* Each output field must be declared in the new actor, with a type that can be +consumed by that declaration. The output value becomes the variable's value in +the new actor. +* A consumed field that the new actor declares but the migration function does +not produce is initialized by running its initialization expression, like a +newly declared field (warning M0206). A consumed field that is neither produced +nor declared is dropped (warning M0207). * All other stable variables of the actor, i.e. those neither consumed nor produced by the migration function are initialized in the usual way, either by transfer from the upgraded actor, if declared in that actor, or, if newly declared, @@ -367,11 +367,7 @@ cannot be consumed at new type var Float ``` -With [enhanced orthogonal persistence](./orthogonal-persistence/enhanced.md), compatibility errors of stable variables are always detected in the runtime system and if failing, the upgrade is safely rolled back. - -:::danger -With [classical orthogonal persistence](./orthogonal-persistence/classical.md), however, an upgrade attempt from `v2.wasm` to `v3.wasm` is unpredictable and may lead to partial or complete data loss if the `dfx` warning is ignored. -::: +Thanks to [enhanced orthogonal persistence](./orthogonal-persistence/enhanced.md), compatibility errors of stable variables are always detected in the runtime system and if failing, the upgrade is safely rolled back. ## Adding record fields @@ -402,8 +398,7 @@ cannot be consumed at new type Do you want to proceed? yes/No ``` -It is recommended not to continue, as you will lose the state in older versions of Motoko that use [classical orthogonal persistence](./orthogonal-persistence/classical.md). -Upgrading with [enhanced orthogonal persistence](./orthogonal-persistence/enhanced.md) will trap and roll back, keeping the old state. +It is recommended not to continue: the upgrade will trap and roll back, keeping the old state. Adding a new record field to the type of existing stable variable is not supported. The reason is simple: the upgrade would need to supply values for the new field out of thin air. In this example, the upgrade would need to conjure up some value for the `description` field of every existing `card` in `map`. Moreover, allowing adding optional fields is also a problem, as a record can be shared from various variables with different static types, some of them already declaring the added field or adding a same-named optional field with a potentially different type (and/or different semantics). diff --git a/docs/languages/motoko/fundamentals/actors/data-persistence.md b/docs/languages/motoko/fundamentals/actors/data-persistence.md index 8e3eea32..80ef044d 100644 --- a/docs/languages/motoko/fundamentals/actors/data-persistence.md +++ b/docs/languages/motoko/fundamentals/actors/data-persistence.md @@ -7,36 +7,33 @@ sidebar: One key feature of Motoko is its ability to automatically persist the program's state without explicit user instruction. This is called **orthogonal persistence**. Data persists across transactions and canister upgrades. -Motoko data persistence is not simple, but it prevents data corruption or loss while being efficient at the same time. No database, stable memory API, or stable data structure is required to retain state across upgrades. Instead, a simple `stable` keyword is sufficient to declare a data structure of arbitrary shape persistent, even if the structure uses sharing, has a deep complexity, or contains cycles transfers. +Motoko data persistence is not simple, but it prevents data corruption or loss while being efficient at the same time. No database, stable memory API, or stable data structure is required to retain state across upgrades. Because actors are persistent by default, every actor field is automatically carried across upgrades: even if the data structure uses sharing, has a deep complexity, or contains cycle transfers, its value survives with no declaration needed. In comparison to other supported languages for building canisters, such as Rust, data persistence must be achieved through explicit use of stable data structures and stable memory, as other languages are not designed for orthogonal persistence and instead rearranges memory structures in an uncontrolled manner on re-compilation or at runtime. -## Declaring stable variables +## State persistence -Within an actor, you can configure which part of the program is considered to be persistent (retained across upgrades) and which part is ephemeral (reset on upgrades). +Every actor persists its state across canister upgrades. You don't need to declare anything: a `let` or `var` actor field is kept, with its value, for the lifetime of the canister. -More precisely, each `let` and `var` variable declaration in an actor can specify whether the variable is `stable` or `transient`. If you don’t provide a modifier, the variable is assumed to be `transient` by default. +```motoko file=/StableCounter.mo +``` -* `stable` means that all values directly or indirectly reachable from that stable variable are considered persistent and are automatically retained across upgrades. This is the primary choice for most of the program's state. +When you compile and deploy a canister for the first time, all actor fields are initialized in sequence. When a canister is upgraded, fields that existed in the previous version are pre-initialized with their old values, and any newly-added fields are initialized in sequence. -* `transient` means that the variable is re-initialized on upgrade such that the values referenced by the transient variable are discarded, unless the values are transitively reachable by other variables that are stable. `transient` is only used for temporary state or references to high-order types, such as local function references. +## Opting a field out of persistence -:::note +Most of the time, persistence is exactly what you want: it is why the actor's counter, registry, or wallet balance survives an upgrade. Occasionally you want a field to restart fresh on every upgrade: a cache, a one-time initialization, or a value whose type cannot be persisted (for example, an object with methods). Fields like these are the exception, not the rule, so declaring them is explicit: -You can only use the `stable`, `transient` (or legacy `flexible`) modifier on `let` and `var` declarations that are **actor fields**. You cannot use these modifiers anywhere else in your program. +* `stable` fields are persisted across upgrades. This is the default; you generally don't need to write it. +* `transient` fields are not. They are re-initialized on upgrade, unless they are transitively reachable from a persisted field. Use `transient` only for temporary state or references to higher-order types, such as local function references. -::: - -The following is a simple example of how to declare a stable counter that can be upgraded while preserving the counter’s value: - -```motoko file=/StableCounter.mo -``` +Only `transient` fields need an explicit `transient` declaration. The `stable` and `transient` modifiers are only allowed on `let` and `var` declarations that are **actor fields**; you cannot use them anywhere else in your program. -When you compile and deploy a canister for the first time, all transient and stable variables in the actor are initialized in sequence. When a canister is upgraded, all stable variables that existed in the previous version of the actor are pre-initialized with their old values and the remaining transient and any newly-added stable variables are initialized in sequence. +:::note -Starting with Motoko v0.13.5, if you prefix the `actor` keyword with the keyword `persistent`, then all `let` and `var` declarations of the actor or actor class are implicitly declared `stable`. Only `transient` variables will need an explicit `transient` declaration. +The `persistent` keyword is redundant for actors, since actors are `persistent` by default. If you see it in code, it can be removed. Older code written against the pre-v2 default used `persistent` (and `stable`) to opt into persistence: now that persistence is the default, these keywords are optional. -Using a `persistent` actor can help avoid unintended data loss. It is the recommended declaration syntax for actors and actor classes. The non-`persistent` declaration is provided for backwards compatibility. +::: ```motoko file=/PersistentCounter.mo ``` @@ -57,14 +54,10 @@ In general, classes are not stable because they can contain local functions. How For variables that do not have a stable type, there are two options for making them stable: -1. Use a `stable` module for the type, such as: - - - [StableBuffer](https://github.com/canscale/StableBuffer) - - [StableHashMap](https://github.com/canscale/StableHashMap) - - [StableRBTree](https://github.com/canscale/StableRBTree) +1. Use a stable data structure. The data structures in [`core`](https://mops.one/core), such as `Map`, `Set`, `List` and `Queue` and their `pure/` counterparts, all have stable types. :::note -Unlike stable data structures in the Rust CDK, these modules do not use stable memory but instead rely on orthogonal persistence. The adjective "stable" only denotes a stable type in Motoko. +Unlike stable data structures in the Rust CDK, these data structures do not use stable memory but instead rely on orthogonal persistence. The adjective "stable" only denotes a stable type in Motoko. ::: 2. Extract the state in a stable type and wrap it in the non-stable type. @@ -74,7 +67,7 @@ For example, the stable type `TemperatureSeries` covers the persistent data, whi ```motoko no-repl file=/WeatherActor.mo ``` -__Discouraged and not recommended__: [Pre- and post-upgrade hooks](#preupgrade-and-postupgrade-system-methods) allow copying non-stable types to stable types during upgrades. This approach is error-prone and does not scale for large data. **Per best practices, using these methods should be avoided if possible.** Conceptually, it also does not align well with the idea of orthogonal persistence. +__Deprecated__: [Pre- and post-upgrade hooks](#legacy-features) allow copying non-stable types to stable types during upgrades. This approach is error-prone and does not scale for large data. **Per best practices, using these methods should be avoided if possible.** Conceptually, it also does not align well with the idea of orthogonal persistence. ## Stable type signatures @@ -121,7 +114,7 @@ When upgrading a canister, it is important to verify that the upgrade can procee - Introducing an incompatible change in stable declarations. - Breaking clients due to a Candid interface change. -With [enhanced orthogonal persistence](./orthogonal-persistence/enhanced.md), Motoko rejects incompatible changes of stable declarations during an upgrade attempt. +Thanks to [enhanced orthogonal persistence](./orthogonal-persistence/enhanced.md), Motoko rejects incompatible changes of stable declarations during an upgrade attempt. Moreover, `dfx` checks the two conditions before attempting the upgrade and warns users as necessary. A Motoko canister upgrade is safe provided: @@ -129,10 +122,6 @@ A Motoko canister upgrade is safe provided: - The canister’s Candid interface evolves to a Candid subtype. You can check valid Candid subtyping between two services described in `.did` files using the [`didc` tool](https://github.com/dfinity/candid) with argument `check file1.did file2.did`. - The canister’s Motoko stable signature evolves to a stable-compatible one. -:::danger -With [classical orthogonal persistence](./orthogonal-persistence/classical.md), the upgrade can still fail due to resource constraints. This is problematic as the canister can then not be upgraded. It is therefore strongly advised to test the scalability of upgrades extensively. This does not apply to enhanced orthogonal persistence. -::: - ## Upgrading a canister @@ -142,7 +131,7 @@ If you have a Motoko canister that has already been deployed, then you make chan You are making a BREAKING change. Other canisters or frontend clients relying on your canister may stop working. ``` -Motoko canisters using enhanced orthogonal persistence implement an extra safeguard in the runtime system to ensure that the stable data is compatible to exclude any data corruption or misinterpretation. Moreover, `dfx` also warns about incompatibility and dropping stable variables. +Motoko canisters implement an extra safeguard in the runtime system to ensure that the stable data is compatible to exclude any data corruption or misinterpretation. Moreover, `dfx` also warns about incompatibility and dropping stable variables. ## Data migration @@ -185,11 +174,11 @@ reference material on [migration expressions](../../reference/language-manual.md ## Legacy features :::danger -Using the pre- and post-upgrade system methods is discouraged. It is error-prone and can render a canister unusable. In particular, if a `preupgrade` method traps and cannot be prevented from trapping by other means, then your canister may be left in a state in which it can no longer be upgraded. Per best practices, using these methods should be avoided if possible. +The pre- and post-upgrade system methods are deprecated (warning M0270) in favor of [migration functions](#explicit-migration). They are error-prone and can render a canister unusable. In particular, if a `preupgrade` method traps and cannot be prevented from trapping by other means, then your canister may be left in a state in which it can no longer be upgraded. Per best practices, using these methods should be avoided if possible. ::: Motoko supports user-defined upgrade hooks that run immediately before and after an upgrade. These upgrade hooks allow triggering additional logic on upgrade. -They are declared as `system` functions with special names, `preugrade` and `postupgrade`. Both functions must have type `: () → ()`. +They are declared as `system` functions with special names, `preupgrade` and `postupgrade`. Both functions must have type `: () → ()`. If `preupgrade` raises a trap, hits the instruction limit, or hits another IC computing limit, the upgrade can no longer succeed and the canister is stuck with the existing version. diff --git a/docs/languages/motoko/fundamentals/actors/enhanced-multi-migration.md b/docs/languages/motoko/fundamentals/actors/enhanced-multi-migration.md index 1275b77a..bf7071d1 100644 --- a/docs/languages/motoko/fundamentals/actors/enhanced-multi-migration.md +++ b/docs/languages/motoko/fundamentals/actors/enhanced-multi-migration.md @@ -21,7 +21,7 @@ With enhanced multi-migration you: The compiler reads all migration modules in lexicographic order, checks that they compose correctly, and compiles them into the actor. At runtime, only migrations that have not yet been applied are executed; already-applied migrations are skipped automatically. :::note -Enhanced multi-migration requires enhanced orthogonal persistence. It cannot be combined with the inline `(with migration = ...)` syntax used for [single migration functions](./compatibility.md#explicit-migration-using-a-migration-function). +Enhanced multi-migration cannot be combined with the inline `(with migration = ...)` syntax used for [single migration functions](./compatibility.md#explicit-migration-using-a-migration-function), nor used for the one-time upgrade of a canister from classical orthogonal persistence. ::: ## Getting started @@ -109,9 +109,7 @@ This restriction ensures that the initialization of stable state is fully determ Pass the migration directory to the compiler: ```bash -moc --enhanced-orthogonal-persistence \ - --default-persistent-actors \ - --enhanced-migration ./migrations \ +moc --enhanced-migration ./migrations \ src/main.mo -o main.wasm ``` @@ -395,7 +393,7 @@ The first migration in the chain must initialize all required fields. When a can - Each migration file must be a module containing a `public func migration(...)`. - The `--enhanced-migration` flag cannot be combined with the inline `(with migration = ...)` syntax. -- Enhanced multi-migration requires enhanced orthogonal persistence. +- Enhanced multi-migration cannot be used for the one-time upgrade of a canister from classical orthogonal persistence. - Stable actor variables must be declared without initializers (e.g. `var x : Nat`, not `var x : Nat = 0`). The compiler rejects stable variables that carry an initializing expression. - The actor body must be static: top-level effectful expressions and most function calls are rejected. Only calls requiring `` capability (e.g. timer setup, Candid decoding configuration) are permitted. - The state after each migration (its output merged with carried-through fields) must be compatible with the input of the next migration in the chain. The compiler rejects the program if this is not the case. @@ -405,9 +403,7 @@ The first migration in the chain must initialize all required fields. When a can ## Usage ```bash -moc --enhanced-orthogonal-persistence \ - --default-persistent-actors \ - --enhanced-migration ./migrations \ +moc --enhanced-migration ./migrations \ actor.mo -o actor.wasm ``` diff --git a/docs/languages/motoko/fundamentals/actors/messaging.md b/docs/languages/motoko/fundamentals/actors/messaging.md index 6810a3e1..5b9057ba 100644 --- a/docs/languages/motoko/fundamentals/actors/messaging.md +++ b/docs/languages/motoko/fundamentals/actors/messaging.md @@ -14,7 +14,7 @@ Only async contexts support [error handling](../error-handling.md) with `try/cat In Motoko, an expression is considered to be in an async context if it appears inside an `async` function. Query functions are read-only, so they do not create an async context and therefore cannot use `await` or send messages. ```motoko no-repl -persistent actor Counter { +actor Counter { var count : Nat = 0; public func increment() : async () { diff --git a/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/classical.md b/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/classical.md deleted file mode 100644 index efd2e311..00000000 --- a/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/classical.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: "Classical orthogonal persistence" -description: "Classical orthogonal persistence is the legacy implementation of Motoko's orthogonal persistence." -sidebar: - order: 3 ---- - -Classical orthogonal persistence is the legacy implementation of Motoko's orthogonal persistence. Classical persistence is deprecated in favor of enhanced orthogonal persistence. - -Upon upgrade, the classical orthogonal persistence mechanism serializes all stable data to the stable memory and then deserializes it back to the main memory. This has several downsides: - -* At maximum, 2 GiB of heap data can be persisted across upgrades. This is because of an implementation restriction. Note that in practice, the supported amount of stable data can be way lower. -* Shared immutable heap objects can duplicated, leading to potential state explosion on upgrades. -* Deeply nested structures can lead to a call stack overflow. -* The serialization and deserialization is expensive and can hit ICP's instruction limits. -* There is no built-in stable compatibility check in the runtime system. If users ignore the `dfx` upgrade warning, data may be lost or an upgrade fails. - -:::danger -The above-mentioned issues can lead to a stuck canister that can no longer be upgraded. -Therefore, it is absolutely necessary to thoroughly test how much data an upgrade of your application can handle and then conservatively limit the data held by that canister. -Moreover, it is ideal to have a backup plan to rescue data even if upgrades fail, e.g. by controller-privileged data query calls. Another option is to [snapshot](/guides/canister-management/snapshots) the canister before attempting the upgrade. -::: - -These issues are solved by [enhanced orthogonal persistence](./enhanced.md). - - -:::note -Classical orthogonal persistence was previously the default compilation mode for Motoko code. Going forward, the default compilation mode is enhanced orthogonal persistence, -previously available only with `moc` compiler flag `--enhanced-orthogonal-persistence`. - -Users unwilling or unable to migrate their code can re-enable support for classical orthogonal persistence using the new compiler flag `--legacy-persistence`. - -To re-activate classical orthogonal persistence under `dfx`, the following command-line argument needs to be specified in `dfx.json`: - -``` -... - "type" : "motoko" - ... - "args" : "--legacy-persistence" -... -``` -::: - diff --git a/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/enhanced.md b/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/enhanced.md index ee739b68..1b09c156 100644 --- a/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/enhanced.md +++ b/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/enhanced.md @@ -12,24 +12,19 @@ Enhanced orthogonal persistence implements the vision of efficient and scalable As a result, the use of secondary storage (explicit stable memory, dedicated stable data structures, DB-like storage abstractions) will no longer be necessary: Motoko developers can directly work on their normal object-oriented program structures that are automatically persisted and retained across program version changes. -Enhanced orthogonal persistence is enabled by default. (It was previously only offered via the compiler flag `--enhanced-orthogonal-persistence`, which is now redundant.) +Enhanced orthogonal persistence is always enabled: since `moc` 2.0, it is the only persistence mode and classical (32-bit) orthogonal persistence has been removed. :::tip Despite the use of enhanced orthogonal persistence, it is strongly recommended to thoroughly test the upgrades of your application. Moreover, it is advised to have a backup possibility for rescuing data even when upgrades fail, e.g. by controller-privileged data query calls. ::: -:::note -[Classical orthogonal persistence](./classical.md) with 32-bit main memory and Candid stabilization was the previous default compilation mode for `moc`. If necessary, it can be re-enabled with compiler flag `--legacy-persistence`. -See [orthogonal persistence modes](./index.md) for a comparison. -::: - ## Design -Compared to the legacy orthogonal persistence in Motoko, this design offers: +This design offers: * **Performance**: New program versions directly resume from the existing main memory and have access to the memory-compatible data. * **Scalability**: The upgrade mechanism scales with larger heaps and in contrast to serialization, does not hit IC instruction limits. -Compared to the explicit use of stable memory, this design improves: +Compared to the explicit use of stable memory, it improves: * **Simplicity**: Developers do not need to deal with explicit stable memory. * **Performance**: No copying to and from the separate stable memory is necessary. @@ -57,7 +52,11 @@ The runtime system checks migration compatibility on upgrade, and if not fulfill Any more complex change can be performed with programmatic instruction, see [explicit migration](../data-persistence.md#explicit-migration). ### Migration path -When migrating from the old serialization-based stabilization to the new persistent heap, the old data is deserialized one last time from stable memory and then placed in the new persistent heap layout. Once operating on the persistent heap, the system should prevent downgrade attempts to the old serialization-based persistence. +When upgrading a canister from classical orthogonal persistence (the old serialization-based stabilization) to the new persistent heap, the old data is deserialized one last time from stable memory and then placed in the new persistent heap layout. This one-time upgrade must be compiled with the explicit `--enhanced-orthogonal-persistence` flag and without `--enhanced-migration`; later upgrades need no flag. Once operating on the persistent heap, the system prevents downgrade attempts to the old serialization-based persistence. + +:::caution +A canister that moved from classical persistence to the persistent heap with a `moc` older than 1.5.0 may fail to upgrade with `moc` 1.5.0 or later. The upgrade traps with `cannot upgrade from an actor using enhanced migration to an actor not using enhanced migration`, even if the canister never used enhanced migration. The cause is runtime metadata, added in later versions, that can still hold leftover bytes from the old classical heap. The upgrade is rolled back, so no data is lost. To fix it, perform that upgrade once with [graph-copy-based stabilization](#graph-copy-based-stabilization), which reinitializes the metadata. Later upgrades work as usual. +::: #### Graph-copy-based stabilization Assuming that the persistent memory layout needs to be changed in the future, the runtime system supports serialization and deserialization to and from stable memory in a defined data format using graph-copy-based stabilization. Arbitrarily large data can be serialized and deserialized beyond the instruction and working set limit of upgrades. Large data serialization and deserialization is split in multiple messages, running before and/or after the IC upgrade to migrate large heaps. Other messages will be blocked during this process and only the canister owner or the canister controllers are permitted to initiate this process. @@ -95,6 +94,6 @@ The old stable memory remains equally accessible as secondary (legacy) memory wi ## IC main memory retention The IC introduces a new upgrade option `wasm_memory_persistence` to control the retention of the canister's Wasm main memory. -* `wasm_memory_persistence = opt keep` retains the Wasm main memory and is required for Motoko's enhanced orthogonal persistence. The IC prevents using this options for canisters with classical persistence. -* `wasm_memory_persistence = null` uses the classical persistence, replacing the main memory. However, a safety check is implemented to prevent that main memory is not accidentally dropped for enhanced orthogonal persistence. +* `wasm_memory_persistence = opt keep` retains the Wasm main memory and is what Motoko requires. +* `wasm_memory_persistence = null` replaces the main memory. A safety check prevents the main memory from being dropped accidentally. * The other option `replace` is not recommended as it drops Wasm main memory, even for enhanced orthogonal persistence, leading to potential data loss. diff --git a/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/index.md b/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/index.md index fd5d36ea..1be0ac58 100644 --- a/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/index.md +++ b/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/index.md @@ -7,4 +7,4 @@ sidebar: hidden: true --- -Orthogonal persistence is the mechanism by which Motoko preserves an actor's state across canister upgrades automatically: no database, no stable memory API, no serialization code. This section covers the classical and enhanced persistence models and the trade-offs between them. +Orthogonal persistence is the mechanism by which Motoko preserves an actor's state across canister upgrades automatically: no database, no stable memory API, no serialization code. This section covers how that mechanism works. diff --git a/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/overview.md b/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/overview.md index b3ebb7cf..a0b71f3b 100644 --- a/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/overview.md +++ b/docs/languages/motoko/fundamentals/actors/orthogonal-persistence/overview.md @@ -7,25 +7,8 @@ sidebar: Orthogonal persistence is the ability to for a program to automatically preserve its state across transactions and canister upgrades without requiring manual intervention. This means that data persists seamlessly, without the need for a database, stable memory APIs, or specialized stable data structures. -Although Motoko’s persistence model is complex under the hood, it’s designed to be both safe and efficient. By simply using the `persistent` (actors) or `stable` (data structures) keyword, developers can mark pieces of their program as persistent. This abstraction significantly reduces the risk of data loss or corruption during upgrades. +Although Motoko’s persistence model is complex under the hood, it’s designed to be both safe and efficient. Because actors are persistent by default, developers get persistence for free: every actor field survives upgrades, with no keyword, annotation, or special structure required. This abstraction significantly reduces the risk of data loss or corruption during upgrades. In contrast, other canister development languages like Rust require explicit handling of persistence. Developers must manually manage stable memory and use specialized data structures to ensure data survives upgrades. These languages lack orthogonal persistence, and may rearrange memory unpredictably during recompilation or runtime, making safe persistence more error-prone and labor-intensive. -Motoko features two implementations for orthogonal persistence: - -* [Enhanced orthogonal persistence](./enhanced.md) provides very fast upgrades, scaling independently of the heap size. This is realized by retaining the entire Wasm main memory on an upgrade and simply performing a type-driven upgrade safety check. By using 64-bit address space, it is designed to scale beyond 4 GiB and in the future, offer the same capacity like stable memory. - -* [Classical orthogonal persistence](./classical.md) is the old implementation of orthogonal persistence that is superseded by enhanced orthogonal persistence. On an upgrade, the runtime system first serializes the persistent data to stable memory and then deserializes it back again to main memory. While this is both inefficient and unscalable, it exhibits problems on shared immutable data (potentially leading to state explosion), deep structures (call stack overflow) and larger heaps (the implementation limits the stable data to at most 2 GiB). - -:::note - -Since version 0.15.0, the `moc` compiler enables enhanced orthogonal persistence by default. -Classical orthogonal persistence, the default compilation mode in previous versions has been deprecated and can only be re-enabled with a compiler flag (`--legacy-persistence`). - -Although it is possible to upgrade a canister compiled with classical persistence to one compiled with enhanced-orthogonal-persistence, downgrades from enhanced to classical are *not* supported. - -As a safeguard, to protect users from unwittingly, and irreversibly, upgrading from classical to enhanced orthogonal persistence, such upgrades will fail unless the new code is compiled with flag `--enhanced-orthogonal-persistence` explicitly set. - -New projects should not require the flag at all (#5308) and will simply adopt enhanced mode. Only projects that wish to transition from classical to enhanced orthogonal persistence should explicitly set `--enhanced-orthogonal-persistence` to disable the safeguard and opt-in to enhanced mode. - -::: \ No newline at end of file +Motoko's orthogonal persistence is implemented by [enhanced orthogonal persistence](./enhanced.md). It provides very fast upgrades, scaling independently of the heap size. This is realized by retaining the entire Wasm main memory on an upgrade and simply performing a type-driven upgrade safety check. By using 64-bit address space, it is designed to scale beyond 4 GiB and in the future, offer the same capacity like stable memory. diff --git a/docs/languages/motoko/fundamentals/basic-syntax/characters-text.md b/docs/languages/motoko/fundamentals/basic-syntax/characters-text.md index 0810cbfc..bef4b962 100644 --- a/docs/languages/motoko/fundamentals/basic-syntax/characters-text.md +++ b/docs/languages/motoko/fundamentals/basic-syntax/characters-text.md @@ -42,7 +42,7 @@ import Text "mo:core/Text"; import Iter "mo:core/Iter"; import Char "mo:core/Char"; -persistent actor Alternator { +actor Alternator { // Turn text into an iterator of Char func textToChars(t: Text) : Iter.Iter { diff --git a/docs/languages/motoko/fundamentals/basic-syntax/defining-an-actor.md b/docs/languages/motoko/fundamentals/basic-syntax/defining-an-actor.md index a8a95001..e93bc277 100644 --- a/docs/languages/motoko/fundamentals/basic-syntax/defining-an-actor.md +++ b/docs/languages/motoko/fundamentals/basic-syntax/defining-an-actor.md @@ -30,7 +30,7 @@ Since actors process messages independently, multiple actors can handle messages ```motoko no-repl // Declares an actor named Main. -persistent actor Main { +actor Main { // Define a private variable called 'count' to track the number of greetings. var count : Nat = 0; @@ -51,8 +51,8 @@ persistent actor Main { :::note This code defines an actor that can be deployed on ICP. -The actor is declared as `persistent` so that its state, `count`, will be preserved -when the actor is upgraded. +An actor's state is preserved across upgrades automatically: `count` keeps its value +when the canister is upgraded to new code. Learn more about [persistence](../actors/data-persistence.md). ::: diff --git a/docs/languages/motoko/fundamentals/basic-syntax/functions.md b/docs/languages/motoko/fundamentals/basic-syntax/functions.md index 03be2c2d..6d832f70 100644 --- a/docs/languages/motoko/fundamentals/basic-syntax/functions.md +++ b/docs/languages/motoko/fundamentals/basic-syntax/functions.md @@ -55,7 +55,7 @@ All shared function, unlike ordinary functions, provide access to the identity o For example, you can rewrite the object above as an actor: ```motoko no-repl -persistent actor Digit { +actor Digit { var value = 0; func reset() { value := 0 }; public shared func inc() : async (){ @@ -71,7 +71,7 @@ persistent actor Digit { Since the public functions of an actor must be `shared`, you can omit the `shared` keyword: ```motoko no-repl -persistent actor Digit { +actor Digit { var value = 0; func reset() { value := 0 }; public func inc() : async () { diff --git a/docs/languages/motoko/fundamentals/basic-syntax/whitespace.md b/docs/languages/motoko/fundamentals/basic-syntax/whitespace.md index ce731060..df258cb2 100644 --- a/docs/languages/motoko/fundamentals/basic-syntax/whitespace.md +++ b/docs/languages/motoko/fundamentals/basic-syntax/whitespace.md @@ -10,13 +10,13 @@ Whitespace characters (spaces, tabs, newlines) are generally ignored in Motoko, ### Incorrect use of whitespace ```motoko no-repl -persistent actor Counter{var x : Nat = 0; public func inc(): async Int{x+1; }}; +actor Counter{var x : Nat = 0; public func inc(): async Int{x+1; }}; ``` ### Proper whitespace usage ```motoko no-repl -persistent actor Counter { +actor Counter { var x : Nat = 0; public func inc() : async Int { x + 1; diff --git a/docs/languages/motoko/fundamentals/contextual-dot.md b/docs/languages/motoko/fundamentals/contextual-dot.md index da3bb87e..dbbdf3ae 100644 --- a/docs/languages/motoko/fundamentals/contextual-dot.md +++ b/docs/languages/motoko/fundamentals/contextual-dot.md @@ -136,10 +136,10 @@ let uppercased = texts.map(func(t) { /* convert to uppercase */ }); ## Compiler warnings and best practices -The Motoko compiler can optionally warn you about opportunities to use contextual dot notation. You can enable this with the `-W M0236` flag: +The Motoko compiler warns when a function call could use contextual dot notation, e.g. `Array.filter(arr, ...)` where `arr.filter(...)` would work. This warning (`M0236`) is enabled by default. Silence it with the `-A M0236` flag: ```bash -moc -W M0236 myfile.mo +moc -A M0236 myfile.mo ``` This helps you maintain consistent coding style across your project. @@ -156,10 +156,13 @@ This helps you maintain consistent coding style across your project. Contextual dot notation has some intentional limitations: - It requires the receiving value to be the first parameter, named `self`. -- Any function must be declared in a module that is imported or otherwise in scope: function in object, actors or nested modules are not considered. -- If there is more than one available module function, and none is more general than all the others, the call is considered ambigious and rejected at compile-time. +- Any function must be declared in a module that is imported or otherwise in scope, or in a module nested inside one (up to a depth of 8): functions in objects or actors are not considered. +- Nested modules are only searched when no module in scope has a matching function itself. Importing a facade module that re-exports other modules, e.g. `public let Map = _Map;`, is enough to use their functions with dot notation. +- If there is more than one available module function, and none is more specific than all the others, the call is considered ambiguous and rejected at compile-time. - The feature is purely syntactic - there is no runtime overhead +The exact search order and disambiguation, shared with implicit arguments, are specified in the [language manual](../reference/language-manual.md#resolution-of-dotted-calls-and-implicit-arguments). + ## See also diff --git a/docs/languages/motoko/fundamentals/control-flow/basic-control-flow.md b/docs/languages/motoko/fundamentals/control-flow/basic-control-flow.md index 80b22892..99540889 100644 --- a/docs/languages/motoko/fundamentals/control-flow/basic-control-flow.md +++ b/docs/languages/motoko/fundamentals/control-flow/basic-control-flow.md @@ -282,7 +282,7 @@ You can also exit any loop in a function using `return` or (in an asynchronous f A function call executes a function by passing arguments and receiving a result. In Motoko, function calls can be synchronous (executing immediately within the same [canister](/concepts/canisters)) or [asynchronous](../actors/actors-async.md#async--await) (message passing between canisters). Asynchronous calls use `async`/`await` and are essential for inter-canister communication. ```motoko no-repl -persistent actor { +actor { func product(numbers : [Int]) : Int { var prod : Int = 1; diff --git a/docs/languages/motoko/fundamentals/control-flow/conditionals.md b/docs/languages/motoko/fundamentals/control-flow/conditionals.md index 8c5f50d3..60e530d9 100644 --- a/docs/languages/motoko/fundamentals/control-flow/conditionals.md +++ b/docs/languages/motoko/fundamentals/control-flow/conditionals.md @@ -41,13 +41,15 @@ let parity = if (n % 2 == 0) #even else #odd; ``` Here, the first branch has type `{#even}` and the second branch has type `{#odd}`. These types are different but they have a common supertype `{#even; #odd}`. The type of the `if-else` is then `{#even; #odd}`. -Motoko will infer the common supertype for you, choosing the most specific one possible. If the types are inconsistent and only have the useless common supertype `Any`, Motoko will issue a warning: +Motoko will infer the common supertype for you, choosing the most specific one possible. If the types are inconsistent and only have the useless common supertype `Any`, Motoko reports an error: ```motoko no-repl let n : Nat = 0; let oops = if (n % 2 == 0) #even else 0; ``` +If `Any` is really what you want, say so with a type annotation: `let ok : Any = if (n % 2 == 0) #even else 0;`. + ## `if`-expression An `if`-expression takes the form `if ` and is like an `if-else` but omits the else and second branch `else `. diff --git a/docs/languages/motoko/fundamentals/control-flow/switch.md b/docs/languages/motoko/fundamentals/control-flow/switch.md index 34b22ac6..51e311e6 100644 --- a/docs/languages/motoko/fundamentals/control-flow/switch.md +++ b/docs/languages/motoko/fundamentals/control-flow/switch.md @@ -16,7 +16,7 @@ Only one branch is ever executed in a switch, the first one that matches. If no Motoko will warn you if any of the cases are redundant and can never matched due to previous cases. -Motoko will also warn you if the cases don't cover all possible values, meaning that the switch could fail with a trap at runtime. +Motoko reports an error if the cases don't cover all possible values, because the switch could otherwise fail with a trap at runtime. A `switch` is useful for replacing long `if-else` chains, improving both readability and structure. It lets you match specific values or patterns and handle each case individually. @@ -45,7 +45,7 @@ func toText(b : Bool) : Text { } ``` -If you forget the case for `false`, Motoko will also issue a warning that `false is not covered by any case`: +If you forget the case for `false`, Motoko reports an error that the switch does not cover the value `false`: ```motoko no-repl func toText(b : Bool) : Text { @@ -55,7 +55,7 @@ func toText(b : Bool) : Text { } ``` -Motoko is able to issue these warnings for much more complicated patterns where it might be hard to see that you've made a mistake, helping you construct correct code and catch errors at compilation. +Motoko is able to perform these checks for much more complicated patterns where it might be hard to see that you've made a mistake, helping you construct correct code and catch errors at compilation. ```motoko no-repl func getDayOfWeek(day : Nat) : Text { switch day { @@ -88,7 +88,7 @@ func value(option : ?T, default : T) : T { The first case matches against the literal `null`, returning `default`. The second case matches against the option pattern `?value`, binding the option's contents to the variable `value` and returning `value`. -If either case is omitted, Motoko will warn that the switch does not cover the `null` or `?_` case. +If either case is omitted, Motoko reports an error that the switch does not cover the `null` or `?_` case. Here's an example of a nested pattern: diff --git a/docs/languages/motoko/fundamentals/declarations/class-declarations.md b/docs/languages/motoko/fundamentals/declarations/class-declarations.md index 4861ead1..0bf92bd9 100644 --- a/docs/languages/motoko/fundamentals/declarations/class-declarations.md +++ b/docs/languages/motoko/fundamentals/declarations/class-declarations.md @@ -101,7 +101,7 @@ This extended `Counter` class checks that the implementation matches interface ` Actor classes enable you to create networks of actors programmatically. These classes must be defined in separate source files. ```motoko no-repl title="Counter.mo" -persistent actor class Counter(init : Nat) { +actor class Counter(init : Nat) { var count : Nat = init; public func inc() : async Nat { @@ -129,7 +129,7 @@ Then, in another file you can create an instance of this actor class: ```motoko no-repl title="CallCounter.mo" import Counter "./Counter"; -persistent actor { +actor { public func test() : async () { let counter = await (with cycles = 1_000_000_000_000) Counter.Counter(0); diff --git a/docs/languages/motoko/fundamentals/declarations/module-declarations.md b/docs/languages/motoko/fundamentals/declarations/module-declarations.md index 4f246aaa..9d8e8e39 100644 --- a/docs/languages/motoko/fundamentals/declarations/module-declarations.md +++ b/docs/languages/motoko/fundamentals/declarations/module-declarations.md @@ -82,7 +82,7 @@ module myModule { If you need mutable state, use an `actor` or an `object` inside an actor instead: ```motoko no-repl -persistent actor { +actor { var x = 0; // allowed because actors can have state } ``` diff --git a/docs/languages/motoko/fundamentals/hello-world.md b/docs/languages/motoko/fundamentals/hello-world.md index d910eea2..fdaa58dc 100644 --- a/docs/languages/motoko/fundamentals/hello-world.md +++ b/docs/languages/motoko/fundamentals/hello-world.md @@ -10,9 +10,9 @@ sidebar: Below is an example of "Hello, world!" written in Motoko: ```motoko no-repl -// If an actor is declared with the persistent keyword, all private declarations are considered stable by default -persistent actor HelloWorld { - // We store the greeting in a stable variable such that it gets persisted over canister upgrades. +// Every actor is `persistent` by default: its fields persist across canister upgrades. +actor HelloWorld { + // We store the greeting in a variable that persists over canister upgrades. var greeting : Text = "Hello, "; // This update method modifies the greeting prefix. @@ -31,7 +31,7 @@ In this example: 1. The code begins by defining an [actor](./actors/actors-async.md) named `HelloWorld`. In Motoko, an actor is an object capable of maintaining state and communicating with other entities via message passing. -2. It then declares the variable `greeting`. This is a [stable variable](./types/stable-types.md) because the actor is declared with the keyword `persistent`. Stable variables are used to store data that persists across canister upgrades. [Read more about canister upgrades.](/guides/canister-management/lifecycle#upgrade-a-canister) +2. It then declares the variable `greeting`. Like every actor field, `greeting` is persisted across canister upgrades, so the prefix the canister has been set to is never lost: even when the canister is upgraded with new code. [Read more about canister upgrades.](/guides/canister-management/lifecycle#upgrade-a-canister) 3. An [update method](/concepts/canisters#update-calls) named `setGreeting` is used to modify the canister’s state. This method specifically updates the value stored in `greeting`. diff --git a/docs/languages/motoko/fundamentals/implicit-parameters.md b/docs/languages/motoko/fundamentals/implicit-parameters.md index 9cfe6218..aa468e1f 100644 --- a/docs/languages/motoko/fundamentals/implicit-parameters.md +++ b/docs/languages/motoko/fundamentals/implicit-parameters.md @@ -65,10 +65,16 @@ The available candidates are: If there is no such value, * Any field named `M.compare` declared in some module available `M`. -* If there is more than one such field, none of which is more specific than all the others, the call is ambiguous. + +If there is no such field either, +* Any field named `compare` of a module nested inside a module available `M`, such as `M.N.compare` (up to a depth of 8). + +If there is more than one candidate at the first step that has one, none of which is more general than all the others, the call is ambiguous. An ambiguous call can always be disambiguated by supplying the explicit arguments for all implicit parameters. +The exact search order and disambiguation, shared with contextual dot notation, are specified in the [language manual](../reference/language-manual.md#resolution-of-dotted-calls-and-implicit-arguments). + ### Contextual dot notation Implicit parameters dovetail nicely with [contextual dot notation](contextual-dot.md). diff --git a/docs/languages/motoko/fundamentals/modules-imports.md b/docs/languages/motoko/fundamentals/modules-imports.md index 3d61a26f..aed67dfc 100644 --- a/docs/languages/motoko/fundamentals/modules-imports.md +++ b/docs/languages/motoko/fundamentals/modules-imports.md @@ -173,7 +173,7 @@ When imported, an [actor](./actors/actors-async.md) class provides a type defini For example, if you define the following actor class: ```motoko no-repl title="Counters.mo" -persistent actor class Counter(init : Nat) { +actor class Counter(init : Nat) { var count = init; public func inc() : async () { count += 1 }; @@ -194,7 +194,7 @@ import Counters "Counters"; import Debug "mo:core/Debug"; import Nat "mo:core/Nat"; -persistent actor CountToTen { +actor CountToTen { public func countToTen() : async () { let counterActor = await Counters.Counter(1); while ((await counterActor.read()) < 10) { diff --git a/docs/languages/motoko/fundamentals/pattern-matching.md b/docs/languages/motoko/fundamentals/pattern-matching.md index 891e004a..f31544c8 100644 --- a/docs/languages/motoko/fundamentals/pattern-matching.md +++ b/docs/languages/motoko/fundamentals/pattern-matching.md @@ -39,7 +39,7 @@ Some types only have a single possible value. These are called singleton types. ### Exhaustiveness checking -To prevent runtime errors when no `switch` case matches, the Motoko compiler performs exhaustiveness checking. It will warn you if any possible input is not handled and shows an example of a missing case. It also flags redundant patterns that will never be matched, helping you write safer, cleaner code. +To prevent runtime errors when no `switch` case matches, the Motoko compiler performs exhaustiveness checking. It rejects the program with an error if any possible input is not handled and shows an example of a missing case. It also warns about redundant patterns that will never be matched, helping you write safer, cleaner code. ## Using pattern matching diff --git a/docs/languages/motoko/fundamentals/types/advanced-types.md b/docs/languages/motoko/fundamentals/types/advanced-types.md index fea6dc0e..df2b7a5d 100644 --- a/docs/languages/motoko/fundamentals/types/advanced-types.md +++ b/docs/languages/motoko/fundamentals/types/advanced-types.md @@ -210,7 +210,7 @@ The *actor reference* expression `actor ` compute a reference to an actor f A simple example of using actor references is to access the management canister with textual address `"aaaaa-aa"`. Amongst other things, it has a method `raw_rand` for generating cryptographically random bytes as a `Blob`. ```motoko no-repl -persistent actor Coin { +actor Coin { public func flip() : async Bool { let managementCanister = actor "aaaaa-aa" : actor { raw_rand : () -> async Blob }; let entropy = await managementCanister.raw_rand(); @@ -224,7 +224,7 @@ A variation computes the textual canister identifier from a given principal. A c ```motoko no-repl import Principal "mo:core/Principal"; -persistent actor Coin { +actor Coin { public func flipWith(principal : Principal) : async Bool { let canister = actor (Principal.toText(principal)) : actor { raw_rand : () -> async Blob }; let entropy = await canister.raw_rand(); @@ -241,7 +241,7 @@ For this reason, you should only use actor references sparingly. It's typically For example, a safer variant of `flipWith` is: ```motoko no-repl -persistent actor Coin { +actor Coin { public func flipWith(canister : actor { raw_rand : () -> async Blob }) : async Bool { let entropy = await canister.raw_rand(); (entropy[0] & 1) == 1; diff --git a/docs/languages/motoko/fundamentals/types/function-types.md b/docs/languages/motoko/fundamentals/types/function-types.md index 2c34361d..f2acd93e 100644 --- a/docs/languages/motoko/fundamentals/types/function-types.md +++ b/docs/languages/motoko/fundamentals/types/function-types.md @@ -48,7 +48,7 @@ Motoko provides different types of functions based on where in the program they Local functions run within the canister's [actor](../actors/actors-async.md). They cannot call other [canisters](/concepts/canisters). Local functions are cheap to call and execute synchronously. ```motoko no-repl -persistent actor CommonDivisor{ +actor CommonDivisor{ func gcd(a : Nat, b : Nat) : Nat { var x = a; var y = b; @@ -99,7 +99,7 @@ They are useful for defining asynchronous logic used in the implementation of pu import Time "mo:core/Time"; import Logger "canister:Logger"; -persistent actor { +actor { private func log(msg : Text) : async () { Logger.log(Time.now() + msg); // sends a message }; @@ -115,7 +115,7 @@ A more efficient variation is to use `async*` and `await*` , which avoids the ov import Time "mo:core/Time" import Logger "canister:Logger"; -persistent actor { +actor { private func log(msg : Text) : async* () { await Logger.log(Time.now() + msg); // sends a message }; @@ -138,7 +138,7 @@ Although queries can temporarily alter the state of an actor, these changes are When called from a front-end, `query` functions generally have much lower latency than equivalent shared functions. This is because shared functions require the protocol to reach consensus on the state changes and results, whereas query functions do not. ```motoko no-repl -persistent actor Account { +actor Account { var balance = 0; public shared func deposit(amount : Nat) : async Nat { @@ -151,7 +151,7 @@ persistent actor Account { Omitting the shared keyword, we can also write: ```motoko no-repl -persistent actor Account { +actor Account { var balance = 0; public func deposit(amount : Nat) : async Nat { @@ -184,7 +184,7 @@ Such a function is called a _one-way_ (or _fire-and-forget_) function. An example of this might be a variant of `Account.deposit`, `Account.credit`, that merely updates the balance without returning its new value: ```motoko no-repl -persistent actor Account { +actor Account { var balance = 0; public func credit(amount : Nat) : () { balance += amount; @@ -215,7 +215,7 @@ Again, the shared keyword is optional. Note that `Account.credit(100` just retur ```motoko no-repl -persistent actor Account { +actor Account { var balance = 0; public shared query func getBalance() : async Nat { balance @@ -226,7 +226,7 @@ persistent actor Account { Again, you can omit the shared keyword: ```motoko no-repl -persistent actor Account { +actor Account { var balance = 0; public query func getBalance() : async Nat { @@ -246,7 +246,7 @@ The `getBalance` function has function type `shared query () -> async Nat`. A good example of a composite query might be a bank that holds references to its individual accounts, implemented as separate actors, and provides a composite query that sums the deposits in all its accounts: ```motoko no-repl -persistent actor Bank { +actor Bank { type Account = actor { getBalance() : query () -> async Nat }; @@ -265,7 +265,7 @@ persistent actor Bank { Again, the shared keyword is redundant and can be omitted: ```motoko no-repl -persistent actor Bank { +actor Bank { // ... code omitted ... public composite query func getDeposits() : async Nat { var deposits = 0; diff --git a/docs/languages/motoko/fundamentals/types/immutable-arrays.md b/docs/languages/motoko/fundamentals/types/immutable-arrays.md index 92a58371..7295e287 100644 --- a/docs/languages/motoko/fundamentals/types/immutable-arrays.md +++ b/docs/languages/motoko/fundamentals/types/immutable-arrays.md @@ -155,7 +155,7 @@ A chessboard is a fixed `8×8` grid. Using immutable arrays to represent the ini import Array "mo:core/Array"; import Debug "mo:core/Debug"; -persistent actor Chess{ +actor Chess{ func generateChessboard() : [[Text]] { let size : Nat = 8; diff --git a/docs/languages/motoko/fundamentals/types/mutable-arrays.md b/docs/languages/motoko/fundamentals/types/mutable-arrays.md index 75bfeed9..0cbe9ca6 100644 --- a/docs/languages/motoko/fundamentals/types/mutable-arrays.md +++ b/docs/languages/motoko/fundamentals/types/mutable-arrays.md @@ -180,7 +180,7 @@ A Tic-tac-toe board is a `3x3` grid that requires updates as players take turns. import VarArray "mo:core/VarArray"; import Debug "mo:core/Debug"; -persistent actor TicTacToe { +actor TicTacToe { func createTicTacToeBoard() : [var [var Text]] { let size : Nat = 3; diff --git a/docs/languages/motoko/fundamentals/types/records.md b/docs/languages/motoko/fundamentals/types/records.md index d9b81557..caa96781 100644 --- a/docs/languages/motoko/fundamentals/types/records.md +++ b/docs/languages/motoko/fundamentals/types/records.md @@ -200,7 +200,10 @@ debug_show(updatedPerson); :::note -If `person` contained a mutable (`var`) field, `with` must redefine it, preventing aliasing. +If `person` contained a mutable (`var`) field, `{ person with ... }` copies it +into a fresh mutable field holding its current value. Mutating the copy leaves +`person` unchanged, and vice versa: the same independence you get by writing +`var = person.` explicitly. ::: diff --git a/docs/languages/motoko/fundamentals/types/stable-types.md b/docs/languages/motoko/fundamentals/types/stable-types.md index ef35c579..417992b1 100644 --- a/docs/languages/motoko/fundamentals/types/stable-types.md +++ b/docs/languages/motoko/fundamentals/types/stable-types.md @@ -12,14 +12,12 @@ The set of stable types defines the kinds of values that can be transferred from Types that cannot be transferred include those whose values depend on the actor's current code, such as non-shared functions or, more generally, objects containing function members. These types are not stable because their behavior cannot be preserved independently of the code that defines them. :::note -In Motoko, the treatment of private declarations depends on whether an actor is declared with the `persistent` keyword: +In Motoko, actors are `persistent` by default, so all `let` and `var` declarations of an actor or actor class hold **stable** values: they persist across upgrades: unless explicitly marked `transient`. -- In actors **without** the `persistent` keyword, all private declarations are considered **transient** by default, unless explicitly marked `stable`. +Stable values must have types that belong to the set of stable types. +Transient values are not subject to this restriction and may have any type, including non-stable types such as functions or objects with function members. -- In **`persistent` actors**, all private declarations (except function declarations) are considered **stable** by default, unless explicitly marked `transient`. - -Stable variables must have types that belong to the set of stable types. -Transient variables are not subject to this restriction and may have any type, including non-stable types such as functions or objects with function members. +Under the pre-v2 compiler, actors were not persistent by default and fields were not persisted unless explicitly marked `stable`. ::: @@ -59,7 +57,7 @@ Non-shared functions and futures (`async T`) and computations (`async* T`) depen Most [primitive types](./primitive-types.md) in Motoko are stable. ```motoko no-repl -persistent actor { +actor { // Numbers, text, booleans and other primitive types are stable var counter : Nat = 0; var greeting : Text = "Welcome"; @@ -73,7 +71,7 @@ persistent actor { Both immutable and mutable collections of stable types are stable. ```motoko no-repl -persistent actor { +actor { // Immutable arrays are stable var usernames : [Text] = ["Motoko", "Ghost"]; @@ -87,7 +85,7 @@ persistent actor { [Records](./records.md) that contain only stable types remain stable, regardless of whether their fields are mutable or immutable. ```motoko no-repl -persistent actor { +actor { // Records with immutable fields are stable var config = { appName = "My_Motoko_App"; @@ -108,7 +106,7 @@ persistent actor { [Variants](./variants.md) are stable when their tags contain only stable types. ```motoko no-repl -persistent actor { +actor { // Variants with stable tags are stable type UserStatus = { #online; @@ -126,7 +124,7 @@ persistent actor { [Option](./options.md) types are stable when they contain stable types. ```motoko no-repl -persistent actor { +actor { // Option types with stable inner types are stable var optionalDeadline : ?Nat = ?1640995200000; var optionalMessage : ?Text = null; @@ -138,7 +136,7 @@ persistent actor { The [`Region`](https://mops.one/core/docs/Region) type, which provides low-level memory management, is stable. ```motoko no-repl -persistent actor { +actor { // Regions are stable var storage : Region = Region.new(); } @@ -149,7 +147,7 @@ persistent actor { References to [actors](../actors/actors-async.md) are stable, allowing stable canister-to-canister interactions. ```motoko no-repl -persistent actor { +actor { // Actor types are stable type LoggerActor = actor { log : shared (message : Text) -> async (); @@ -163,7 +161,7 @@ persistent actor { Simple objects with mutable fields (but no methods) are stable. Such simple objects are the same as records. ```motoko no-repl -persistent actor { +actor { object user = { var name = "Motoko"; var loginCount = 0; diff --git a/docs/languages/motoko/fundamentals/types/type-conversions.md b/docs/languages/motoko/fundamentals/types/type-conversions.md index d4877b0b..e2eead88 100644 --- a/docs/languages/motoko/fundamentals/types/type-conversions.md +++ b/docs/languages/motoko/fundamentals/types/type-conversions.md @@ -62,7 +62,7 @@ import Char "mo:core/Char"; import Nat8 "mo:core/Nat8"; import Nat32 "mo:core/Nat32"; -persistent actor CharConverter{ +actor CharConverter{ func nat8ToChar(n : Nat8) : Char { Char.fromNat32(Nat32.fromNat(Nat8.toNat(n))); }; @@ -77,7 +77,7 @@ Motoko provides a built-in function `Text.toLower`, which converts all character ```motoko no-repl import Text "mo:core/Text"; -persistent actor CaseConverter{ +actor CaseConverter{ func toLowercaseExample(s : Text) : Text { return Text.toLower(s); }; @@ -93,7 +93,7 @@ persistent actor CaseConverter{ ```motoko no-repl import Text "mo:core/Text"; -persistent actor TextToBlobConverter { +actor TextToBlobConverter { func textToOptionalBlob(s : Text) : ?Blob { ?Text.encodeUtf8(s); }; @@ -169,7 +169,7 @@ Motoko lacks support for dynamic objects, so an array of tuples is converted int import HashMap "mo:core/HashMap"; import Text "mo:core/Text"; -persistent actor MapConverter { +actor MapConverter { func arrayToMap(arr : [(Text, Nat)]) : HashMap.HashMap { let map = HashMap.HashMap(arr.size(), Text.equal, Text.hash); for ((key, value) in arr.values()) { @@ -190,7 +190,7 @@ type User = { name : Text; age : Nat; }; -persistent actor TupleConverter{ +actor TupleConverter{ func tuplesToUsers(arr : [(Text, Nat)]) : [User] { Array.map<(Text, Nat), User>(arr, func((name, age)) { { name; age } diff --git a/docs/languages/motoko/icp-features/caller-identification.md b/docs/languages/motoko/icp-features/caller-identification.md index 5e929988..871f9b28 100644 --- a/docs/languages/motoko/icp-features/caller-identification.md +++ b/docs/languages/motoko/icp-features/caller-identification.md @@ -24,7 +24,7 @@ In this example, the shared function `inc()` specifies a `msg` parameter, a reco To access the caller of an actor class constructor, you use the same syntax on the actor class declaration: ```motoko no-repl -shared(msg) persistent actor class Counter(init : Nat) { +shared(msg) actor class Counter(init : Nat) { // ... msg.caller ... } ``` diff --git a/docs/languages/motoko/icp-features/candid-serialization.md b/docs/languages/motoko/icp-features/candid-serialization.md index 287d64df..899fc1cb 100644 --- a/docs/languages/motoko/icp-features/candid-serialization.md +++ b/docs/languages/motoko/icp-features/candid-serialization.md @@ -106,7 +106,7 @@ In this example, use the imported `call` function to make a dynamic call on the import Principal "mo:core/Principal"; import { call } "mo:core/InternetComputer"; -persistent actor MyActor { +actor MyActor { public func concat(ts : [Text]) : async Text { var r = ""; diff --git a/docs/languages/motoko/icp-features/randomness.md b/docs/languages/motoko/icp-features/randomness.md index 92a8cbbe..7615cf2a 100644 --- a/docs/languages/motoko/icp-features/randomness.md +++ b/docs/languages/motoko/icp-features/randomness.md @@ -33,7 +33,7 @@ The `raw_rand` function is a system API provided by the [ICP management canister Since `raw_rand` operates asynchronously, canisters must await its response before using the generated bytes. Each call returns a 32-byte (256-bit) random `Blob`. ```motoko no-repl -persistent actor { +actor { let SubnetManager : actor { raw_rand() : async Blob; } = actor "aaaaa-aa"; @@ -53,7 +53,7 @@ Below is an example demonstrating how to generate a random boolean using `Random ```motoko no-repl import Random "mo:core/Random"; -persistent actor { +actor { transient let random = Random.crypto(); public func main() : async () { @@ -95,7 +95,7 @@ The following example demonstrates generating a UUID v4 using `idempotency-keys` import UUID "mo:idempotency-keys/UUID"; import Random "mo:core/Random"; -persistent actor { +actor { public func generateUUID() : async Text { let seed = await Random.blob(); UUID.v4(seed); diff --git a/docs/languages/motoko/icp-features/stable-memory.md b/docs/languages/motoko/icp-features/stable-memory.md index 5c414f44..6a3f5a31 100644 --- a/docs/languages/motoko/icp-features/stable-memory.md +++ b/docs/languages/motoko/icp-features/stable-memory.md @@ -5,9 +5,9 @@ sidebar: order: 5 --- -Canisters have two types of storage: Wasm memory and stable memory. The Wasm memory is often referred to as the [heap memory](/concepts/orthogonal-persistence#heap-wasm-linear-memory). It is automatically used for heap-allocated objects and has a maximum size limitation of 4 GiB or 6 GiB respective to whether you are using 32-bit or 64-bit heap storage without enhanced orthogonal persistence. When a canister is upgraded, the heap memory is cleared, only retaining data stored in stable variables. +Canisters have two types of storage: Wasm memory and stable memory. The Wasm memory is often referred to as the [heap memory](/concepts/orthogonal-persistence#heap-wasm-linear-memory). It is automatically used for heap-allocated objects and, with Motoko's [enhanced orthogonal persistence](../fundamentals/actors/orthogonal-persistence/enhanced.md), is a 64-bit memory with a maximum size of 6 GiB that is retained across canister upgrades. -Stable memory has a maximum size of 500 GiB and is preserved across canister upgrades. Motoko utilizes [stable memory](/concepts/orthogonal-persistence#stable-memory) through the [stable storage feature](/concepts/orthogonal-persistence#motoko-true-orthogonal-persistence) to preserve data across canister upgrades. Stable regions extend this functionality to allow more structured and flexible memory management. +Stable memory has a maximum size of 500 GiB and is preserved across canister upgrades. Motoko's stable variables do not need [stable memory](/concepts/orthogonal-persistence#stable-memory), as they live in the persistent main memory. Stable regions instead give direct access to stable memory, allowing more structured and flexible memory management. The system automatically commits all memory modifications, both Wasm and stable, after the successful execution of a message. If a message execution fails, the changes are not committed. @@ -148,7 +148,7 @@ This example illustrates the simultaneous use of stable variables and stable mem import Nat64 "mo:core/Nat64"; import Region "mo:core/Region"; -persistent actor StableLog { +actor StableLog { // Index of saved log entry. public type Index = Nat64; diff --git a/docs/languages/motoko/icp-features/system-functions.md b/docs/languages/motoko/icp-features/system-functions.md index 0755138e..f01fa069 100644 --- a/docs/languages/motoko/icp-features/system-functions.md +++ b/docs/languages/motoko/icp-features/system-functions.md @@ -52,7 +52,7 @@ system func timer(setGlobalTimer : Nat64 -> ()) : async () { The `preupgrade()` system function is invoked immediately before a canister upgrade. It runs before the new Wasm module is installed, giving the current version one last chance to act. The function takes no arguments and must have type `() -> ()`. ```motoko no-repl -persistent actor MyCanister { +actor MyCanister { system func preupgrade() { // Runs before the upgrade installs the new Wasm. } @@ -60,7 +60,7 @@ persistent actor MyCanister { ``` :::danger -If `preupgrade` traps, runs out of cycles, or hits any other IC computing limit, **the upgrade fails and the canister cannot be upgraded going forward**: it is stuck on the current version. Use of this hook is discouraged. +If `preupgrade` traps, runs out of cycles, or hits any other IC computing limit, **the upgrade fails and the canister cannot be upgraded going forward**: it is stuck on the current version. This hook is deprecated (warning M0270) in favor of migration functions. ::: With orthogonal persistence, `mo:core` data structures persist across upgrades automatically and this hook is rarely needed. For the (legacy) save-into-stable-storage pattern and the migration alternatives that replace it, see [Data persistence](../fundamentals/actors/data-persistence.md). @@ -70,14 +70,14 @@ With orthogonal persistence, `mo:core` data structures persist across upgrades a The `postupgrade()` system function runs immediately after an upgrade installs the new Wasm. The function takes no arguments and must have type `() -> ()`. ```motoko no-repl -persistent actor MyCanister { +actor MyCanister { system func postupgrade() { // Runs after the upgrade installs the new Wasm. } } ``` -`postupgrade` is rarely required: the same effect can usually be achieved with actor initialization expressions (`let` bindings and statements at the top of the actor body), which run on every install and upgrade. See [Data persistence](../fundamentals/actors/data-persistence.md) for the recommended patterns. +`postupgrade` is deprecated (warning M0270) and rarely required: the same effect can usually be achieved with actor initialization expressions (`let` bindings and statements at the top of the actor body), which run on every install and upgrade. See [Data persistence](../fundamentals/actors/data-persistence.md) for the recommended patterns. ## `lowmemory()` @@ -113,7 +113,7 @@ The following actor defines an inspect function that blocks anonymous callers, l ```motoko no-repl import Principal "mo:core/Principal"; -persistent actor Counter { +actor Counter { var c = 0; diff --git a/docs/languages/motoko/icp-features/view-queries.md b/docs/languages/motoko/icp-features/view-queries.md deleted file mode 100644 index e51c4efe..00000000 --- a/docs/languages/motoko/icp-features/view-queries.md +++ /dev/null @@ -1,187 +0,0 @@ ---- -title: "Stable variable inspection" -description: "Using --generate-view-queries to auto-generate query methods that expose stable variable contents in Motoko." -sidebar: - order: 7 ---- - -The `--generate-view-queries` compiler flag instructs `moc` to auto-generate **query methods** that expose the contents of an actor's stable variables for inspection at runtime. This enables tooling, dashboards, and generic front-end UIs to browse canister state without writing boilerplate accessor code. - -## Usage - -``` -moc --generate-view-queries ... -``` - -The flag is **off by default**. When enabled, the compiler examines every stable variable in the actor and, where possible, emits a corresponding query function. - -## How it works - -For each stable variable `id` of type ``, the compiler attempts to generate a public query method named `__id`. Generation is skipped if: - -- A member named `__id` is already declared in the actor (user-defined methods take precedence). -- The name `__id` has the reserved prefix `__motoko`. - -When generation proceeds, the compiler chooses one of two strategies: - -### 1. `.view()` method available - -If the expression `id.view()` resolves to a function with shared argument types `(T1, ..., TN)` and shared result type `R`, the compiler generates: - -```motoko no-repl -public shared ({ caller }) query func __id(arg1 : T1, ..., argN : TN) : async R { - ; - id.view()(arg1, ..., argN) -}; -``` - -The `.view()` call may rely on implicit arguments (such as `compare`) provided they resolve at the call site. This is the preferred strategy because it lets library authors define **logical views** of non-shared data structures. For example, a B-tree map can present its entries as paginated key-value pairs instead of exposing its internal node layout. - -### 2. Shared-type fallback - -If no `.view()` method is available but `` is a shared type, the compiler generates a simple accessor: - -```motoko no-repl -public shared ({ caller }) query func __id() : async { - ; - id -}; -``` - -### 3. No generation - -### 3. Approximation to `Any` - -If neither condition is met (no `.view()` and the type is not shared), the query just returns a value of the non-informative type `Any`. - -## Access control - -Every generated query enforces that the `caller` must be either: - -1. The canister **itself** (self-calls), or -2. A **controller** of the canister. - -Unauthorized callers receive a trap: `"Unauthorized caller (caller must be self or a controller)"`. - -## Candid interface behaviour - -- **Public interface** (custom section / `--public-metadata candid:service`): Generated view queries are **excluded**. This means upgrading a canister never requires view methods to be backward-compatible, and stable variable implementation details do not leak into the public API. -- **Local `.did` file** (`moc --idl`): Generated view queries **are included**. This allows generic front-end tools to parse the full Candid description and present a type-driven UI for data inspection. - -## Writing custom `.view()` methods - -A `.view()` method is a function resolved via contextual dot syntax on a stable variable's type. It must return a function whose argument and return types are all shared. - -### Signature pattern - -```motoko no-repl -module MyView { - public func view<...>(self : , ...) : (arg1 : T1, ..., argN : TN) -> R = ... -} -``` - -Implicit arguments (like `compare` for ordered collections) are supported and resolved automatically by the compiler. - -### Example: paginated map view - -```motoko no-repl -module MapView { - public func view( - self : Map.Map, - compare : (implicit : (K, K) -> Order.Order) - ) : (ko : ?K, count : ?Nat) -> [(K, V)] = - func(ko, count) { - let entries = switch ko { - case null { self.entries() }; - case (?k) { self.entriesFrom(k) }; - }; - switch count { - case null { entries.toArray() }; - case (?c) { entries.take(c).toArray() }; - }; - }; -}; -``` - -Given a stable variable: - -```motoko no-repl -let customers : Map.Map = Map.empty(); -``` - -The compiler generates a query with the Motoko signature: - -```motoko no-repl -public shared query func __customers(ko : ?Text, count : ?Nat) : async [(Text, Customer)] -``` - -And corresponding Candid signature: - -```candid -__customers : (ko : opt text, count : opt nat) -> (vec record { text; Customer }) query; -``` - -### Reusable view mixin - -View modules for common core data structures (`Map`, `Set`, arrays, `List`, `Stack`, `Queue`, and their pure counterparts) can be collected into a **mixin** and included in any actor: - -```motoko no-repl -import Views "views"; - -persistent actor { - include Views(); - - let customers : Map.Map = Map.empty(); - // __customers query is auto-generated using MapView.view -}; -``` - -## Example: full actor - -```motoko no-repl -//MOC-FLAG --generate-view-queries -import Array "mo:core/Array"; - -persistent actor Self { - - module ArrayView { - public func view(self : [var V]) : - (start : Nat, count : Nat) -> [V] = - func(start, count) { - Array.tabulate(count, func i { self[start + i] }) - }; - }; - - // .view() available -> generates paginated query - let array : [var (Nat, Text)] = [var (1, "1"), (2, "2")]; - - // shared type, no .view() -> generates simple accessor - var some_variant = #node(#leaf, 0, #leaf); - let some_record = { a = 1; b = "hello"; c = true }; - - // non-shared type, no .view() -> no query generated - let some_mutable_record = { var a = 1 }; -}; -``` - -The actor above exposes the following generated queries: - -| Stable variable | Strategy | Generated query | -|-------------------------|-----------------|----------------------------------------------------------| -| `array` | `.view()` | `__array(start : Nat, count : Nat) : async [(Nat, Text)]` | -| `some_variant` | shared fallback | `__some_variant() : async Tree` | -| `some_record` | shared fallback | `__some_record() : async {a : Nat; b : Text; c : Bool}` | -| `some_mutable_record` | approximated | `__some_mutable_record() : async Any` | - -## Limitations - -- View queries are **not** part of the canister's public Candid interface and are therefore invisible to other canisters importing the actor's type. -- Only controllers and the canister itself may call the generated queries; there is currently no hook for application-level authorization. -- If a `.view()` method returns a non-shared type, the variable is treated as if no `.view()` exists (the view is silently skipped). - -## Links - -The sample project https://github.com/crusso/motoko-stable-viewer is a simple database application. -It provides its own, reusable `views.mo` mixin, that adds simple paginated views to `core` collections. -Its frontend uses a generic react component that renders the backend's stable variables. -The rendering is driven by the backend's Candid interface file. diff --git a/docs/languages/motoko/index.md b/docs/languages/motoko/index.md index a25f9e16..b9e1e45b 100644 --- a/docs/languages/motoko/index.md +++ b/docs/languages/motoko/index.md @@ -11,7 +11,7 @@ Motoko is a high-level programming language designed for AI agents building back **Actor model.** Every Motoko canister is an actor: an isolated unit of state and behavior that communicates with other actors through asynchronous messages. This maps directly to how canisters work on ICP: each canister has private state and a public interface. -**Orthogonal persistence.** Variables declared in a `persistent actor` survive canister upgrades automatically. There is no database layer, no serialization code, and no pre/post-upgrade hooks needed for most use cases. See [Orthogonal persistence](/concepts/orthogonal-persistence) for how this works at the platform level. +**Orthogonal persistence.** Actors persist their state across canister upgrades automatically: the variables you declare in an actor keep their values for the lifetime of the canister. There is no database layer, no serialization code, and no pre/post-upgrade hooks needed for most use cases. See [Orthogonal persistence](/concepts/orthogonal-persistence) for how this works at the platform level. **Async/await messaging.** Inter-canister calls use `async`/`await`, making sequential message flows read like synchronous code. The compiler and runtime handle the underlying callback mechanics. @@ -24,7 +24,7 @@ Motoko is a high-level programming language designed for AI agents building back A minimal Motoko canister with a query method and an update method: ```motoko -persistent actor Counter { +actor Counter { var count : Nat = 0; public query func get() : async Nat { @@ -95,6 +95,7 @@ For more, browse the full [Motoko examples collection](https://github.com/dfinit ## Further reading +- [Migrating from moc 1 to moc 2](moc-v2-migration.md): the breaking changes in `moc` 2.0 and how to fix each one - [Quickstart](/getting-started/quickstart): create and deploy your first canister - [core library API docs](https://mops.one/core/docs): standard library reference - [Orthogonal persistence](/concepts/orthogonal-persistence): how persistent memory works at the platform level diff --git a/docs/languages/motoko/moc-v2-migration.md b/docs/languages/motoko/moc-v2-migration.md new file mode 100644 index 00000000..e0a60a98 --- /dev/null +++ b/docs/languages/motoko/moc-v2-migration.md @@ -0,0 +1,414 @@ +--- +title: "Migrating from moc 1 to moc 2" +description: "What changes when you move a project from moc 1.16 to moc 2.0, and how to fix each breaking change." +sidebar: + order: 2 + label: "Migrating to moc 2" +--- + +This guide is for projects on `moc` 1.16.x whose canisters already use enhanced orthogonal persistence (EOP), the 1.16 default. It lists every breaking change in `moc` 2.0 with the message you will see and the fix. + +Most projects need only a few changes: removing flags that no longer exist, fixing code that the former warnings now reject, and optionally deleting the now-redundant `persistent` and `stable` keywords. + +## Upgrade checklist + +1. Build cleanly with the latest 1.16.x first. The `.vals()` (M0269) and `preupgrade`/`postupgrade` (M0270) deprecation warnings are the same in 2.0, so you can fix them before switching. +2. Bump the toolchain in `mops.toml`: + + ```toml + [toolchain] + moc = "2.0.0" + ``` + +3. Remove the [removed flags](#removed-flags) from `[moc].args`, `[canisters.].args` and your build scripts. `moc` rejects each one with `unknown option`. +4. Run `mops check` (or `mops build`) and fix what it reports, in this order: + 1. syntax errors: [`??`](#-is-whitespace-sensitive), [glued branches](#if-while-for-and-switch-heads), [`flexible`](#flexible-is-removed); + 2. [errors that used to be warnings](#warnings-that-are-now-errors); + 3. [redundant `async` in function bodies](#async-function-bodies) (M0277); + 4. library and module errors: [M0142](#libraries-must-be-modules-m0142), [M0193](#actor-class-return-type-m0193); + 5. persistence errors such as M0131, which only appear if you used [`--legacy-actors`](#actors-are-persistent-by-default). +5. Check upgrade compatibility against the deployed version (`mops check-stable`, or `moc --stable-compatible old.most new.most`) before you upgrade a live canister. + +## Summary + +| Change | What you see | Fix | +|---|---|---| +| [Actors are persistent by default](#actors-are-persistent-by-default) | warnings M0217, M0218 | delete `persistent` and `stable`; mark reset-on-upgrade fields `transient` | +| [`flexible` removed](#flexible-is-removed) | `syntax error [M0001]` | write `transient` | +| [`preupgrade`/`postupgrade` deprecated](#preupgrade-and-postupgrade) | warning M0270 | a migration function | +| [`??` whitespace and right-hand side](#-is-whitespace-sensitive) | `syntax error [M0001]`, M0273 | `a ?? b`; `a ?? do { ... }` for a block | +| [Glued `if` branches](#if-while-for-and-switch-heads) | M0275 | put a space before the branch | +| [`.vals()` deprecated](#vals-is-deprecated) | warning M0269 | `.values()` | +| [`async` function bodies](#async-function-bodies) | M0277 | drop the inner `async`; or nothing, for new inference | +| [Record update copies `var` fields](#record-update-copies-var-fields) | nothing (was error M0179) | none, unless you relied on `--experimental-field-aliasing` | +| [Warnings that are now errors](#warnings-that-are-now-errors) | M0145, M0222, M0210, M0212, M0215, M0128, M0242, M0005, M0276, M0278 | per code, below | +| [Inferred `Any`/`None`](#inferred-any-or-none) | M0074, M0081, M0101, M0166, M0167 | fix the code, or annotate `Any` | +| [Patterns that do not fit the type](#patterns-that-do-not-fit-the-type) | errors M0116, M0050, M0115 | fix the pattern, or annotate a larger type | +| [Bare-declaration libraries](#libraries-must-be-modules-m0142) | error M0142 | wrap in `module { ... }` | +| [Actor class return type](#actor-class-return-type-m0193) | error M0193 | `: async actor { ... }` | +| [Removed primitives](#removed-primitives-and-experimentalstablememory) | M0072 in `ExperimentalStableMemory` | use `Region` | +| [Read-only primitives drop ``](#read-only-primitives-no-longer-take-system) | warning M0196 | delete `` | +| [Removed flags](#removed-flags) | `unknown option` | see the table | +| [`moc --check` per file](#moc---check-checks-each-file-on-its-own) | M0057 unbound variable; `-r expects exactly one source file` | use imports | +| [New default warnings](#new-default-warnings) | M0146, M0217, M0236 | fix them, or `-A` them if you build with `-Werror` | +| [`moc.js` API](#mocjs) | `Invalid_argument` | `Motoko.run([], ...)`; `gcFlags` `"force"`/`"scheduling"` only | +| [Release artifacts](#release-artifacts) | no Intel-Mac or `base` tarball | on Intel Macs build from source, stay on moc 1, or use `moc.js` | + +## Persistence + +### Actors are persistent by default + +In 2.0 every `actor` and `actor class` is persistent: its `let` and `var` fields keep their values across upgrades unless declared `transient`. The `persistent` keyword and the `stable` modifier are redundant, and `moc` reports them as warnings M0217 and M0218. Both still compile, and deleting them leaves the stable signature unchanged. + +```motoko no-repl +// moc 1 +persistent actor { + stable var count : Nat = 0; + transient var hits : Nat = 0; +}; +``` + +```motoko no-repl +// moc 2 +actor { + var count : Nat = 0; + transient var hits : Nat = 0; +}; +``` + +With moc 1.16 defaults, an actor without `persistent` was already an error (M0219 on each unmarked field, M0220 on the actor), so your code says `persistent actor` and compiles under 2.0 without changes. + +If you built with `--legacy-actors`, unmarked fields used to be transient and are now persistent. Mark every field that should reset on upgrade `transient`. A field whose type cannot be persisted, such as a function or an object with methods, is now error M0131 until you mark it: + +```motoko no-repl +actor { + transient let log = func (t : Text) { Debug.print(t) }; + transient var cache : [Nat] = []; +}; +``` + +A field that was transient and becomes persistent needs no migration. On the first upgrade it is new to the stable signature, so it runs its initializer, and from then on it keeps its value. + +See [Data persistence](fundamentals/actors/data-persistence.md). + +### `flexible` is removed + +`flexible` was an alias of `transient`. It is now an ordinary identifier, so `flexible var x = 0` is a syntax error. Write `transient var x = 0`. + +### `preupgrade` and `postupgrade` + +`system func preupgrade` and `system func postupgrade` still work in 2.0, with deprecation warning M0270. Replace them with a migration function, which cannot leave a canister stuck the way a trapping `preupgrade` can. + +The usual reason for these hooks is a data structure that could not be persisted, such as `mo:base/HashMap`, copied into a stable array on the way out and rebuilt on the way in: + +```motoko no-repl +// moc 1 +import HashMap "mo:base/HashMap"; +import Iter "mo:base/Iter"; +import Text "mo:base/Text"; + +persistent actor { + var entries : [(Text, Nat)] = []; + transient let users = HashMap.HashMap(16, Text.equal, Text.hash); + + public func set(k : Text, v : Nat) : async () { users.put(k, v) }; + + system func preupgrade() { + entries := Iter.toArray(users.entries()); + }; + + system func postupgrade() { + for ((k, v) in entries.vals()) { users.put(k, v) }; + entries := []; + }; +}; +``` + +The data structures in `mo:core` are stable, so the map can be an ordinary persistent field. A one-time migration function moves the existing entries into it: + +```motoko no-repl +// moc 2 +import Map "mo:core/Map"; +import Text "mo:core/Text"; + +(with migration = func(old : { var entries : [(Text, Nat)] }) : { users : Map.Map } { + { users = Map.fromIter(old.entries.values(), Text.compare) } +}) +actor { + let users : Map.Map = Map.empty(); + + public func set(k : Text, v : Nat) : async () { users.add(k, v) }; +}; +``` + +The upgrade that installs this version still runs the old version's `preupgrade`, so `entries` holds the data when the migration runs. `moc` reports the dropped field as warning M0207, which is intended here. Once this version is deployed, delete the `(with migration = ...)` clause: later versions no longer have an `entries` field to consume. + +If you use the [enhanced migration chain](fundamentals/actors/enhanced-multi-migration.md) (`--enhanced-migration`), put the same function in the next file of `migrations/` as `public func migration`. + +Code that only initializes something in `postupgrade` can move into the actor body, which runs on every install and upgrade. See [Explicit migration using a migration function](fundamentals/actors/compatibility.md#explicit-migration-using-a-migration-function). + +### Canisters still on classical persistence + +`moc` 2 can no longer produce classical-persistence canisters, but it can still upgrade one. Compile that one-time upgrade with `--enhanced-orthogonal-persistence` and without `--enhanced-migration`, as described in [Migration path](fundamentals/actors/orthogonal-persistence/enhanced.md#migration-path). Later upgrades need neither flag. If your canisters are already on EOP, `--enhanced-orthogonal-persistence` is accepted and has no effect, so you can remove it. + +A canister that moved to EOP with a `moc` older than 1.5.0 may trap on upgrade with `cannot upgrade from an actor using enhanced migration to an actor not using enhanced migration`; see [Migration path](fundamentals/actors/orthogonal-persistence/enhanced.md#migration-path) for the fix. + +## Syntax + +### `??` is whitespace-sensitive + +The null-coalescing operator must be followed by whitespace, as `<` and `>` already are. `??x` with no space is two option introductions, `?(?x)`. + +```motoko no-repl +let n = o ??0; // moc 1: 0 if o is null. moc 2: syntax error +let n = o ?? 0; // both +``` + +The right-hand side of `??` is now an expression, so `{` there opens a record literal. A block needs `do`: + +```motoko no-repl +let r = o ?? { x = 1 }; // record literal, new in moc 2 +let n = o ?? do { let k = f(); k + 1 }; // block: was `o ?? { ... }`, now M0273 without `do` +``` + +### `if`, `while`, `for` and `switch` heads + +A condition, scrutinee or `for` collection may be any expression without parentheses. The branches or body are then blocks: + +```motoko no-repl +if f(x) { a } else { b }; +while n > 0 { n -= 1 }; +for x in xs.values() { total += x }; +switch p.x { + case 0 { "zero" } + case _ { "other" } +}; +``` + +The moc 1 forms, such as `if (c) a else b`, `for (x in xs) ...` and `switch (e) { ... }`, still work. Two things break: + +- **A bare branch glued to the condition.** A `(`, `[` or prefix operator directly after a name or a parenthesized condition now continues the condition. Put a space before the branch: + + ```motoko no-repl + if c[0] else [1]; // moc 2: `c[0]` is the condition, M0275 + if c [0] else [1]; // branch `[0]` + + if (c)-1 else 1; // moc 2: `(c)-1` is the condition, M0275 + if (c) -1 else 1; // branch `-1` + ``` + +- **Bare branches after a compound condition** (M0275). When the condition is more than a name or a parenthesized expression, the branches must be blocks. Write `if f(x) { a } else { b }`, or keep `if (f(x)) a else b`. + +A record literal as a head needs parentheses, `switch ({ x = 0 }) { ... }` (M0272). The [style guide](reference/style-guide.md#parentheses) has the spacing rules. + +### Lighter `switch` cases + +These forms are new and optional; code in the moc 1 style still compiles. + +- The `;` between cases can be dropped. +- A case pattern that is a literal, `null`, `?p`, `#tag`, `#tag(p)` or `_` needs no parentheses, and can be combined with `or`, `and` and `: T`. +- A variant payload always has its own parentheses: `case #node(n)`, never `case #node n`. + +```motoko no-repl +switch t { + case #leaf { 0 } + case #node(n) { n } +}; + +switch n { + case -1 { "negative" } + case 0 or 1 { "small" } + case _ { "large" } +}; +``` + +A case body is a block, so a record literal in it must be nested: `case null { { x = 0 } }`. Writing `case null { x = 0 }` is M0272. + +### `.vals()` is deprecated + +The built-in `.vals()` on arrays and `Blob` is deprecated with warning M0269. Use `.values()`: + +```motoko no-repl +for x in xs.values() { total += x }; +``` + +### `async` function bodies + +The body of a function returning `async T` is implicitly asynchronous, whether it is a block or `= e`. A function passed where an `async` function type is expected is asynchronous too, and takes its parameter types from that type, so the annotations moc 1 needed can go: + +```motoko no-repl +func run(f : Nat -> async Nat) : async Nat { await f(1) }; +let n = await run(func(n) { n + 1 }); // moc 1: func(n : Nat) : async Nat { n + 1 } +``` + +In moc 1 an `= e` body was not made asynchronous, so `= async { ... }` spelled out the `async`. That is now redundant and rejected with M0277, whose fix-it removes it: + +```motoko no-repl +func f() : async Nat = async { 1 }; // M0277 +func f() : async Nat { 1 }; // or: = 1 +``` + +The same goes for `= ignore (async ...)` in a one-way `shared` function. Two moc 1 forms have no direct replacement: + +- a parenthetical on the body, `= (with cycles = n) async { ... }`, goes at the call site instead: `(with cycles = n) f()`; +- a local function can no longer return a future of the function around it (`let fut = g(); func f() : async T = fut`); await `fut` in the function that created it. + +### Record update copies `var` fields + +`{ base with ... }` now copies the base's `var` fields into new cells, like the equivalent record literal. In moc 1 this was error M0179, unless you passed `--experimental-field-aliasing`, which made the copy share the base's cells. + +```motoko no-repl +let base = { var count = 0; name = "a" }; +let copy = { base with name = "b" }; +copy.count += 1; // base.count is still 0 +``` + +If you relied on aliasing, keep the mutable state in a shared object and reference it from both records. + +## Warnings that are now errors + +These diagnostics flag code that traps, or silently does something other than what it says. They are errors by default in 2.0. Each can be downgraded back to a warning with `-W `, for example `-W=M0145,M0215` in `[moc].args`. Treat this as a stopgap while you fix the code. + +| Code | Flags | Before | After | +|---|---|---|---| +| M0145 | a pattern that does not cover every value, in `switch`, `let`, `catch`, `for` and function parameters | `let #ok(f) = r;` | `let #ok(f) = r else { return 0 };`, or add `case _ { ... }` | +| M0222 | `ignore` of an `async*` value, which never runs | `ignore bump();` | `await* bump();` | +| M0210 | a parenthetical on an `await*` call, where it has no effect | `await* (with cycles = n) pay();` | attach cycles to an `async` call: `await (with cycles = n) pay();` | +| M0212 | an unknown parenthetical attribute | `(with cycle = n)` | `(with cycles = n)` | +| M0215 | a record field the expected type drops, for example a typo in an update | `{ u with emial = e }` | `{ u with email = e }` | +| M0128 | a function named like a system method, but not declared `system` | `func heartbeat() : async () { ... }` | `system func heartbeat() : async () { ... }`, or rename it | +| M0242 | a `public func` without a return type, which is implicitly oneway | `public func log(t : Text) { ... }` | `public func log(t : Text) : () { ... }`, or `: async ()` | +| M0005 | an import path whose letter case differs from the file name | `import L "lib";` for `Lib.mo` | `import L "Lib";` | +| M0276 | a comparison at a type with a single value, such as `Any` or `{}`, whose result is constant | `user == order` for records that share no field | compare the fields you mean: `user.id == order.userId` | +| M0278 | a file in the `--enhanced-migration` directory that does not export a public `migration` function, which never runs (was warning M0251) | `public func migrate(old : { ... }) : { ... }` | `public func migration(old : { ... }) : { ... }`, or move helper modules out of the directory | + +### Inferred `Any` or `None` + +When the type `moc` infers collapses to `Any` or `None`, the value is useless and the code is almost always a mistake. Joins that leave a smaller but useful type, such as two records reduced to their common fields, are unaffected. + +| Code | Before | After | +|---|---|---| +| M0074 | `let xs = [1, "two"];` | make the elements agree, or annotate `let xs : [Any] = [1, "two"];` | +| M0081 | `if b { 1 } else { "one" }` | make the branches agree, or annotate `: Any` | +| M0101 | `switch o { case null { "none" } case ?n { n } }` | `case ?n { debug_show n }` | +| M0166 | `type U = Nat and Text;` (`None`) | write the intended type | +| M0167 | `type V = Nat or Text;` (`Any`) | write `Any`, or the intended type | + +### Patterns that do not fit the type + +A pattern is now checked against the type of the value it matches, not a larger type. Before, a pattern that the type could not contain was accepted and only flagged as never matched (M0146, or M0145 in a plain `let`), or not at all in `let ... else`, so the case silently never ran. These are type errors now and cannot be downgraded with `-W`: + +| Pattern | Matched type | Error | +|---|---|---| +| `#suspnded`, a tag the variant type lacks | `{#active; #suspended}` | M0116, with "did you mean tag #suspended?" | +| `-1` or `+1`, a signed literal | `Nat` | M0050, as for `let n : Nat = -1` | +| `?x`, an option pattern | `Null` | M0115 | + +To match at a larger type on purpose, annotate the matched value: `switch (n : Int) { case (-1) { ... }; ... }`. When the scrutinee's inferred type is narrower than intended, as in `let mode = #dev; switch mode { case (#prod) { ... }; ... }`, annotate the value: `let mode : {#dev; #prod} = #dev`. A pattern that fits the type but can never be reached, such as a duplicate case, stays warning M0146. + +## Libraries and modules + +### Libraries must be modules (M0142) + +An imported file that is a bare sequence of declarations is now error M0142, and `-W M0142` no longer works. Wrap the declarations in `module { ... }` and mark the exported ones `public`: + +```motoko no-repl +// Lib.mo +module { + public func greet() : Text { "hi" }; +}; +``` + +### Actor class return type (M0193) + +An actor class whose declared return type is not `async` is now error M0193 (it was warning M0135): + +```motoko no-repl +actor class C() : actor {} { }; // M0193 +actor class C() : async actor {} { }; +``` + +### Implicits and dot notation search nested modules + +Implicit arguments and contextual dot (`e.f(...)`) are also resolved from modules nested inside the modules in scope, up to a depth of 8, so importing a facade that re-exports its package's modules, such as `public let Map = _Map;`, is enough for both. This is not a breaking change: nested modules are only searched when no module in scope has a matching direct field, so every call that resolved in moc 1 resolves to the same function. Calls that moc 1 rejected may now resolve, or be ambiguous between two nested modules (M0224, M0231); the [language manual](reference/language-manual.md#resolution-of-dotted-calls-and-implicit-arguments) has the rules. + +### Removed primitives and `ExperimentalStableMemory` + +- The `stableMemory*` primitives are gone, so importing `mo:base/ExperimentalStableMemory` fails to type-check. Use `Region` from `mo:core` (or `mo:base`) instead. The rest of `base` still compiles; for new code use [`core`](base-core-migration.md). +- `Prim.createActor` is removed. Use actor classes, or the management canister's `create_canister` and `install_code`. +- Canisters no longer export the `__motoko_stable_var_info` query, which always trapped under EOP. + +### Read-only primitives no longer take `` + +`Prim.getSelfPrincipal`, `Prim.envVarNames`, `Prim.envVar`, `Prim.callerInfoSigner`, `Prim.callerInfoData`, `Prim.getCandidLimits` and `Prim.getCandidTypeLimits` no longer need the `system` capability, so `query` methods can call them. An explicit `` on them is warning M0196; delete it: + +```motoko no-repl +public query func me() : async Principal { Prim.getSelfPrincipal() }; +``` + +## Compiler and tooling + +### Removed flags + +`moc` rejects these flags with `unknown option`. Remove them. + +| Flag | Replacement | +|---|---| +| `--legacy-persistence` | none: `moc` 2 cannot build classical-persistence canisters. Keep an older `moc` for those. | +| `--default-persistent-actors`, `--require-persistent-actors` | no longer needed: actors are persistent by default | +| `--legacy-actors` | mark fields `transient` explicitly | +| `--copying-gc`, `--compacting-gc`, `--generational-gc` | none: the incremental GC is the only GC | +| `--incremental-gc` | no longer needed: always on | +| `--rts-stack-pages`, `--skip-gc-deprecation-warning`, `--experimental-rtti` | no longer needed | +| `--experimental-stable-memory` | use `Region` | +| `--generate-view-queries` | write `public query func` getters for the fields clients read. The generated `__` queries disappear from the canister's interface on upgrade. | +| `--experimental-multi-value`, `--no-experimental-multi-value` | no longer needed | +| `--experimental-field-aliasing` | none: [record update copies `var` fields](#record-update-copies-var-fields) | +| `--trap-on-call-error` | none: a failed call throws an `Error`; handle it with `try`/`catch` | +| `-no-system-api` | `-wasi-system-api` to run outside the Internet Computer, e.g. in `wasmtime` | +| `-ref-system-api` | no longer needed: it selected the default Internet Computer system API | +| `--print-source-on-error`, `-no-link`, `--profile`, `--profile-file`, `--profile-line-prefix`, `--profile-field` | none | + +`--enhanced-orthogonal-persistence` stays, for [classical canisters](#canisters-still-on-classical-persistence). + +### `moc --check` checks each file on its own + +`moc --check a.mo b.mo` checks each file in a scope that holds only its own imports. In moc 1 the files were concatenated into one program, so `b.mo` could use a declaration from `a.mo` without importing it; that is now M0057 (unbound variable). Compiling (`-c`, `--idl`) and running (`-r`) take exactly one main file, and the REPL (`-i`) preloads at most one. + +Move the shared declarations into a module and import it: + +```motoko no-repl +// Helper.mo +module { + public func helper() : Nat { 1 }; +}; +``` + +```motoko no-repl +// main.mo +import Helper "Helper"; + +let n = Helper.helper(); +``` + +### `-g` + +`-g` now emits only the DWARF line table (`.debug_line` and `.debug_line_str`). The `.debug_abbrev`, `.debug_addr` and `.debug_rnglists` sections are gone; they had no content without the `.debug_info` section, which `moc` never emitted. + +### New default warnings + +These warnings are new, or reported in new places, by default. They matter mainly if you build with `-Werror`: + +- M0146 is now also reported in `let ... else`, for an alternative that is never matched, such as the second `1` in `let (1 or 1) = n else { ... }`. Delete it. +- M0217: redundant `persistent` keyword. Delete it. +- M0236: a call that could use dot notation, such as `Map.size(map)` for `map.size()`. Apply the suggestion (`mops check --fix`), or silence it with `-A M0236`. + +## `moc.js` + +- `Motoko.run` no longer preloads files. Its first argument must be `[]`, as in `Motoko.run([], "main.mo")`; any other list raises `Invalid_argument`. Use imports instead. +- `gcFlags` accepts only `"force"` and `"scheduling"`. `"incremental"`, `"enhancedOP"`, `"copying"`, `"marking"`, `"generational"` and `"classicOP"` raise `Invalid_argument`; remove those calls. + +## Release artifacts + +- There is no Intel-Mac build (`motoko-Darwin-x86_64`). On an Intel Mac, build from source, stay on `moc` 1, or use `moc.js`. Apple Silicon (`motoko-Darwin-arm64`), `x86_64` Linux and `aarch64` Linux builds are unchanged. +- The `motoko-base-library.tar.gz` artifact is gone. Get `base` from [mops](https://mops.one/base); `motoko-core.tar.gz` is unchanged. diff --git a/docs/languages/motoko/reference/changelog.md b/docs/languages/motoko/reference/changelog.md index 8dac5180..d4a99714 100644 --- a/docs/languages/motoko/reference/changelog.md +++ b/docs/languages/motoko/reference/changelog.md @@ -8,6 +8,322 @@ sidebar: # Motoko compiler changelog +## 2.0.0 (2026-10-06) + +* Upgrading from `moc` 1.x? The [moc v1 → v2 migration guide](doc/md/moc-v2-migration.md) + lists every breaking change below with the message you will see and its fix (#6417). + +* motoko (`moc`) + + * **Persistence** + + * Breaking change: actors are `persistent` by default. A bare `actor` or + `actor class` keeps its fields across upgrades; mark fields that should + reset on upgrade `transient`. The `--default-persistent-actors`, + `--require-persistent-actors` and `--legacy-actors` flags are removed, + so the old transient-by-default behaviour cannot be restored (#6356). + + * Breaking change: classical (32-bit) persistence is removed. `moc` always + targets enhanced orthogonal persistence (EOP) with a persistent 64-bit + main memory, and the incremental GC is the only GC. The flags + `--legacy-persistence`, `--copying-gc`, `--compacting-gc`, + `--generational-gc`, `--rts-stack-pages`, + `--skip-gc-deprecation-warning`, `--incremental-gc` and + `--experimental-rtti` are rejected as unknown options. Projects that + still need a classical module must keep an older `moc` (1.14.x) + (#6362, #6380). + + * Existing classical canisters still upgrade. The runtime reads every + earlier classical stable-memory format, and a classical canister moves + to EOP on its next upgrade. Compile that one upgrade with + `--enhanced-orthogonal-persistence` and without `--enhanced-migration`; + otherwise it traps with "Detected implicit upgrade from classical + orthogonal persistence to enhanced orthogonal persistence" or "Cannot + upgrade from classical orthogonal persistence with + --enhanced-migration". The migration is irreversible and later upgrades + need no flag. `--enhanced-migration` no longer requires + `--enhanced-orthogonal-persistence` (#6362). + + * Breaking change: the `flexible` keyword is removed. Write `transient`; + `flexible` is now an ordinary identifier (#6407). + + * Breaking change: `ExperimentalStableMemory` is gone. The + `Prim.stableMemory*` primitives, `Prim.stableVarQuery`, the + `__motoko_stable_var_info` canister query and the + `--experimental-stable-memory` flag are removed, so importing + `mo:base/ExperimentalStableMemory` no longer type-checks; `core` is + unaffected. Use `Region` instead. The `M0199` diagnostic is retired + (#6357, #6378, #6403). + + * **Warnings that are now errors** (`-W ` downgrades a code again) + + * Breaking change: code that traps or silently does not do what it says + is an error. Non-exhaustive patterns in `switch`, `let`, `catch`, `for` + and function parameters (M0145), `ignore` of an `async*` value (M0222), + misplaced parentheticals (M0210), unknown parenthetical attributes such + as `(with cycle = ...)` (M0212), record fields the expected type drops, + such as the typo in `{ user with emial = e }` (M0215), functions named + like system methods but not declared `system` (M0128), implicit oneway + `public func`s (M0242), and import paths whose letter case differs + from the file name (M0005). Fix with `let ... else`, a `case _` branch, + or an explicit `: ()` / `: async ()` annotation (#6410). + + * Breaking change: an inferred type that collapses to `Any` or `None` is + an error. Array literals (M0074), `if` branches (M0081) and `switch` + branches (M0101) whose only common type is `Any`, and intersections + that are `None` (M0166) or unions that are `Any` (M0167) although + neither operand is. Joins into a smaller but useful type, such as the + common fields of two records, are unaffected. Annotate the type, + `let xs : [Any] = ...`, when `Any` is intended (#6412). + + * Breaking change: a comparison whose result is constant is error M0276. + It covers comparisons at a type with a single value, such as `Any`, + `{}` or `()`: `n == t` with `n : Nat` and `t : Text`, two records that + share no field, or `x == y` on type parameters. Comparisons at a common + type that still has content stay warning M0062 (#6415). + + * Breaking change: a file in the `--enhanced-migration` directory that + does not export a public `migration` function is error M0278 instead of + warning M0251. The file was skipped, so the build succeeded and the + migration never ran (#6429). + + * Breaking change: an imported library must be a `module { ... }` or a + named actor class. A bare sequence of declarations is error M0142 + instead of a deprecation warning; wrap it in `module { ... }` and mark + the exports `public`. `-A`, `-W` and `-E` no longer accept `M0142` + (#6407). + + * Breaking change: an actor class must return `async`. + `actor class C() : actor {} { ... }` is error M0193 instead of warning + M0135; write `: async actor {}`. `-A`, `-W` and `-E` no longer accept + `M0135` (#6407). + + * **Syntax** + + * feat: `if`, `while`, `switch` and `for` heads need no parentheses. + `if f(x) { e1 } else { e2 }`, `while n > 0 { n -= 1 }`, + `switch p.x { ... }`, `for x in xs.vals() { }`; the branches or body + are then blocks. The old forms keep working. A record literal as head + needs parentheses, `switch ({ x = 0 }) { ... }` (M0272), and `break l e` + takes any expression (#6348, #6388). + + * Breaking change: a bare branch glued to the condition no longer parses. + After `if c` or `if (c)`, a glued `(`, `[` or prefix operator continues + the condition and a space starts the branch: `if xs[i] { }` indexes, + `if (c) [i] else []` branches, `if (c)-1 else 1` is rejected, + `if (c) -1 else 1` branches, and an operator spaced on both sides is + just an operator, `if n - 1 > 0 { }`. Add the space. A condition that is + more than a name or a parenthesized expression requires braced branches + (M0275): `if f(x) e1 else e2` is rejected. Outside conditions nothing + changes; the style guide has the two rules to follow (#6348, #6388). + + * Breaking change: `??` is whitespace-sensitive, like `<` and `>`. + `a ?? b` is null-coalescing and `??x` is two option introductions, + `?(?x)`. `a ??b` no longer parses, and the compatibility hack that read + the operator token `?? e` as `?(?e)` in expressions, types and patterns + is removed. The right-hand side is now an expression: + `opt ?? { x = 0 }` is a record literal, and a block must be written + `opt ?? do { ... }` (#6358). + + * feat: `switch` cases are lighter. The `;` between cases is optional, and + patterns with a deterministic extent need no parentheses: `case null`, + `case -1`, `case ?v`, `case #tag`, `case #tag(p)`, combined with `or`, + `and` and `: T` as in `case #less or #equal { ... }` or + `case x : Nat { ... }`. `catch` accepts the same patterns, + `catch e : Error { ... }`. A variant payload always takes its own + parentheses, `case #tag(p)`, never `case #tag p` (#6358, #6394). + + * feat: `do { ... }` and `do ? { ... }` work as operator operands: + `1 + do { 2 }`, `-do { ... }`, `debug_show do { ... }`. A postfix form + needs parentheses around the block, `(do { ... }).field` (#6395). + + * feat: targeted parse errors with fix-its replace the generic M0001. + M0272 flags a record literal in block position, such as a `case` arm, + a function body or a `switch`/`if`/`while` head, and suggests nesting + it as the block's result; M0273 flags a block in record-literal + position and suggests `do { ... }`; M0274 flags a reserved keyword such + as `query` or `implicit` used as an identifier (#6358, #6348). + + * **Type checking** + + * feat: a function passed where an `async` function type is expected is + asynchronous without annotations. For `run : (Nat -> async Nat) -> + async Nat`, `run(func(n) { n + 1 })` takes both `n : Nat` and the + `async Nat` return type from `run`, as non-async functions already + did. This covers `async*` and generic calls such as + `forEach(xs, func(x) { await process(x) })` (#6428). + + * Breaking change: `func f() : T = e` and `func f() : T { e }` mean the + same. A body is implicitly `async` exactly when the return type is + `async`, and a one-way `shared` function's body is implicitly + `ignore async`, so `= async { ... }` and `= ignore (async ...)` are + redundant and rejected with M0277, whose fix-it drops the `async`. A + parenthetical on the body, `= (with cycles = n) async { ... }`, goes at + the call site, `(with cycles = n) f()`, and a local function can no + longer return a future of its enclosing function (`= fut`). M0078, + M0079 and M0213 are retired (#6428). + + * Breaking change: `{ base with ... }` copies the base's `var` fields. + The result is the equivalent field-for-field record literal, so + mutating the copy does not mutate the base. This was error M0179, or + aliasing under the now-removed `--experimental-field-aliasing` flag; + code that relied on aliasing must keep the shared state in one object + referenced from both records (#6346). + + * feat: implicit arguments are also found in nested modules. When `M` is + in scope, candidates such as `M.N.compare` count, up to a nesting depth + of 8 (#6084). + + * feat: contextual dot also finds functions in nested modules, so + importing a facade that re-exports its package's modules is enough for + both `e.f(...)` and implicits. Both resolve in the same tiers: a local + value, then the direct fields of the modules in scope, then their nested + modules. A nested candidate only counts when no direct field matches, so + a module reached through a facade never competes with a module in scope + and existing calls keep their meaning. M0236 also suggests `e.f(...)` + for `M.N.f(e, ...)` (#6419). + + * feat: read-only primitives no longer require the `system` capability, + so `query` and `composite query` methods can call them: + `Prim.getSelfPrincipal`, `Prim.envVarNames`, `Prim.envVar`, + `Prim.callerInfoSigner`, `Prim.callerInfoData`, `Prim.getCandidLimits` + and `Prim.getCandidTypeLimits`. An explicit `` on a function + that does not need it is warning M0196, with a fix-it deleting it, + instead of an error (#6414). + + * feat: the dot-notation suggestion (M0236) is on by default. `moc` warns + about calls like `Map.filter(map, ...)` that could be + `map.filter(...)`; silence it with `-A M0236`. M0223 (redundant type + instantiation) and M0237 (redundant explicit arguments) stay off by + default (#6361). + + * bugfix: contextual dot resolves functions returning `async` in an async + context, e.g. `await x.asyncFunc()` (#6085). + + * Breaking change: a pattern is checked against the type of the value it + matches, not a larger type. A variant tag the type lacks is error M0116, + with a suggestion for a misspelled tag (`case (#suspnded)` on + `{#active; #suspended}`); a signed literal against `Nat` is error M0050 + (`case (-1)`), as `let n : Nat = -1` already is; an option pattern + against `Null` is error M0115. Before, only the coverage check flagged + these, and nothing did in `let ... else`. To match at a larger type, + annotate the matched value: `switch (n : Int)`. A pattern that fits the + type but is unreachable, such as a duplicate case, stays warning M0146 + (#6429). + + * bugfix: in `let ... else`, an alternative that is never matched, such as + the second `1` in `let (1 or 1) = n else { ... }`, is warning M0146, as + in a `switch`. In any pattern, an unreached pattern nested inside + another one is no longer reported a second time (#6429). + + * **Command line and tooling** + + * Breaking change: `moc --check a.mo b.mo` checks each file on its own. + Each file sees only its own imports, every imported library is checked + once, duplicate diagnostics are reported once, and an error in one file + does not stop the others. `moc` no longer concatenates several files + into one program: `-c`, `--idl` and `-r` take exactly one main file, + and the REPL (`-i`) preloads at most one. Use imports to split a + program across files (#6397). + + * Breaking change: dead flags and primitives are removed. + `-no-system-api` (use `-wasi-system-api` to run outside the Internet + Computer, e.g. in `wasmtime`), `-ref-system-api` (the default needs no + flag), `--trap-on-call-error` (a failed call always throws an + `Error`), `--generate-view-queries` and the `__` queries it + generated, `--(no-)experimental-multi-value`, + `--print-source-on-error`, `-no-link`, `--profile`, `--profile-file`, + `--profile-line-prefix` and `--profile-field`. `Prim.createActor` is + removed; use actor classes, or the management canister's + `create_canister` and `install_code` (#6357, #6403, #6405, #6411, #6417). + + * chore: `-g` emits only the DWARF line table, `.debug_line` and + `.debug_line_str`. The `.debug_abbrev`, `.debug_addr` and + `.debug_rnglists` sections only served a `.debug_info` section that + `moc` never emitted (#6406). + + * Breaking change: no more Intel-Mac release binaries. The + `motoko-Darwin-x86_64` tarball and its CI legs are dropped; on an Intel + Mac, build from source. x86_64 Linux, aarch64 Linux and Apple Silicon + binaries continue. The `motoko-base-library.tar.gz` artifact is also + dropped; `motoko-core.tar.gz` is unaffected (#6355). + + * feat: M0217 (redundant `persistent`) and M0269 (deprecated `.vals()`) + ship machine-applicable edits, so `mops check --fix` removes the + `persistent` and rewrites `.vals()` to `.values()` (#6439). + + * **Bug fixes** + + * bugfix: a type pattern field such as `let { type T } = m` in the body of + `let o = object { ... }` or `module M { ... }` no longer crashes the + compiler with an internal assertion. A well-typed binding is accepted, + and a scrutinee that is not an object reports M0113 as it does + elsewhere (#6431). + + * bugfix: an upgrade that drops a stable variable without a migration + function is rejected even when it also adds a stable variable. + Depending on how the names hashed, as when renaming `balances` to + `users`, the runtime check accepted it and the dropped variable's + value was lost (#6432). + + * bugfix: numeric field ids in imported Candid (`.did`) files no longer + crash `moc`. Hex ids such as `0x7f` parse, and an id that does not fit + in 32 bits is a syntax error (#6421). + + * bugfix: clearer diagnostics. Calling a local `async` or `async*` + function from a `query` (M0188) or `composite query` (M0187) says so + instead of claiming a `shared` function was called, and the M0185 + warning on importing a Candid service constructor explains that its + initialization arguments are ignored; `moc --explain M0185` has the + details (#6425). + + * bugfix: the interpreter (`moc -r`) traps where compiled code does. An + out-of-bounds `Blob` index or an index of 2^62 or more no longer + crashes with an internal error, `Array_init` and `Array_tabulate` trap + on a length of 2^48 or more, and `Nat`/`Int` `**` traps on an exponent + of 2^32 or more (#6422). + + * bugfix: checking time with `--error-recovery`, which `moc.js` always + enables, no longer grows cubically with the number of syntax errors. A + file with 400 syntax errors takes 0.05 s instead of 7 s (#6424). + + * bugfix: a `switch` on a two-legged variant that repeats a tag, such as + `case (#admin t)` followed by `case (#admin u)`, no longer compiles the + second case without its tag test. Passing the other leg bound that + leg's payload at the first case's type instead of trapping (#6427). + + * bugfix: a class method returning `async*` with an `= e` body, such as + `public func next() : async* Nat = v`, no longer fails with M0137 when + the class is used at a record type: the method binds its own async + scope, like a block body always did (#6428). + + * bugfix: generated Candid never reuses a user-written type name. A + suffixed name `moc` picks for a generic instantiation or a same-named + type, e.g. `Foo_1` for `Foo`, no longer crashes with `Not_found` + or silently emits a wrong type when a user type has that name (#6416). + + * bugfix: `--error-format=json` byte offsets are correct in files with + CRLF line endings. `byte_start`/`byte_end` were one byte early on every + line after a CRLF, and a form feed, NEL, U+2028 or U+2029 in a comment + or text literal shifted every later offset, so tools applying edits by + offset, such as `mops check --fix`, garbled the file. The M0236 + suggestion is also no longer dropped after a line ending in a lone CR + (#6393). + + * bugfix: the M0236 and M0237 fix-its that remove a call's last argument + remove its trailing comma too, so `mops check --fix` no longer turns a + multi-line call into an M0001 syntax error. Removing a juxtaposed sole + argument (`f x`) leaves `f ()` (#6392). + +* motoko-js (`moc.js`) + + * Breaking change: `Motoko.run` no longer preloads files. Its first argument + must be `[]`; use imports instead (#6397). + + * Breaking change: `gcFlags` accepts only `"force"` and `"scheduling"`. + `"incremental"`, `"enhancedOP"`, `"copying"`, `"marking"`, + `"generational"` and `"classicOP"` raise `Invalid_argument` (#6362, #6380). + ## 1.16.1 (2026-09-16) * motoko (`moc`) diff --git a/docs/languages/motoko/reference/compiler-ref.md b/docs/languages/motoko/reference/compiler-ref.md index 694fcc28..764225b3 100644 --- a/docs/languages/motoko/reference/compiler-ref.md +++ b/docs/languages/motoko/reference/compiler-ref.md @@ -16,9 +16,12 @@ Use the Motoko compiler (`moc`) to compile Motoko programs into executable WebAs ### Basic usage ``` bash -moc [option] [file ...] +moc [option] [file] +moc [option] --check [file ...] ``` +Imports are the way to combine several files into one program, so `moc` takes a single main file. Only `--check` takes several files, checking each one on its own. + ### Options You can use the following options with the `moc` command. @@ -31,18 +34,13 @@ You can use the following options with the `moc` command. | `--args ` | Read additional newline separated command line arguments from ``. | | `--args0 ` | Read additional `NUL` separated command line arguments from ``. | | `-c` | Compile to WebAssembly. | -| `--check` | Performs type checking only. | -| `--compacting-gc` | Use compacting GC (only available with legacy/classical persistence). | -| `--copying-gc` | Use copying GC (only available with legacy/classical persistence). | +| `--check` | Performs type checking only. Each given file is checked on its own; libraries they import are checked once. | | `--debug` | Respects debug expressions in the source (the default). | -| `--enhanced-orthogonal-persistence` | Use enhanced orthogonal persistence (default): Scalable and fast upgrades using a persistent 64-bit main memory. | -| `--enhanced-migration ` | Enable enhanced migration system: requires initializers for all stable variables, disallows side-effects in actor bodies; only available with enhanced orthogonal persistence. The `motoko:stable-types` custom section is omitted from the wasm (the runtime system enforces stable-type compatibility at upgrade time); the `.most` file is still emitted under `--stable-types`. | +| `--enhanced-orthogonal-persistence` | Enhanced orthogonal persistence is always on: scalable and fast upgrades using a persistent 64-bit main memory. The flag is only needed to upgrade a canister from classical orthogonal persistence. | +| `--enhanced-migration ` | Enable enhanced migration system: stable variables are declared without initializers and receive their values from the migration chain, disallows side-effects in actor bodies; cannot be used for the upgrade from classical orthogonal persistence. The `motoko:stable-types` custom section is omitted from the wasm (the runtime system enforces stable-type compatibility at upgrade time); the `.most` file is still emitted under `--stable-types`. | | `--error-detail ` | Set level of error message detail for syntax errors, n in \[0..3\] (default 2). | -| `--experimental-stable-memory ` | Select support for the deprecated `ExperimentalStableMemory.mo` library (n < 0: error, n = 0: warn, n > 0: allow) (default 0). | | `-fno-shared-code` | Do not share low-level utility code: larger code size but decreased cycle consumption (default). | -| `--generational-gc` | Use generational GC (only available with legacy/classical persistence). | | `-fshared-code` | Do share low-level utility code: smaller code size but increased cycle consumption. | -| `--generate-view-queries` | Auto-generate queries for stable variables; preferring applicable .view() methods (default false) | | `-help`,`--help` | Displays usage information. | | `--hide-warnings` | Hides compiler warnings. | | `-Werror` | Treat warnings as errors. | @@ -50,14 +48,11 @@ You can use the following options with the `moc` command. | `-W ` | Enable (Warn) comma-separated warning codes, e.g. `-W M0223` | | `-E ` | Treat as error comma-separated warning codes, e.g. `-E M0217` | | `--warn-help` | Show available warning codes, current lint level, and descriptions | -| `--incremental-gc` | Use incremental GC (default, works with both enhanced orthogonal persistence and legacy/classical persistence). | | `--idl` | Compile binary and emit Candid IDL specification to `.did` file. | | `-i` | Runs the compiler in an interactive read–eval–print loop (REPL) shell so you can evaluate program execution (implies -r). | | `--implicit-derivation-depth ` | Maximum recursion depth for [implicit](../fundamentals/implicit-parameters.md) argument derivation (default 100). Raise if a complex derivation is rejected as depth-limited. | -| `--legacy-persistence` | Use legacy (classical) persistence. This also enables the usage of --copying-gc, --compacting-gc, and --generational-gc. Deprecated in favor of the new enhanced orthogonal persistence, which is default. Legacy persistence will be removed in the future.| | `--map` | Outputs a JavaScript source map. | | `--max-stable-pages ` | Set maximum number of pages available to stable memory via the `Region` library (default 1638400, i.e. 100 GiB). | -| `-no-system-api` | Disables system API imports. | | `-no-timer` | Disables timer API imports and hides timer primitives. | | `-o ` | Specifies the output file. | | `-p ` | Sets the print depth. | @@ -65,16 +60,12 @@ You can use the following options with the `moc` command. | `--public-metadata ` | Emit ICP custom section `` (`candid:args` or `candid:service` or `motoko:stable-types` or `motoko:compiler`) as `public` (default is `private`).| | `--omit-metadata ` | Omit ICP custom section `` (`candid:args` or `candid:service` or `motoko:stable-types` or `motoko:compiler`). | | `--print-deps` | Prints the dependencies for a given source file. | -| `-r` | Interprets programs. | +| `-r` | Interprets a program. | | `--release` | Ignores debug expressions in the source. | -| `--stable-regions` | Force eager initialization of stable regions metadata (for testing purposes); consumes between 386KiB or 8MiB of additional physical stable memory, depending on current use of ExperimentalStableMemory. | +| `--stable-regions` | Force eager initialization of stable regions metadata (for testing purposes); consumes between 386KiB or 8MiB of additional physical stable memory. | | `--stable-types` | Compile binary and emit signature of stable types to `.most` file. | | `--stable-compatible
 `        | Test upgrade compatibility between stable-type signatures `
` and ``.                                                                       |
-| `--rts-stack-pages `                   | Set maximum number of pages available for runtime system stack (only supported with classical persistence, default 32).                               |
-| `--trap-on-call-error`                    | Trap, don't throw an [`Error`](https://mops.one/core/docs/Error), when an IC call fails due to destination queue full or freezing threshold is crossed. Emulates behavior of moc versions < 0.8.0.                                                                                                                                           |
 | `-t`                                      | Activates tracing in interpreter.                                                                                                                     |
 | `-v`                                      | Generates verbose output.                                                                                                                             |
 | `--version`                               | Displays version information.                                                                                                                         |
 | `-wasi-system-api`                        | Uses the WASI system API (`wasmtime`).                                                                                                                |
-
-
diff --git a/docs/languages/motoko/reference/error-codes.md b/docs/languages/motoko/reference/error-codes.md
index 3dcfd960..6b3a9f80 100644
--- a/docs/languages/motoko/reference/error-codes.md
+++ b/docs/languages/motoko/reference/error-codes.md
@@ -39,7 +39,7 @@ sidebar:
 | M0097 | Expected function type. | ```let x = 5; x();``` | Cannot call a non-function value. |
 | M0098 | Cannot instantiate function type. | ```type Func = (Nat) -> Nat; let f = Func();``` | Function types cannot be instantiated directly. |
 | M0112 | Tuple pattern cannot consume type. | ```let (x, y) = { a = 1; b = 2 };``` | Cannot destructure an object using tuple pattern. |
-| M0116 | Variant pattern cannot consume type. | ```switch (value) { case (#ok(x)) { ... } };``` (where `value` is not a variant) | Cannot match a non-variant value with variant patterns. |
+| M0116 | Variant pattern cannot consume type. | ```switch (value) { case (#okk(x)) { ... } };``` (where `value` is not a variant, or its type has no `#okk` tag) | A variant pattern's tag must be a tag of the matched type; check for a misspelled tag. |
 | M0126 | Shared function cannot be private. | ```actor { private shared func publicAPI() { ... } }``` | Shared functions must be public in actors. |
 | M0137 | A type or class was declared that explicitly or implicitly references an outer type parameter. | ```class Container() { class Inner() { var value : T = ... } }``` | Inner classes cannot reference type parameters from outer classes. |
 | M0139 | Inner actor classes are not supported. | ```class Outer() { actor class Inner() { ... } }``` | Actor classes cannot be nested inside other classes. |
diff --git a/docs/languages/motoko/reference/language-manual.md b/docs/languages/motoko/reference/language-manual.md
index fc2e426d..6633d51d 100644
--- a/docs/languages/motoko/reference/language-manual.md
+++ b/docs/languages/motoko/reference/language-manual.md
@@ -91,8 +91,8 @@ The following keywords are reserved and may not be used as identifiers:
 
 ``` bnf
 actor and assert async async* await await? await* break case
-catch class composite continue debug debug_show do else false flexible
-finally for from_candid func if ignore import implicit in include label let
+catch class composite continue debug debug_show do else false finally
+for from_candid func if ignore import implicit in include label let
 loop mixin module not null object or persistent private public query
 return shared stable switch system throw to_candid true transient try
 type var weak while with
@@ -455,8 +455,7 @@ For ``, an absent `?` is shorthand for the wildcard pattern `_`
 
  ::=                                     field stability (actor only)
   stable
-  flexible
-  transient                                      (equivalent to flexible)
+  transient
 ```
 
 The **visibility** qualifier `?` determines the accessibility of every field `` declared by ``:
@@ -473,10 +472,8 @@ The **visibility** qualifier `?` determines the accessibility of every fiel
 
 The **stability** qualifier `` determines the **upgrade** behavior of actor fields:
 
--   A stability qualifier should appear on `let` and `var` declarations that are actor fields.
-    Within a `persistent` actor or actor class, an absent stability qualifier defaults to `stable`.
-    Within a non-`persistent` actor or actor class, an absent stability qualifier defaults to `flexible` (or `transient`).
-    The keywords `transient` and `flexible` are interchangeable.
+-   A stability qualifier may appear on `let` and `var` declarations that are actor fields.
+    Actors and actor classes are `persistent` by default, so an absent stability qualifier means the field is persisted across upgrades (`stable`).
 
 -   `` qualifiers must not appear on fields of objects or modules.
 
@@ -807,7 +804,7 @@ The type `Region` represents opaque stable memory regions. Region objects are dy
 
 The region type is stable but not shared and its objects, which are stateful, may be stored in stable variables and data structures.
 
-Objects of type `Region` are created and updated using the functions provided by base libary `Region`. See [stable regions](./icp-features/stable-memory) and library [Region](https://mops.one/core/docs/Region) for more information.
+Objects of type `Region` are created and updated using the functions provided by core library `Region`. See [stable regions](./icp-features/stable-memory) and library [Region](https://mops.one/core/docs/Region) for more information.
 
 ### Constructed types
 
@@ -1018,7 +1015,7 @@ For a function, the number of type arguments, when provided, must agree with the
 
 Given a vector of type arguments instantiating a vector of type parameters, each type argument must satisfy the instantiated bounds of the corresponding type parameter.
 
-In function calls, supplying the `system` pseudo type argument grants system capability to the function that requires it.
+In function calls, supplying the `system` pseudo type argument grants system capability to the function that requires it. Supplying it to a function that does not require it is redundant and produces a warning.
 
 System capability is available only in the following syntactic contexts:
 
@@ -1193,7 +1190,7 @@ A type `T` is **stable** if it is:
 
 This definition implies that every shared type is a stable type. The converse does not hold: there are types that are stable but not share, notably types with mutable components.
 
-The types of actor fields declared with the `stable` qualifier must have stable type.
+The types of actor fields not declared `transient` must have stable type.
 
 The current value of such a field is preserved upon upgrade, whereas the values of other fields are reinitialized after an upgrade.
 
@@ -1379,8 +1376,7 @@ Any identifier bound by a `public` declaration appears in the type of enclosing
 
 An identifier bound by a `private` or `system` declaration is excluded from the type of the enclosing object, module or actor and thus inaccessible.
 
-In a `persistent` actor or actor class, all declarations are implicitly `stable` unless explicitly declared otherwise.
-In a non-`persistent` actor or actor class, all declarations are implicitly `transient` (equivalently `flexible`) unless explicitly declared otherwise.
+In an actor or actor class (`persistent` by default, so the `persistent` keyword is redundant), all declarations are persisted across upgrades (`stable`) unless explicitly declared `transient`.
 
 The declaration field has type `T` provided:
 
@@ -1388,9 +1384,9 @@ The declaration field has type `T` provided:
 
 -   If `?` is `stable`  then `T` must be a stable type (see [stability](#stability)).
 
--   If `?` is absent and the actor or actor class is `persistent`, then `T` must be a stable type (see [stability](#stability)).
+-   If `?` is absent, then `T` must be a stable type (see [stability](#stability)).
 
-Actor fields declared `transient` (or legacy `flexible`) can have any type, but will not be preserved across upgrades.
+Actor fields declared `transient` can have any type, but will not be preserved across upgrades.
 
 
 In the absence of any `?` migration expression, sequences of declaration fields are evaluated in order by evaluating their constituent declarations, with the following exception:
@@ -1413,7 +1409,7 @@ These effects can only be achieved using an explicit [migration expression](#mig
 
 #### Migration expressions
 
-Actors and actor class declaration may specify a migration expression, using an optional, leading `` expression with a required field named `migration`.
+Actors and actor class declarations may specify a migration expression, using an optional, leading `` expression with a required field named `migration`.
 The value of this field, a function, is applied to the stable variables of an upgraded actor, before initializing any stable fields of the declared actor.
 
 The parenthetical expression must satisfy the following conditions:
@@ -1443,7 +1439,7 @@ the value of a `stable` declaration is obtained as follows:
 - Otherwise, if the stable declaration is not present in the domain and is declared stable in the retired actor,
   then its initial value is obtained from the retired actor.
 
-- Otherwise, its value is obtained by evaluating the declaration's initalizer.
+- Otherwise, its value is obtained by evaluating the declaration's initializer.
 
 Thus a stable variable's initializer is run if the variable is not produced by the migration function and either
 consumed by the migration function (by appearing in its domain) or absent in the retired actor.
@@ -1456,8 +1452,8 @@ For the upgrade to be safe:
 - Every stable identifier declared with type `T` in the retired actor, not present in the domain or codomain,
   and declared stable and of type `U` in the replacement actor, must satisfy `T < U` (stable subtyping).
 
-Thses conditions ensure that every stable variable is either discarded or fresh, requiring initialization,
-or that its value can be safely consumed from the output of migration or the retired actor without loss of date.
+These conditions ensure that every stable variable is either discarded or fresh, requiring initialization,
+or that its value can be safely consumed from the output of migration or the retired actor without loss of data.
 
 The compiler will issue a warning if a migration function appears to be discarding data by consuming a field and not producing it.
 The warnings should be carefully considered to verify any data loss is intentional and not accidental.
@@ -1487,7 +1483,7 @@ The declaration `` of a `system` field must be a manifest `func` declaratio
 
 :::danger
 
-Using the pre- and post-upgrade system methods is discouraged. It is error-prone and can render a canister unusable.
+The pre- and post-upgrade system methods are deprecated (warning M0270) in favor of [migration expressions](#migration-expressions). They are error-prone and can render a canister unusable.
 In particular, if a `preupgrade` method traps and cannot be prevented from trapping by other means, then your canister may be left in a state in which it can no longer be upgraded.
 Per best practices, using these methods should be avoided if possible.
 
@@ -1564,6 +1560,8 @@ The consequences of pattern match failure depends on the context of the pattern.
 
 -   In a `case` branch of a `switch` expression, failure to match that case’s pattern continues with an attempt to match the next case of the switch, trapping only when no such case remains.
 
+A pattern is checked against the type of the value it matches, not a larger type: a variant pattern needs a tag of that variant type, a literal pattern a literal of that type, and an option pattern an option type. Only `None`, which has no values, accepts any pattern. To match at a larger type, annotate the matched value, as in `switch (n : Int)`.
+
 ### Wildcard pattern
 
 The wildcard pattern `_` matches a single value without binding its contents to an identifier.
@@ -1677,14 +1675,15 @@ All bindings declared by a `let-else` if any are immutable.
 
 In the presence of refutable patterns, the pattern in a `let` declaration may fail to match the value of its expression.
 In such cases, the `let`-declaration will evaluate to a trap.
-The compiler emits a warning for any `let`-declaration than can trap due to pattern match failure.
+The compiler reports an error (M0145) for any `let`-declaration that can trap due to pattern match failure.
+To accept the trap instead, pass `-W M0145` to downgrade it to a warning.
 
 Instead of trapping, a user may want to explicitly handle pattern match failures.
 The `let-else` declaration, `let  =  else `, has mostly identical static and dynamic semantics to `let`,
 but diverts the program's control flow to `` when pattern matching fails, allowing recovery from failure.
 The `else` expression, ``, must have type `None` and typically exits the declaration using imperative control flow
 constructs such as `throw`, `return`, `break` or non-returning functions such as `Debug.trap(...)` that all produce a result of type `None`.
-Any compilation warning that is produced for a `let` can be silenced by handling the potential pattern-match failure using `let-else`.
+Handling the potential pattern-match failure with `let-else` resolves the error.
 
 ### Var declaration
 
@@ -2133,11 +2132,11 @@ Thus the field list serves to:
 -   Define new fields.
 -   Override existing fields and their types.
 -   Add new `var` fields.
--   Redefine existing `var` fields from some base to prevent aliasing.
+-   Override existing `var` fields from some base, replacing the base's field with a fresh one.
 
 The resulting type is determined by the bases' and explicitly given fields' static type.
 
-Any `var` field from some base must be overwritten in the explicit field list. This prevents introducing aliases of `var` fields.
+A `var` field of some base that is not overwritten is copied into a fresh mutable field of the result, initialized with the base field's value at the time the record expression is evaluated. Mutating that field of the result does not affect the base, and mutating the base's field does not affect the result. Since an explicit field initializer may mutate a base, each such copy is made after all the explicit fields have been evaluated.
 
 The record expression `{  and ...  with ; ... ; }` has type `T` provided:
 
@@ -2153,8 +2152,6 @@ The record expression `{  and ...  with ; ... `).
 
-    -   No field in `field_tysi` is a `var` field.
-
     -  `fields(i)` is disjoint from `fields(j)` for `j < i`.
 
 -   Considering type fields:
@@ -2174,7 +2171,7 @@ Note that the case for type fields is simpler than the value fields case only be
 The record expression `{  and ...  with ; ... ; }` evaluates records `` through `` and `{ exp-field1; ... ) = fi()` if `` is in `fields(i)`, for some `i`, or `f()` if `` is in `newfields`.
+values or locations such that `g() = fi()` if `` is in `fields(i)` and is not a `var` field, for some `i`; `g()` is a fresh location holding the value of `fi()` if `` is in `fields(i)` and is a `var` field; and `g() = f()` if `` is in `newfields`. A location `fi()` copied this way yields a different location than `g()`, so the two can be updated independently. As the copy is taken after the evaluation of `{ exp-field1; ...  }`, a `var` base field mutated by a field initializer is copied at its updated value.
 
 ### Object projection (member access)
 
@@ -2285,31 +2282,56 @@ Otherwise,
 -   Otherwise, `v` is `vi`, the value currently stored in the `i`-th location of the array.
 
 
+### Resolution of dotted calls and implicit arguments
+
+A [dotted function call](#dotted-function-calls) `. ` whose receiver has no function field ``, and an omitted [implicit argument](#function-calls) named ``, are both resolved by searching the scope for values named ``. The search is the same for both; they differ only in how a candidate is matched against the call and in which of two matching candidates is the closer one.
+
+The values named `` are searched in tiers. The first tier that has a matching candidate decides the resolution, so a closer candidate hides farther ones, and a module reached through another module never competes with a module in scope:
+
+1. The value `` bound in the local scope, provided it is immutable.
+2. The immutable fields `.` of the modules `` in scope.
+3. The immutable fields `..…..` of the modules nested inside the modules in scope, reached through module-typed fields only, up to a depth of 8. In the body of `module M`, the modules nested in `M` are in scope by themselves, so `M` contributes only its direct fields.
+4. When the compiler runs with `--implicit-package `, the libraries of package `` that the program does not import, searched like tiers 2 and 3 with the library in place of ``.
+
+Within a tier:
+
+* Every matching candidate counts, even when several of them are the same function reached through several paths, such as `M.f` from an imported library `M` and `Facade.M.f` through a facade that re-exports it. Importing the library directly makes it a direct field and lets it win.
+
+* The set of matching candidates Cs is filtered to the set Ds of the candidates closest to the call. A candidate is dropped when another candidate is strictly closer; candidates that are equally close or unrelated are all kept. In both cases a candidate of exactly the required type, when there is one, wins.
+
+  For a dotted call, a candidate matches when the receiver type is a subtype of its `self` parameter type, and the closest candidate is the one with the smallest `self` type. For a receiver `n : Nat` and candidates `f(self : Nat)` and `f(self : Int)`, both match, since `Nat <: Int`, and `f(self : Nat)` wins.
+
+  For an implicit argument, a candidate matches when its type is a subtype of the required type, and the closest candidate is the one with the greatest type. For a required `n : Int` and candidates `n : Int` and `n : Nat`, both match, since `Nat <: Int`, and `n : Int` wins. Function types compare through their parameters, so for a required `toText : Int -> Text` and candidates `toText : Int -> Text` and `toText : Any -> Text`, both match, since `Any -> Text <: Int -> Text`, and `toText : Int -> Text` wins.
+
+* If Ds is a singleton, its element is the resolution. Otherwise the call is ambiguous and rejected, with M0224 for a dotted call and M0231 for an implicit argument; the error names the candidates by their module paths. Writing the qualified function, or passing the implicit argument explicitly, resolves the ambiguity.
+
+If no tier has a matching candidate, the call is rejected, with M0072 for a dotted call and M0230 for an implicit argument. The error suggests importing a library when one of the libraries the program loads, imported or not and from any package, has a matching field named `` directly or in a nested module. For implicit arguments, [implicit derivation](#function-calls) is attempted before the call is rejected, walking the same tiers.
+
 ### Dotted function calls
 
 The dotted function call expression `? . ? ` has type `T` provided
 the expanded function call expression `?  ? ` has type `T`,
 where
 
-  * If the projection `.` has some function type then ` = .` and ` = `.
+  * If the receiver `` has an object type with a function-typed field `` then ` = .` and ` = `.
 
-  * Otherwise, the module environment is used to determine an appropriate function `` and argument `` by
+  * Otherwise, the scope is searched for a function named `` as described in [Resolution of dotted calls and implicit arguments](#resolution-of-dotted-calls-and-implicit-arguments),
     constructing a set of candidates Cs and disambiguation Ds:
 
       * If the receiver `` has type `R` and
-      * Cs = { `(, V1[Ts/Xs], a)` | `` has type `module {}` and `.` has type `(self : V1, ..., Va) -> V` and `R <: V1[Ts/Xs]` for `Ts` } and
-      * Ds = { `(, V, a)`  in Cs | for all `(_, W, _)` in Cs, `V <: W` } and
-      * { `(, _, _)` } = Ds and
+      * Cs = { `(, V1[Ts/Xs], a)` | `` is the local value `` or a field `.` found by the search, `` has type `(self : V1, ..., Va) -> V` and `R <: V1[Ts/Xs]` for some `Ts` } and
+      * Ds = { `(, V, a)`  in Cs | for all `(_, W, _)` in Cs, `V <: W` } and
+      * { `(, _, _)` } = Ds and
 
-    Then ` = .` and ` = extend_args(, , a)`.
+    Then ` = ` and ` = extend_args(, , a)`.
 
     Here:
 
     * `R` is the type of the receiver expression ``.
-    *  Cs is the set of candidate functions `.` in modules named ``, with explicitly name `self` parameter that matches the receiver type `R`.
-    *  Ds is the disambiguated set of candidates, filtered by specifity.
-    * `` is the name of the unique disambiguation, if one exists (that is, when Ds is a singleton set).
-    *  Finally `extend_args` is the following auxilliary function that inserts the receiver into arguments ``, using the candidate's arity `a`:
+    *  Cs is the set of candidate functions with an explicitly named `self` parameter that matches the receiver type `R`, from the first tier of the search that has one: the local value ``, then the modules in scope, then the modules nested in them, then the libraries of the implicit package. `` is a module name or a path `..….` to a nested module.
+    *  Ds is the disambiguated set of candidates, filtered by specificity of the `self` parameter.
+    * `` is the unique disambiguation, if one exists (that is, when Ds is a singleton set).
+    *  Finally `extend_args` is the following auxiliary function that inserts the receiver into arguments ``, using the candidate's arity `a`:
 
     ```
     extend_args( : exp,  : exp, arity : Nat) : exp
@@ -2374,20 +2396,20 @@ the expanded function call expression `?  ? <
 
       Otherwise:
 
-      * Cs = { `(, V)` | `` has type `module {}` and `.` has type `V` and `V <: [T0/X0, …​, Tn/Xn]U1` }; and
-      * Ds = { `(, V)`  in Cs | for all `(_, W)` in Cs, `W <: V` }; and
-      * { `(, _)` } = Ds; and
-      * `hole(i, , Ui) = .`.
+      * Cs = { `(, V)` | `` is a field `.` found by the scope search and has type `V` with `V <: [T0/X0, …​, Tn/Xn]Ui` }; and
+      * Ds = { `(, V)`  in Cs | for all `(_, W)` in Cs, `W <: V` }; and
+      * { `(, _)` } = Ds; and
+      * `hole(i, , Ui) = `.
 
     Here:
 
     * `hole(i, , Ui)` is the description of the `ith` hole, a placeholder for an expression `` or `.`.
-    *  `` is the resolution of the hole from the local context, if any;
-    *  Cs is the set of candidate module `` named ``, with type `V` whose field `.` matches hole type `Ui` (after type instantiation).
+    *  `` is the resolution of the hole from the local context, if any; it must be immutable and have a type `V <: [T0/X0, …​, Tn/Xn]Ui`.
+    *  Cs is the set of candidate fields `.` from the first tier of the search that has one: the modules in scope, then the modules nested in them, then the libraries of the implicit package, as described in [Resolution of dotted calls and implicit arguments](#resolution-of-dotted-calls-and-implicit-arguments). `` is a module name or a path `..….` to a nested module.
     *  Ds is the disambiguated set of candidates, filtered by generality.
-    * `.` is the name of the unique disambiguation, if one exists (that is, when Ds is a singleton set).
+    * `` is the unique disambiguation, if one exists (that is, when Ds is a singleton set).
 
-    **Implicit derivation**: When no direct candidate is found (neither from local values, module fields, nor library fields of unimported modules when `--implicit-package` is set), the compiler additionally searches for *derivable* candidates, starting with local values, then among module fields, then among library fields.
+    **Implicit derivation**: When no direct candidate is found (neither from the local value, nor the fields of the modules in scope and their nested modules, nor the library fields of unimported modules when `--implicit-package` is set), the compiler additionally searches for *derivable* candidates, in the same order: the local value, then module fields, then library fields.
     A derivable candidate is a function (possibly polymorphic) that has implicit parameters of its own, and whose type, after removing its implicit parameters and instantiating its type parameters, matches the required hole type.
     If the derivable candidate's own implicit parameters can be recursively resolved (up to a configurable depth limit), the compiler synthesizes a wrapper function that calls the candidate with the resolved inner implicits.
     This allows, for example, an implicit `compare : ([Nat], [Nat]) -> Order` to be derived from `Array.compare` when `Nat.compare` is in scope. The derivation depth is bounded by the `--implicit-derivation-depth` flag.
@@ -2440,6 +2462,18 @@ The function expression `? func < X0 <: T0, …​, Xn <: Tn > ` has type return type `U2` under the assumption that `` has type `U1`.
 
+The body of a function returning `async U` (or `async* U`) is implicitly asynchronous: it is treated as `async ` (or `async* `), so it has type `U`, not `async U`.
+Likewise, the body of a one-way `shared` function, returning `()`, is treated as `ignore (async )`.
+Writing the body as a block or as `= ` makes no difference; a body that is already an explicit `async` expression is rejected as redundant.
+
+A local function without type parameters that is checked against an expected function type, for example as an argument of a call, takes the types of its unannotated parameters and its return type from the expected type.
+If that return type is `async U` (or `async* U`), the function is asynchronous as if its return type were written:
+
+```motoko no-repl
+func run(f : Nat -> async Nat) : async Nat { await f(1) };
+func g() : async Nat { await run(func(n) { n + 1 }) };
+```
+
 `? func ?  (: )? =? ` evaluates to a function value denoted `? func ?  = `, that stores the code of the function together with the bindings from the current evaluation environment needed to evaluate calls to the function value.
 
 Note that a `` function may itself be `shared ` or `shared query ` or  `shared composite query `.
diff --git a/docs/languages/motoko/reference/style-guide.md b/docs/languages/motoko/reference/style-guide.md
index c45e1c4f..e3fbf582 100644
--- a/docs/languages/motoko/reference/style-guide.md
+++ b/docs/languages/motoko/reference/style-guide.md
@@ -460,7 +460,22 @@ Rationale: `g[1]` in particular will be misparsed as an indexing operation.
     };
     ```
 
-    Rationale: Omitting both at the same time makes the code harder to read, since there is less visual clue how it groups.
+    Rationale: Omitting both at the same time makes the code harder to read, since there is less visual clue how it groups. The compiler agrees: a condition that is more than a name or a parenthesized expression requires braced branches.
+
+-   Keep the condition of an `if`/`while` and the head of a `switch`/`for` tight: `f(x)`, `xs[i]`, `n - 1`, never `f (x)`, `xs [i]`, `n -1`.
+
+    After a name or a parenthesized condition, a space before `(`, `[`, `-`, `+`, `^`, `#` means "here the branch starts", so `if (c) -1 else 1` and `if (c) [x] else []` keep their meaning, and a stray space in a condition silently ends it.
+
+    ``` motoko no-repl
+    if xs[i] { f() };         // index
+    if (c) [i] else [];       // branch
+    if n - 1 > 0 { f() };     // subtraction
+    if (c) -1 else 1;         // branch
+
+    // COUNTER EXAMPLES!
+    if xs [i] { f() };        // DO NOT DO THIS: `xs` is the condition, `[i] { f() }` the branch
+    if n -1 > 0 { f() };      // OR THIS: `n` is the condition, `-1 > 0 { f() }` the branch
+    ```
 
 -   Similarly, do not omit parentheses around function parameters if the function also has type parameters.