Properties

A property is a behavioral member that uses field-like access syntax. It can control how a value is read, how assignment is handled, or both.

impl Vehicle {
    property TopSpeed: Speed {
        get {
            return self._speed
        }

        set speed {
            self._speed = speed
        }
    }
}

Callers use normal member syntax:

let speed := vehicle.TopSpeed
vehicle.TopSpeed = requestedSpeed
Core rule: A field stores representation. A property defines behavior. Declaring a property does not create hidden instance storage.

Fields vs Properties

A field is stored state:

type Vehicle struct {
    _speed: Speed,
}

A property is controlled behavior:

impl Vehicle {
    property TopSpeed: Speed {
        get {
            return self._speed
        }
    }
}

The property may read a field, compute a result, synchronize access, translate another representation, observe foreign state, or perform other operations permitted by Sec's ordinary rules.

The property syntax itself never means “create a hidden backing field”.

If no behavioral boundary is needed, use a field or another direct stored member. Use a property when the access itself has meaning.

Property Declaration

An instance property is declared inside an implementation:

impl Vehicle {
    property TopSpeed: Speed {
        get {
            return self._speed
        }

        set speed {
            self._speed = speed
        }
    }
}

The declaration has:

  • a property name;
  • a declared property type;
  • one or more accessors.

The general shape is:

property Name: Type {
    accessors
}

A property must contain at least one accessor.

A property body is not a general declaration block. It may contain only the property accessors permitted by the language.

Accessor Combinations

A property may contain:

  • one get;
  • one set;
  • or one try set.

set and try set are mutually exclusive because both define assignment behavior.

Accessors Property behavior
get Read-only
set name Write-only, infallible
try set name ErrorType Write-only, fallible
get + set name Read-write, infallible assignment
get + try set name ErrorType Readable, fallible assignment

A property without a getter is not readable.

A property without a setter is not assignable.

Getter Semantics

A getter returns the property's declared type:

property CurrentSpeed: Speed {
    get {
        return self._speed
    }
}

A getter:

  • must return a value assignable to the property type;
  • must return on every reachable path required by normal return analysis;
  • uses ordinary expression, ownership and borrowing rules;
  • receives implicit self for an instance property;
  • is behavioral and is not assumed to be a simple stored-field load.

Getters are infallible in Sec 0.1

Sec 0.1 does not define try get.

This is not property syntax:

property Data: Data {
    try get DataError {
        ...
    }
}

If reading an abstraction can legitimately fail, expose that fallibility through an ordinary method or another API whose type communicates the failure.

The Setter Parameter Is Always Explicit

Every setter names its incoming value:

property Name: string {
    set value {
        self._name = value
    }
}

The parameter name is chosen by the programmer.

property Name: string {
    set name {
        self._name = name
    }
}

The setter parameter automatically has the declared property type.

There is no implicit C#-style setter variable named value.

This is invalid:

property Name: string {
    set {
        self._name = value
    }
}
Rule: The setter parameter is explicit in instance properties, static properties and interface property requirements.

Infallible set

An infallible setter uses set:

property TopSpeed: Speed {
    set speed {
        self._speed = speed
    }
}

Assignment:

vehicle.TopSpeed = requestedSpeed

means that:

  1. the receiver is evaluated;
  2. requestedSpeed is evaluated;
  3. the setter parameter speed receives that value;
  4. the setter body executes.

An infallible setter does not expose an error result.

If assignment can legitimately fail, the property must use try set instead of hiding the failure.

Fallible try set

A property whose assignment may fail uses try set and declares the error type explicitly:

property TopSpeed: Speed {
    get {
        return self._speed
    }

    try set speed SpeedError {
        if speed < MinimumSpeed {
            return Err(SpeedError.TooLow)
        }

        self._speed = speed
    }
}

The shape is:

try set ParameterName ErrorType {
    ...
}

ErrorType must be error or a concrete Sec error type. It is part of the public property contract and is not inferred from the body.

Success

Normal completion means success:

try set speed SpeedError {
    self._speed = speed
}

A bare return is also success:

try set speed SpeedError {
    if speed == self._speed {
        return
    }

    self._speed = speed
}

Failure

Failure is explicit:

return Err(SpeedError.TooLow)

No return Ok()

This is invalid:

try set speed SpeedError {
    self._speed = speed
    return Ok()
}

A try set accessor is not written as a source-level function returning Result[void, E]. Its error contract is expressed by the accessor syntax.

Fallible Property Assignment

Assignment through a fallible setter must be visibly fallible at the call site.

try vehicle.TopSpeed = requestedSpeed {
    Err(error) => Handle(error)
}

Omitting try is invalid:

vehicle.TopSpeed = requestedSpeed

when TopSpeed selects a try set accessor.

This keeps assignment syntax honest: a property cannot hide a failing operation behind syntax that looks infallible.

Propagation and other try forms follow the ordinary Sec error-handling rules.

Compound Assignment

Compound assignment uses both property read and property write behavior.

vehicle.Count += 1

requires:

  • a getter;
  • a valid setter;
  • a valid underlying compound operation for the property type.

A write-only property cannot be used with compound assignment because there is no value to read first.

If the selected setter is fallible, compound assignment also requires try:

try vehicle.TopSpeed += delta {
    Err(error) => Handle(error)
}

Compound Assignment Evaluation Order

Sec gives compound property updates precise evaluation guarantees.

For:

GetVehicle().Count += NextDelta()

the conceptual order is:

  1. evaluate GetVehicle() exactly once;
  2. read the getter exactly once;
  3. evaluate NextDelta() exactly once;
  4. compute the compound result;
  5. invoke the setter exactly once with the computed value.

The compiler must preserve this source-visible order and every observable side effect.

Rule: Compound property assignment is not permission to duplicate the receiver, getter, right-hand side or setter.

Implicit self

Instance property accessors receive compiler-provided self.

impl Counter {
    property Value: int {
        get {
            return self._value
        }

        set next {
            self._value = next
        }
    }
}

The programmer does not declare self, ref self or ref mut self in property syntax.

The compiler derives the receiver access required by each accessor from the accessor body and ordinary ownership and borrowing rules.

A setter normally requires mutable receiver authority when it mutates instance state. A getter may require only shared access when its implementation only reads state.

Primary Impl and impl extends

Properties may appear in the primary implementation:

impl Vehicle {
    property Speed: Speed {
        get {
            return self._speed
        }
    }
}

or in a same-module extension fragment:

impl extends Vehicle {
    property IsMoving: bool {
        get {
            return self._speed > 0
        }
    }
}

All valid fragments form one combined implementation member surface.

A property therefore cannot be duplicated or conflict with another member merely because the declarations are in different source files.

Static Properties

A type-level property uses explicit static property:

impl Application {
    static let mut _mode: ApplicationMode := ApplicationMode.Normal

    static property Mode: ApplicationMode {
        get {
            return Application._mode
        }

        set mode {
            Application._mode = mode
        }
    }
}

Use the type to access it:

let current := Application.Mode
Application.Mode = ApplicationMode.Safe

A static property:

  • has no instance receiver;
  • may not use self;
  • uses the same explicit setter-parameter rule;
  • supports get, set and try set under the same property rules;
  • is accessed through the type, not an instance.

Static properties do not themselves create static storage. Their implementation may use explicit static storage or compute a value by another valid mechanism.

Properties in Interface Contracts

An interface may require observable property behavior without requiring any particular stored field.

interface Positioned {
    property Position: Point {
        get
        try set position PositionError
    }
}

An interface property requirement specifies:

  • the property name;
  • the property type;
  • required readable access with get;
  • required infallible writable access with set name;
  • or required fallible writable access with try set name ErrorType.

The setter parameter remains explicit even though the interface contains no setter body.

The implementing type may satisfy the contract using:

  • stored state;
  • a computed value;
  • foreign state;
  • register access;
  • another valid implementation strategy.

Conformance checks include:

  • property type;
  • required accessor availability;
  • setter fallibility;
  • fallible setter error contract;
  • static versus instance membership;
  • receiver capability.

Property Identity and Member Namespace

A property has semantic identity including:

  • name;
  • declared property type;
  • getter availability;
  • setter kind;
  • fallible setter error type where applicable;
  • static or instance membership;
  • source location.

Property names participate in the target type's member namespace.

A property may not silently collide with:

  • a stored field;
  • a register field;
  • another property;
  • a method where the member naming rules forbid the collision;
  • another associated member occupying the same namespace.

Member registration happens before property bodies are analyzed, so accessors may use compatible members declared later in the same complete implementation surface.

Effects and Side Effects

Member-access syntax does not imply purity.

let value := object.Property

may execute getter behavior.

A getter is not assumed pure merely because it visually resembles field access. A setter is not assumed to be a simple field store.

Property semantics preserve:

  • evaluation order;
  • getter side effects;
  • setter side effects;
  • fallible setter behavior;
  • ownership and borrowing;
  • cleanup and defer;
  • volatile effects reached through the implementation;
  • foreign effects reached through the implementation.

Optimizations may inline or simplify a property only when all language-visible behavior remains equivalent.

Ownership and Borrowing

Properties use ordinary Sec ownership and borrowing rules.

The property type determines the value that crosses the accessor boundary. The accessor body must respect the same move, copy, borrow and lifetime rules as equivalent ordinary code.

A property does not become addressable stored state merely because its access uses object.Name syntax.

Receiver requirements are derived from what the accessor actually does. A property that mutates state requires the appropriate mutable authority; a read-only computation may require only shared receiver access.

Fallibility does not weaken ownership guarantees. A failed try set follows ordinary path-sensitive ownership and cleanup rules.

Properties and Constrained Types

Contracts belong to types, not to properties.

type Percent int range 0..100

A property may use the constrained type directly:

type Settings struct {
    _volume: Percent,
}

impl Settings {
    property Volume: Percent {
        get {
            return self._volume
        }

        set volume {
            self._volume = volume
        }
    }
}

Because the setter parameter already has type Percent, its value has already satisfied the type's contract before entering the setter.

If an accessor performs a separate checked conversion or other fallible operation, that fallibility follows the ordinary try rules and, for a setter, must be represented by a try set error contract where failure may escape.

Rule: A property cannot bypass the contracts of its declared type or of any constrained named type used inside its implementation.

Compiler-Known SizeOf Properties

Sec also uses the read-only property shape for certain compiler-known authoritative properties.

In particular, where defined by the compiler-known registry:

value.SizeOf
TypeName.SizeOf

have canonical uint property shapes.

For example:

let valueSize: uint := value.SizeOf
let typeSize: uint := Packet.SizeOf

These are compiler-known properties rather than ordinary user declarations. User code cannot replace them where the compiler-known property applies.

Sec 0.1 has no global source form:

SizeOf(Packet)
Rule: Compiler-known properties reuse the property access model without pretending to be ordinary user-defined implementation members.

Property Access Has Distinct Semantic Meaning

The compiler must preserve the difference between:

  • field read;
  • field write;
  • property read;
  • property write;
  • fallible property write;
  • compound property update.

A backend must not infer property semantics later from ordinary field access.

This distinction is important even when optimization eventually makes a trivial property access as cheap as a direct field access.

Source semantics are defined by the accessor behavior, not by the final machine instruction sequence.

Diagnostics

Important property diagnostics include:

  • missing property name;
  • missing : or property type;
  • property with no accessor;
  • duplicate getter;
  • duplicate setter;
  • both set and try set on one property;
  • setter without an explicit value parameter;
  • missing or invalid error type on try set;
  • getter value incompatible with the property type;
  • getter missing a required return;
  • read from a write-only property;
  • assignment to a read-only property;
  • assignment value incompatible with the property type;
  • fallible property assignment without try;
  • return Ok() inside try set;
  • compound assignment without a getter;
  • invalid receiver or borrow use in an accessor;
  • instance/static access mismatch;
  • duplicate or conflicting property across implementation fragments;
  • interface property conformance mismatch.

A useful fallibility diagnostic is:

assigning fallible property TopSpeed requires try

A missing setter parameter should identify the property directly:

setter for Name must declare a value parameter

LSP hover should expose whether a property is:

  • instance or static;
  • readable;
  • infallibly writable;
  • fallibly writable;
  • and the explicit setter error type where applicable.

Best Practices

  • Use fields for stored representation.
  • Use properties for controlled behavior.
  • Prefer a field when no behavioral boundary is required.
  • Use a property when access must validate, compute, synchronize, translate or observe.
  • Name setter parameters for clarity; value is conventional but never implicit.
  • Keep getters unsurprising when possible, even though getters may legally have effects.
  • Use try set whenever assignment can legitimately fail.
  • Do not hide failure inside an infallible set.
  • Remember that compound assignment performs both a read and a write.
  • Use methods instead of properties when an operation's cost, fallibility or action-like nature would be clearer as a call.
  • Keep related properties near the type's main behavior; use impl extends when coherent source separation improves readability.

Summary

  • A property is behavior presented through member-like access syntax.
  • A property does not create hidden storage.
  • Properties belong to implementations or interface contracts.
  • A property must contain at least one accessor.
  • A property may have one get and either one set or one try set.
  • set and try set are mutually exclusive.
  • A property without get is write-only.
  • A property without a setter is read-only.
  • Getters are infallible in Sec 0.1.
  • Sec 0.1 has no try get.
  • A getter must produce the declared property type on every continuing path.
  • Every setter names its incoming value parameter explicitly.
  • The setter parameter automatically has the property type.
  • Sec has no implicit setter variable named value.
  • set name defines infallible assignment.
  • try set name ErrorType defines fallible assignment.
  • A fallible setter's error type is explicit and is not inferred from the body.
  • Normal completion or bare return means success in try set.
  • return Err(error) reports setter failure.
  • return Ok() is invalid inside try set.
  • Assignment through a fallible setter requires explicit try.
  • Compound property assignment requires both a getter and a setter.
  • A fallible compound update also requires try.
  • Compound assignment evaluates receiver, getter, right-hand side and setter exactly once in defined order.
  • Instance property accessors receive implicit self.
  • The compiler derives accessor receiver requirements from the accessor bodies.
  • Properties may appear in the primary implementation or same-module impl extends fragments.
  • Static properties use explicit static property, have no self and are accessed through the type.
  • Interface properties describe observable behavior, not stored fields.
  • Interface conformance checks property type, accessors, fallibility, error contract, static/instance category and receiver capability.
  • Property names participate in the type's combined member namespace.
  • Properties may not silently collide with fields, register fields, other properties or incompatible members.
  • Property syntax does not imply purity.
  • Getter and setter side effects, ownership, cleanup, volatile effects and foreign effects must be preserved.
  • Properties use ordinary ownership and borrowing rules.
  • A constrained property type preserves that type's contracts.
  • Compiler-known value.SizeOf and TypeName.SizeOf use canonical read-only uint property shapes where defined.
  • Sec 0.1 has no global SizeOf(TypeName) source form.
  • Semantic lowering preserves property reads, writes, fallible writes and compound updates as distinct operations until optimization can safely simplify them.