Files
Structive/docs/DESIGN.md
T
2026-08-11 11:54:09 +08:00

12 KiB

Structive Design Philosophy and Principles

中文

1. Purpose

Structive exists to give ordinary C++ structs explicit structural meaning while preserving the native C++ object model.

It is not a replacement language, reflection runtime, security boundary, ORM or object framework. It is a structural metadata and managed-property layer that remains close to normal C++.

The design target is:

ordinary C++ data
    + explicit schema
    + intrinsic property capability
    + metadata
    + optional managed synchronization
    + optional runtime adaptation

2. Primary principle: enhance, do not replace

The first rule is:

Enhance the struct; do not replace the struct.

A registered field remains a real member. Unregistered state remains normal C++. Raw access remains possible when the C++ type itself permits it.

Structive must not require every field to become a wrapper such as Property<T>, nor should it force application objects into a second object model.

Rule

If a Structive feature can be implemented as metadata or a thin managed layer without changing the native member model, prefer that design.

3. Type information and instance behavior are separate

Structive has two architectural layers.

3.1 Type layer

Type_Descriptor<T> and Object_Schema contain structural facts:

  • registered properties;
  • keys;
  • intrinsic readable/writable capability;
  • Attributes;
  • Constraints;
  • default synchronization description.

This information belongs to the type.

3.2 Instance layer

Property_Object<T> provides instance behavior:

  • managed read/write;
  • lock storage for writable synchronization domains;
  • multi-property guards;
  • traversal;
  • type-erased runtime access;
  • optional per-instance synchronization override.

Rule

Do not move type-level facts into every object instance unless the fact genuinely varies per instance.

4. Registration is explicit

Structive does not assume every member is a property.

struct Device : Property_Object<Device> {
    int temperature;
    int internal_cache;
};

If only temperature is registered, internal_cache does not exist in the Structive schema.

Rule

Registration defines participation. Absence from the schema means absence from Structive.

5. Intrinsic capability belongs to the property itself

Structive has no built-in access-control subsystem.

A property only describes what it intrinsically supports:

none
read
write
read_write

The capability is normally derived from the accessor. Metadata may explicitly narrow it.

field<&Device::serial_number>(key<"serial_number">, read_only)

This says:

Within the Structive managed model, this property is readable and not writable.

It does not say which user, service, GUI or process is allowed to see it.

Rule

Property capability describes structure, not authorization.

6. Core must not own external access policy

Structive deliberately does not define:

internal
external
persistence
role
context
permission

as managed access modes.

A GUI can decide which properties are editable. An RPC service can decide which fields are exposed. A persistence layer can decide which fields it saves. Those decisions belong to those systems.

Core exposes structural facts; consumers define policy.

Rule

Do not add access policy to Core merely because an adapter needs a policy. The adapter owns that policy.

7. Raw access and managed access are distinct contracts

These are intentionally different:

device.temperature = 30;
device.write<&Device::temperature>(30);

Raw access follows normal C++ rules. Managed access follows the Structive schema and synchronization model.

If code writes a read_only public member directly, it has intentionally bypassed the Structive contract.

Rule

Structive protects cooperative managed code. It does not pretend to prevent deliberate raw C++ access.

8. Read-only metadata must produce a real optimization

A stored property that cannot be written through Structive cannot race with another Structive managed writer, because no managed writer exists.

Therefore a stored intrinsic read-only property:

  • receives no lock slot;
  • contributes no mutex;
  • ignores broad synchronization defaults;
  • performs managed reads without lock lookup;
  • performs managed reads without shared_lock construction.

This is a structural optimization derived from schema information.

Rule

If the schema proves that synchronization state is unnecessary, do not allocate or execute it.

9. Compile-time knowledge should remove runtime work

Typed APIs know the selected property at compile time:

device.read<&Device::serial_number>();

For a stored read-only property, the compiler-visible implementation path bypasses synchronization entirely.

Similarly, typed writes to a read-only property are removed by constraints rather than accepted and rejected at runtime.

Dynamic key APIs use runtime checks because the key is not known until runtime.

Rule

Static facts should become constexpr, requires or if constexpr, not runtime branches.

10. Synchronization describes mutable consistency domains

Synchronization exists to coordinate mutable managed state.

The default rules are:

independent
shared
unsynchronized

Groups allow several mutable properties to share one lock domain.

Read-only stored properties are removed from the final resolved lock topology even if a broad rule names them.

Rule

Synchronization topology is about mutable consistency, not property visibility.

11. Computed read-only properties are a special case

A computed property may itself be read-only while depending on writable fields.

The computed property does not contain writable storage. However, its read may require a stable snapshot of mutable dependencies.

Dependencies must be explicit Schema facts. The Synchronized_Computed_Accessor read view can access only declared direct dependencies, and the computed property's read slot is derived from the dependency graph instead of requiring its key to be repeated in a synchronization group.

Writable dependencies that require one atomic snapshot must share one synchronization domain with each other. Read-only stored dependencies are safe to read directly through the synchronized view because they have no managed writer.

Rule

Do not give a read-only stored field a mutex. A computed read uses synchronization only for mutable dependencies that require consistency.

12. One Attribute protocol

Core and extensions share one Attribute mechanism.

An Attribute owns a category and may define whether it is single-valued and inheritable.

Core must not create parallel metadata systems for UI, serialization, diagnostics or domain-specific features.

Rule

New metadata domains should extend the Attribute protocol instead of adding a second metadata framework.

13. Category ownership must be clear

The component that defines an Attribute category owns its semantics.

Presentation owns presentation categories. Core can store them, traverse them and expose them generically, but it must not interpret them.

This keeps dependency direction one-way:

Extension → Core
Core     -X→ Extension

Rule

Core stores unknown extension metadata without learning extension semantics.

14. Defaults are metadata inheritance only

defaults(...) is for inheritable Attributes. It should not mutate objects or hide procedural behavior.

Property-level metadata can override an inheritable default for the same single-valued category.

Intrinsic capability is not inheritable because the capability describes each property itself.

Rule

Defaults may reduce metadata repetition, but they must not become an invisible behavior engine.

15. Validation is explicit

Constraints describe valid values. They do not automatically execute inside every write.

auto error = validate_property_value<&Device::temperature>(schema, candidate);

Field validation, cross-field invariants, transaction boundaries and rollback are separate operations.

Rule

Do not turn write() into an implicit workflow containing validation, events, transactions and rollback.

16. Runtime access is adaptation, not the primary programming model

Property_Object_Base provides key-based type-erased access for dynamic systems.

Runtime access only uses intrinsic capability. There is no runtime permission mode.

An adapter decides whether it should expose a property and whether it should invoke runtime read or write.

Rule

Use member-pointer typed access inside normal C++ business code. Use runtime access at dynamic boundaries.

17. Runtime and compile-time errors have different jobs

Compile-time typed operations should reject impossible structural operations through constraints.

Examples:

  • writing an intrinsic read-only property;
  • requesting a typed unique guard for a read-only property;
  • reading a write-only property.

Runtime key APIs report dynamic failures through Runtime_Access_Result. The runtime write boundary is copy-input by design: intrinsic writable remains a structural fact, while runtime_copy_writable states whether the accessor can participate in that type-erased copy boundary. Move-only typed writes therefore do not get mislabeled as structurally non-writable.

Rule

Do not defer a statically knowable property error to runtime.

18. Synchronization policy remains replaceable

Shared_Mutex_Policy provides real shared/exclusive locking. No_Lock_Policy removes real mutex storage.

NoLock should not allocate fake mutex arrays or construct meaningless lock objects.

Rule

A policy that removes a capability should remove its storage and hot-path cost where possible.

19. Per-instance synchronization overrides preserve object-local policy

The default resolved topology is shared per type. An object may explicitly receive a Property_Synchronization override.

Only such an object stores an override layout. Copy/move construction preserves that layout; assignment preserves the target object's synchronization policy.

Rule

Do not make the default path pay the storage cost of a feature that only some instances use.

20. Extensions decide domain behavior

A persistence extension may choose fields based on its own metadata. A GUI may decide editability. An RPC layer may implement authorization.

Those systems may inspect Structive metadata such as readable, writable, sensitive or custom extension Attributes, but Structive does not decide their policy for them.

Rule

Core describes structure. Consumers decide behavior at their boundary.

21. Non-goals

Property Core is not intended to become:

  • an authorization engine;
  • an ORM;
  • a JSON library;
  • an RPC framework;
  • a GUI binding framework;
  • a transaction manager;
  • an event bus;
  • a scripting engine.

Such systems can consume Structive but should remain separate.

22. Evolution rules

When extending Structive, review the change against these rules:

  1. Does it enhance normal C++ rather than replace it?
  2. Is the information type-level or instance-level?
  3. Is this intrinsic structural capability or external policy?
  4. Can compile-time information remove runtime work?
  5. Does a read-only stored property remain outside the lock topology?
  6. Is synchronization limited to mutable consistency?
  7. Is validation still explicit?
  8. Does the extension own its own category semantics?
  9. Is runtime adaptation kept separate from typed business APIs?
  10. Is a new Core concept genuinely universal?

23. Architectural summary

The intended architecture is:

Native C++ struct
        │
        ├── raw C++ access
        │
        └── Structive schema
                │
                ├── intrinsic readable/writable capability
                ├── Attributes
                ├── Constraints
                ├── synchronization description
                │       └── only mutable consistency domains create locks
                └── managed object
                        ├── typed read/write
                        ├── guards
                        ├── traversal
                        └── intrinsic runtime access

External systems
        ├── GUI policy
        ├── RPC policy
        ├── persistence policy
        └── other domain policy

External policies consume Structive facts; they are not Structive Core access modes.

The compact statement of the design is:

Structive describes what a property is and what it intrinsically supports. It does not decide who may use it.