Properties

Properties provide a getter and an optional setter to indirectly get and set values.

The use of properties is the same as that of common variables. You only need to perform operations on data and are unaware of internal implementation. In this way, mechanisms such as access control, data monitoring, tracing and debugging, and data binding can be implemented more conveniently.

Properties can be used as expressions or assigned values. The following uses classes and interfaces as examples, but properties are not limited to classes and interfaces.

For example, b is a typical property that encapsulates external access to a:

class Foo {
    private var a = 0

    public mut prop b: Int64 {
        get() {
            println("get")
            a
        }
        set(value) {
            println("set")
            a = value
        }
    }
}

main() {
    var x = Foo()
    let y = x.b + 1 // get
    x.b = y // set
}

Foo provides a property named b. Cangjie provides the get and set syntaxes to define the getter and setter functions. When variable x of the Foo type accesses b, the get operation of b is called to return a value of the Int64 type. Therefore, variable x can be added to 1. When x assigns a value to b, the set operation of b is called to transfer the value of y to the value of set, and finally value is assigned to a.

Through property b, the external system is unaware of the member variable a of Foo, but the same access and modification operations can be performed through property b, thereby implementing effective encapsulation. The program output is as follows:

get
set

Defining Properties

Properties can be defined in interface, class, struct, enum, and extend.

A typical property syntax structure is as follows:

class Foo {
    public prop a: Int64 {
        get() { 0 }
    }
    public mut prop b: Int64 {
        get() { 0 }
        set(v) {}
    }
}

a and b declared using prop are properties, and the types of a and b are both Int64. a is a property without the mut modifier. This type of property has only the implementation of getter (corresponding to the value to be got). b is a property modified by mut. This type of property must define the implementations of getter (corresponding to the value to be got) and setter (corresponding to the value to be assigned).

The getter and setter of a property correspond to two different functions.

  1. The type of the getter function is () -> T, where T is the type of the property. When the property is used as an expression, the getter function is executed.
  2. The type of the setter function is (T) -> Unit. T indicates the type of the property. The formal parameter name needs to be explicitly specified. When a value is assigned to the property, the setter function is executed.

Like the function body, the implementations of getter and setter can contain declarations and expressions. The rules of getter and setter are the same as those of the function body. For details, see [Function Body](../function/define_functions.md# Function Body).

The parameter in setter corresponds to the value transferred during value assignment.

class Foo {
    private var j = 0
    public mut prop i: Int64 {
        get() {
            j
        }
        set(v) {
            j = v
        }
    }
}

It should be noted that accessing a property in the getter and setter of the property is a recursive call, which may cause an infinite loop like the case in calling a function.

Modifiers

You can declare the required modifiers before prop.

class Foo {
    public prop a: Int64 {
        get() {
            0
        }
    }
    private prop b: Int64 {
        get() {
            0
        }
    }
}

Like member functions, member properties can be modified by open, override, and redef. Therefore, you can override or redefine the implementations of supertype properties in subtypes.

When a subtype overrides a property of a supertype, if the property of the supertype has the mut modifier, the property of the subtype must also have the mut modifier and be of the same type as the supertype property.

As shown in the following code, properties x and y are defined in A, and override or redef can be performed on properties x and y in B.

open class A {
    private var valueX = 0
    private static var valueY = 0

    public open prop x: Int64 {
        get() { valueX }
    }

    public static mut prop y: Int64 {
        get() { valueY }
        set(v) {
            valueY = v
        }
    }
}
class B <: A {
    private var valueX2 = 0
    private static var valueY2 = 0

    public override prop x: Int64 {
        get() { valueX2 }
    }

    public redef static mut prop y: Int64 {
        get() { valueY2 }
        set(v) {
            valueY2 = v
        }
    }
}

Abstract Properties

Similar to abstract functions, abstract properties can be declared in an interface or abstract class and the abstract properties are not implemented.

interface I {
    prop a: Int64
}

abstract class C {
    public prop a: Int64
}

When an implementing type implements an interface or a non-abstract subclass inherits an abstract class, the abstract properties must be implemented.

The rule for implementing abstract properties is similar to that for overriding. When an implementing type or a subclass implements the abstract properties, if the supertype property has the mut modifier, the subtype property must also have the mut modifier and be of the same type as the supertype property.

interface I {
    prop a: Int64
    mut prop b: Int64
}
class C <: I {
    private var value = 0

    public prop a: Int64 {
        get() { value }
    }

    public mut prop b: Int64 {
        get() { value }
        set(v) {
            value = v
        }
    }
}

Through abstract properties, you can specify easy-to-use data operations for properties and abstract classes. Abstract properties are more intuitive than functions.

As shown in the following code, if you want to specify how to get and set of a size value, using a property (I1) requires less code than using a function (I2) and is more suitable for data operation.

interface I1 {
    mut prop size: Int64
}

interface I2 {
    func getSize(): Int64
    func setSize(value: Int64): Unit
}

class C <: I1 & I2 {
    private var mySize = 0

    public mut prop size: Int64 {
        get() {
            mySize
        }
        set(value) {
            mySize = value
        }
    }

    public func getSize() {
        mySize
    }

    public func setSize(value: Int64) {
        mySize = value
    }
}

main() {
    let a: I1 = C()
    a.size = 5
    println(a.size)

    let b: I2 = C()
    b.setSize(5)
    println(b.getSize())
}
5
5

Using Properties

Properties are classified into instance member properties and static member properties. The usage of member properties is the same as that of member variables. For details, see [Member Variables](./class.md#class-Member Variables).

class A {
    public prop x: Int64 {
        get() {
            123
        }
    }
    public static prop y: Int64 {
        get() {
            321
        }
    }
}

main() {
    var a = A()
    println(a.x) // 123
    println(A.y) // 321
}

The result is:

123
321

A property without the mut modifier is similar to a variable declared by let and cannot be assigned a value.

class A {
    private let value = 0
    public prop i: Int64 {
        get() {
            value
        }
    }
}

main() {
    var x = A()
    println(x.i) // OK
    x.i = 1 // Error
}

A property with the mut modifier is similar to a variable declared by var and you can get the value of the property or assign a value to it.

class A {
    private var value: Int64 = 0
    public mut prop i: Int64 {
        get() {
            value
        }
        set(v) {
            value = v
        }
    }
}

main() {
    var x = A()
    println(x.i) // OK
    x.i = 1 // OK
}
0