Skip to content

[Language design] Choose a stable surface for value generics and public API signatures #23

Description

@a19q3

Decision

Before CellScript 0.25 is stabilized, decide whether user-defined value generics are part of the dependency-facing language or remain a package-local implementation mechanism.

Default recommendation: choose Proposal B unless concrete 0.25 packages demonstrate that cross-package user-defined generic libraries are required and Proposal A's normalization rules can be specified completely.

This is a bounded language-design decision. It is not a proposal for generic Cells, generic entry points, dynamic dispatch, traits, or runtime type reflection.

At a glance

Proposal A: restricted public generics Proposal B: package-local generics
Cross-package user generics Supported Deferred
Package-local generics Supported Supported
Built-in Option<T> and fixed arrays Supported Supported
Public interface Templates plus applied types Concrete signatures only
Normal source spelling Compact profile such as fixed_value Concrete public wrappers
Compatibility commitment in 0.25 Larger Smaller
Ability to redesign later Limited after publication Preserved
flowchart TD
    NEED{"Does 0.25 have a concrete cross-package<br/>user-defined generic requirement?"}
    PROFILE{"Can a closed constraint profile,<br/>derivation rules, and canonical identity<br/>be specified completely?"}
    A["Proposal A<br/>Stabilize restricted public generics"]
    B["Proposal B<br/>Keep user generics package-local"]
    NEED -- "No" --> B
    NEED -- "Yes" --> PROFILE
    PROFILE -- "Yes" --> A
    PROFILE -- "No" --> B
Loading

Why this needs an explicit decision

The existing implementation has strong safety properties, but its ordinary source form exposes a large amount of compiler vocabulary:

public struct Pair<T: copy + drop + store + fixed + serializable + non_linear>
    has copy, drop, store, fixed, serializable, non_linear
{
    left: T,
    right: T,
}

The author's intent is much simpler:

T is an ordinary, fixed-width, serializable, non-Cell value.

Three costs currently leak into the user experience:

  1. Constraint repetition: six properties appear on both the parameter and the resulting type.
  2. Compatibility commitment: exporting a template commits its parameter count, phantom status, bounds, resulting abilities, layouts, and accepted applications.
  3. Interface overload: the canonical audit record is appropriately detailed, but that detail is not a good default human review surface.

Current 0.25 baseline

The implementation already provides:

  • deterministic, budgeted monomorphization before IR;
  • generic structs, enums, and pure functions;
  • explicit parameters, phantom parameters, and value-ability constraints;
  • built-in Option<T> and generic fixed arrays;
  • specialization of imported public templates in their owning module;
  • rejection of generic Cell-backed values;
  • canonical package interfaces containing exported templates and applied types;
  • exclusion of implementation-only monomorphizations from the public interface;
  • compatibility comparison across source API, serialized layout, runtime ABI, effects/capabilities, generated builders, and deployment contracts.
Non-negotiable safety boundary under either proposal
  • Generic Cell-backed resource, shared, and receipt declarations remain rejected.
  • Actions and locks do not become generic entry points.
  • Specialization remains deterministic, bounded, and complete before IR.
  • Value abilities never grant Cell lifecycle capabilities.
  • No higher-kinded types, open traits, inheritance, dynamic dispatch, or runtime reflection.
  • The canonical machine record retains fully expanded constraints.
  • Private implementation instantiations do not affect the public interface hash.
  • Equivalent source sugar normalizes to one semantic interface identity.
  • No implicit signer, sighash, witness-placement, or authorization semantics are introduced.

Reference comparison: Move and Sui Move

Move is the closest mature reference design, but its execution and package model differ materially from CellScript.

What Move does

Concern Move / Sui treatment Relevance to CellScript
Public generics Generic structs and functions are first-class public APIs. Type arguments are often inferred. Evidence that public generics can be usable, but not that CellScript needs them in 0.25.
Constraint vocabulary A closed set of four abilities: copy, drop, store, and key. A small vocabulary keeps declarations readable. CellScript currently exposes six properties.
Conditional abilities A generic datatype receives declared abilities only when its non-phantom arguments satisfy the corresponding requirements. Prefer structural derivation over repeating the same property list.
Phantom identity phantom T carries type identity without participating in datatype ability derivation. Closely matches CellScript's bounded phantom identity use case.
Machine interface Type parameters, constraints, phantom flags, parameters, returns, and generic instantiations remain in compiled module bytecode. The complete machine contract can stay detailed without requiring a handwritten interface.
Upgrade compatibility Public signatures cannot be removed or changed. Function constraints may be relaxed, not tightened. Existing datatype layouts and abilities remain fixed for Sui user-package upgrades. Generic bounds are a real compatibility commitment, not implementation detail.
Sensitive generic calls Sui adds a verifier-level “private generics” rule for selected framework functions: designated type arguments must be defined by the caller's module. Authority-sensitive operations can be narrowed independently instead of disabling all generics.

Primary implementation references:

Why the precedent is only partial

flowchart LR
    subgraph MOVE["Move / Sui"]
        MS["Public generic source"] --> MB["Generic bytecode<br/>handles, constraints, instantiations"]
        MB --> MV["Bytecode verifier and Move VM"]
    end

    subgraph CELL["CellScript 0.25"]
        CS["Generic source template"] --> CM["Deterministic bounded<br/>monomorphization before IR"]
        CM --> CI["Concrete IR and CKB-VM artifact"]
    end
Loading

Move keeps generic definitions and instantiations as first-class bytecode concepts. CellScript deliberately specializes them before IR and emits concrete CKB-VM code. Consequently, a CellScript public generic template also commits to source availability, package-instance identity, owning-module specialization, specialization budgets, concrete layout, and artifact evidence.

Move also has foundational public-generic use cases such as Coin<phantom T>, Balance<phantom T>, Option<T>, and vector<T>. CellScript's user-defined generics are restricted to non-Cell values, so the need for third-party public generic libraries must be demonstrated separately.

Lesson for this issue

Move supports the direction of Proposal A, but the reusable lesson is not “expose every constraint.” It is:

  1. keep the source-level property vocabulary small and closed;
  2. infer structural properties where the result is deterministic;
  3. treat public generic bounds as ABI commitments;
  4. keep the complete representation in compiler-owned metadata;
  5. apply additional restrictions at authority-sensitive operations;
  6. let tooling, rather than a second handwritten file, enforce compatibility.

Proposal A: restricted public generics with a compact source surface

Public generic templates remain available across packages, but the common source form describes intent instead of repeating the expanded property set.

The name remains an RFC choice; fixed_value is illustrative:

public struct Pair<T: fixed_value> {
    left: T,
    right: T,
}

The compiler would normalize that profile to the precise accepted properties, for example:

copy + drop + store + fixed + serializable + non_linear

Where safe, result abilities are derived structurally from fields. The profile and derivation algorithm must be closed, versioned, deterministic, non-overridable, and independently checkable. Compact and expanded spellings must produce the same semantic interface identity.

Layered interface presentation

flowchart TD
    SRC["Source<br/>Pair&lt;T: fixed_value&gt;"] --> NORM["Canonical normalization<br/>expanded abilities + layouts"]
    NORM --> HUMAN["Default human view<br/>exports, concise signatures, breaking changes"]
    NORM --> JSON["Canonical JSON<br/>full constraints, ABI, builders, deployment"]
    JSON --> CHECK["Registry + standalone checker<br/>hash and compatibility enforcement"]
Loading

The default human view could be:

public struct Pair<T: fixed_value>
public fn first<T: fixed_value>(Pair<T>) -> T

Detailed or JSON output would expose the expanded abilities, phantom status, layouts, ABI/profile identities, builder contracts, and deployment identities.

Benefits

  • Preserves cross-package generic library reuse.
  • Retains the existing monomorphization and interface work.
  • Keeps common source and review output concise.
  • Preserves complete audit evidence in the machine representation.

Risks

  • fixed_value becomes a versioned semantic contract.
  • Public generic bounds become long-term compatibility commitments.
  • Structural derivation must be specified for structs, enums, tuples, arrays, and future fixed values.
  • Cross-package specialization remains coupled to package and owning-module identity.

Gate for choosing A

Choose Proposal A only if all of the following are demonstrated before stabilization:

  • at least one concrete 0.25 use case requires cross-package user-defined generics;
  • a closed profile can be specified without creating an open trait system;
  • all equivalent spellings normalize to one interface identity;
  • the default human interface remains concise;
  • package-instance and owning-module identities are sufficient for safe specialization;
  • compatibility behavior for relaxing and tightening every bound is specified and tested.

Proposal B: keep user-defined generics package-local in 0.25

Keep the generic engine and safety work, but defer dependency-facing user-defined templates.

  • User-defined generic structs, enums, and pure functions may be private or public(package).
  • A public generic declaration is rejected with a targeted diagnostic.
  • Built-in Option<T> and fixed arrays remain supported.
  • Concrete applied types such as Option<u64> may appear in public signatures.
  • External APIs use concrete exported types or wrapper functions.
  • Internal instantiations remain in typed evidence but stay out of the dependency interface.

Benefits

  • Keeps the stable public language and interface smaller.
  • Preserves most of the implemented generic machinery.
  • Avoids premature cross-package specialization commitments.
  • Leaves room for a cleaner public-generic design in a later schema/version transition.

Risks

  • Third-party packages cannot publish generic value libraries in the 0.25 line.
  • Some package boundaries may need concrete wrappers or duplicated declarations.
  • Existing nightly packages with public generic templates require migration.

Compatibility rules required by either decision

Change Required treatment
Remove or rename a public export Breaking
Change generic parameter count or phantom status Breaking
Tighten a public generic constraint Breaking
Relax a public constraint Compatible only if all layout, runtime, effect, builder, and deployment dimensions remain valid
Change a private/package-local instantiation Must not change the public interface hash
Add a public export Compatible, but changes the exact interface identity
Change compact spelling without changing normalized semantics Must retain the same semantic interface identity

Unlike Move, CellScript cannot assume that relaxing a bound is automatically safe: a larger accepted type set may cross fixed-layout, serialization, code-size, specialization-budget, builder, or deployment boundaries. All six compatibility dimensions still apply.

Evidence requested before resolution

Please contribute concrete examples rather than syntax preference alone:

  1. Which package requires user-defined generics rather than built-in Option<T> or arrays?
  2. Which use case specifically requires cross-package specialization?
  3. Which exported template cannot reasonably be represented by concrete wrappers?
  4. Which ability combinations occur in real packages?
  5. Does a real package require phantom identity for a non-Cell value?
  6. What should a maintainer see in the default human interface view?

API signatures versus cryptographic signatures

Here, signature means a public type or callable API signature: type parameters, parameter types, return types, effects, and ABI-relevant qualifiers.

Cryptographic signatures are a separate security boundary. Neither proposal introduces implicit signers, hidden sighash defaults, or new signature-verification syntax. Transaction digest/domain, Script group, witness placement, identity binding, verifier/CellDep identity, replay policy, and authorization policy remain explicit.

Acceptance criteria

  • One proposal is selected in an accepted language-design decision.
  • Source syntax, visibility, and normalization are documented normatively.
  • Generic Cell-backed types and generic entries remain rejected.
  • Equivalent source forms produce one canonical semantic interface identity.
  • Private generic changes do not change the public interface hash.
  • Human output is concise and canonical JSON remains complete.
  • Interface diffs classify generic changes in the correct compatibility dimensions.
  • Registry admission and the independent verifier enforce the selected boundary.
  • Typed semantics and the artifact checker retain complete instantiation evidence.
  • Parser, formatter, type checker, resolver, monomorphization, IR, codegen, metadata, LSP, VS Code, Playground, docs, and syntax-combination tests agree.
  • Migration from the current nightly surface is explicit and tested.
  • Release notes describe the accepted boundary without presenting CellScript as a general-purpose generic language.
  • The dev, ci, and backend gates pass.

Related work

CellScript references

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions