Repository navigation
[Language design] Choose a stable surface for value generics and public API signatures #23
Description
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
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:
Tis an ordinary, fixed-width, serializable, non-Cell value.
Three costs currently leak into the user experience:
- Constraint repetition: six properties appear on both the parameter and the resulting type.
- Compatibility commitment: exporting a template commits its parameter count, phantom status, bounds, resulting abilities, layouts, and accepted applications.
- 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:
- Move generics and type inference
- Move's four abilities and conditional ability derivation
- Generic datatype and function signatures in Move bytecode
- Move bytecode compatibility rules
- Sui package upgrade requirements
- Sui verifier-level private generics
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
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:
- keep the source-level property vocabulary small and closed;
- infer structural properties where the result is deterministic;
- treat public generic bounds as ABI commitments;
- keep the complete representation in compiler-owned metadata;
- apply additional restrictions at authority-sensitive operations;
- 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<T: fixed_value>"] --> 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"]
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_valuebecomes 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
privateorpublic(package). - A
publicgeneric 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:
- Which package requires user-defined generics rather than built-in
Option<T>or arrays? - Which use case specifically requires cross-package specialization?
- Which exported template cannot reasonably be represented by concrete wrappers?
- Which ability combinations occur in real packages?
- Does a real package require phantom identity for a non-Cell value?
- 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, andbackendgates pass.
Related work
- [Language/stdlib] Add typed CKB temporal and Since domains #12 needs an explicit decision on the generic spelling of typed
Sincedomains. - [Toolchain][Resolver][P1] Define and enforce package instance, version, source, and feature unification #16 owns package-instance and version/source/feature identity used by cross-package specialization.
- [Language/ABI] Bind runtime Script values to verified package interfaces #11 binds runtime Script values to verified package interfaces and should consume, not broaden, this boundary.
Activity
a19q3 commentedon Sep 11, 2026
Completed and accepted for the bounded Proposal A scope in
69488316, with acceptance evidence inf47a6c6e: public cross-package value templates/functions, fixed-value normalization, aggregate ability derivation, bounded monomorphization, canonical interface hashing/compatibility, Registry/checker validation, and product-surface parity are implemented. The recorded dev/CI/backend gates passed at that candidate commit; recursive/unbounded layouts remain non-goals. Ledger recordsremaining_work=[]atcdc5b53c. No TAC or release workflow was invoked.