Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
add doc on rust dialect
Browse files Browse the repository at this point in the history
  • Loading branch information
graydon authored and tomerweller committed Jul 19, 2022
1 parent 6bb04cf commit 5be90c5
Showing 1 changed file with 54 additions and 0 deletions.
54 changes: 54 additions & 0 deletions docs/learn/rust-dialect.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
sidebar_position: 8
title: Contract Rust dialect
---

Contract development occurs in the Rust programming language, but several features of the Rust language are either unavailable in the contract environment, or not recommended because their use would incur unacceptable costs at runtime.

For this reason it makes sense to consider code written for contracts to be a _dialect_ or special variant of the Rust programming language, with certain unusual constraints and priorities, such as determinism and code size.

These constraints and priorities are _similar_ to those encountered when writing Rust code for "embedded systems", and the tools, libraries and techniques used in the "contract dialect" are frequently borrowed from the [Rust embedded systems community](https://docs.rust-embedded.org/book/index.html), and by default contracts are recommended to be built with the [`#[no_std]` mode](https://docs.rust-embedded.org/book/intro/no-std.html) that excludes the Rust standard library entirely, relying on the smaller underlying `core` library instead.

The "contract dialect" has the following characteristics:

## No floating point
Floating-point arithmetic in the guest is completely prohibited. Floating-point operations in WASM have a few nondeterministic or platform-specific aspects: mainly NaN bit patterns, as well as floating-point environment settings such as rounding mode.

While it is theoretically possible to force all floating-point code into deterministic behaviour across WASM implementations, doing so on some WASM implementations may be difficult, costly, or error-prone. To avoid the complexity, all floating-point code is rejected at instantiation time.

This restriction may be revisited in a future version.

## Limited (ideally zero) dynamic memory allocation
Dynamic memory allocation within the guest is **strongly** discouraged, but not completely prohibited.

The host object and host function repertoire has been designed to relieve the guest from having to perform dynamic allocation within its own linear memory; instead, the guest is expected and intended to allocate dynamic structures _within host objects_ and interact with them using lightweight handles.

Using host objects instead of data structures in guest memory carries numerous benefits: much higher performance, much smaller code size, interoperability between contracts, shared host support for serialization, debugging and data structure introspection.

The guest does, however, has a small linear memory available to it in cases where dynamic memory allocation is necessary. Using this memory carries costs: the guest must include in its code a full copy of a memory allocator, and must pay the runtime cost of executing the allocator's code inside the VM.

This restriction is due to the limited ability of WASM to support code-sharing: there is no standard way for the WASM sandbox to provide shared "standard library" code within a guest, such as a memory allocator, nor does the host have adequate insight into the contents of the guest's memory to provide an allocator itself. Every contract that wishes to use dynamic allocation must therefore carry its own copy of an allocator.

Many instances where dynamic memory allocation might _seem_ to be required can also be addressed just as well with a library such as [heapless](https://docs.rs/heapless/latest/heapless/). This library (and others of its kind) provide data structures with familiar APIs that _appear_ dynamic, but are actually implemented in terms of a single stack or static allocation, with a fixed maximum size established at construction: attempts to grow the dynamic size beyond the maximum size simply fail. In the context of a contract, this can sometimes be perferable behaviour, and avoids the question of dynamic allocation entirely.

## Non-standard I/O
All standard I/O facilities and access to the operating system that a typical Rust program would expect to perform using the Rust standard library is prohibited; programs that try to import such functions from the host through (for example) the WASI interface will fail to instantiate, since they refer to functions not provided by the host.

No operating system, nor any simulation thereof, is present in the contract sandbox. Again, the repertoire of host objects and host functions is intended to replace and largely obviate the need for such facilities from the standard library.

This restriction arises from the fact that contracts need to run with _stronger_ guarantees than those made by typical operating-system APIs. Specifically contracts must perform I/O with all-or-nothing, transactional semantics (relative to their successful execution or failure) as well as serializable consistency. This eliminating most APIs that would relate to typical file I/O. Furthermore contracts must be isolated from all sources of nondeterminism such as networking or process control, which eliminating most of the remaining APIs. Once files, networking and process control are gone, there simply isn't enough left in the standard operating system I/O facililties to bother trying to provide them.

## No multithreading
Multithreading is not available. As with I/O functions, attempting to import any APIs from the host related to multithreading will fail at instantiation time.

This restriction is similarly based on the need for contracts to run in an environment with strong determinism and serializable consistency guarantees.

## Immediate panic
The Rust `panic!()` facility for unrecoverable errors will trap the WASM virtual machine immediately, halting execution at the instruction that traps rather than unwinding. This means that `Drop` code in Rust types will not run during a panic. This behaviour is similar to the `panic = "abort"` profile that Rust code can (and often is) compiled with.

This is not a hard restriction enforced by the host, but a soft configuration made through a mixture of SDK functions and flags used when compiling, in the interest of minimizing code size and limiting execution costs. It can be bypassed with some effort if unwinding and `Drop` code is desired, at the cost of greatly increased code size.

## Pure-functional collections
Host objects have significantly different semantics than typical Rust data structures, especially those implementing _collections_ such as maps and vectors.

In particular: host objects are **immutable**, and often **share substructure**. They therefore resemble the data structures from pure-functional programming more closely than the typical imperative model used in many Rust programs.

0 comments on commit 5be90c5

Please sign in to comment.