属性

属性是一种特殊的语法,它不像字段一样会存储值,相反它们提供了一个 getter 和一个可选的 setter 来间接检索和设置值。

通过使用属性可以将数据操作封装成访问函数,使用的时候与普通字段无异,我们只需要对数据操作,对内部的实现无感知,可以更便利地实现访问控制、数据监控、跟踪调试、数据绑定等机制。

属性在使用时语法与字段一致,可以作为表达式或被赋值。

以下是一个简单的例子,b 是一个典型的属性,封装了外部对 a 的访问:

class Foo {
    private var a = 0
    mut prop b: Int64 {
        get() {
            print("get")
            a
        }
        set(value) {
            print("set")
            a = value
        }
    }
}

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

属性的语法

属性的语法规则为:

propertyDefinition
    : propertyModifier* 'prop' identifier ':' type propertyBody?
    ;

propertyBody
    : '{' propertyMemberDeclaration+ '}'
    ;

propertyMemberDeclaration
    : 'get' '(' ')' block end*
    | 'set' '(' identifier ')' block end*
    ;

propertyModifier
    : 'public'
    | 'private'
    | 'protected'
    | 'internal'
    | 'static'
    | 'open'
    | 'override'
    | 'redef'
    | 'mut'
    ;

没有 mut 修饰符声明的属性需要定义 getter 实现。用 mut 修饰符声明的属性需要单独的 getter 和 setter 实现。

特别是,对于数值类型、BoolUnitNothingRuneStringRangeFunctionEnumTuple 类型,在它们的扩展或定义体中不能定义 mut 修饰的属性,也不能实现有 mut 属性的接口。

如下面的示例所示,a 是没有 mut 声明的属性,b 是使用 mut 声明的属性。

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

没有mut声明的属性没有 setter,而且与用 let 声明的字段一样,它们不能被赋值。

class A {
	prop i: Int64 {
		get() {
			0
		}
	}
}
main() {
    var x = A()
    x.i = 1 // error
}

特别是,当使用 let 声明 struct 的实例时,不能为 struct 中的属性赋值,就像用 let 声明的字段一样。

struct A {
	var i1 = 0
    mut prop i2: Int64 {
        get() {
            i1
        }
        set(value) {
            i1 = value
        }
    }
}
main() {
    let x = A()
    x.i1 = 2 // error
    x.i2 = 2 // error
}

属性与字段不同,属性不可以赋初始值,必须要声明类型。

属性的定义

属性可以在 interface, class,struct,enum,extend 中定义。

class A {
    prop i: Int64 {
        get() {
            0
        }
    }
}

struct B {
    prop i: Int64 {
        get() {
            0
        }
    }
}

enum C {
    prop i: Int64 {
        get() {
            0
        }
    }
}

extend A {
    prop s: String {
        get() {
            ""
        }
    }
}

可以在 interfaceabstract class 中声明抽象属性,它的定义体可以省略。在实现类型中实现抽象属性时,它必须保持相同的名称、相同的类型和相同的 mut 修饰符。

interface I {
    prop a: Int64
}

class A <: I {
    public prop a: Int64 {
        get() {
            0
        }
    }
}

如同 interface 中的抽象函数可以拥有默认实现,interface 中的抽象属性也同样可以拥有默认实现。

拥有默认实现的抽象属性,实现类型可以不提供自己的实现(必须符合默认实现的使用规则)。

interface I {
    prop a: Int64 { // ok
        get() {
            0
        }
    }
}
class A <: I {} // ok

属性分为实例成员属性和静态成员属性。其中,实例成员属性只能由实例访问,在 getter 或 setter 的实现中可以访问 this、实例成员和其它静态成员。而静态成员属性只能访问静态成员。

class A {
    var x = 0
    mut prop X: Int64 {
        get() {
            x + y
        }
        set(v) {
            x = v + y
        }
    }
    static var y = 0
    static mut prop Y: Int64 {
        get() {
            y
        }
        set(v) {
            y = v
        }
    }
}

属性不支持重载,也不支持遮盖,不能和其它同级别成员重名。

open class A {
	var i = 0
	prop i: Int64 { // error
		get() {
			0
		}
	}
}
class B <: A {
    prop i: Int64 { // error
		get() {
			0
		}
	}
}

属性的实现

属性的 getter 和 setter 分别对应两个不同的函数。

  1. getter 函数类型是 ()->T,T 是该属性的类型,当使用该属性作为表达式时会执行 getter 函数。
  2. setter 函数类型是 (T)->Unit,T 是该属性的类型,形参名需要显式指定,当对该属性赋值时会执行 setter 函数。

属性的实现同函数的实现规则一样,其中可以包含声明和表达式,可以省略 return,其返回值必须符合返回类型。

class Foo {
    mut prop a: Int64 {
        get() { // () -> Int64
            "123" // error
        }
        set(v) { // (Int64) -> Unit
            123
        }
    }
}

无论在属性内部还是外部,访问属性的行为都是一致的,因此属性递归访问时与函数一样可能会造成死循环。

class Foo {
    prop i: Int64 {
        get() {
            i // dead loop
        }
    }
}

需要注意的是,struct 的 setter 是 mut 函数,因此也可以在 setter 内部修改其它字段的值,并且 this 会受到 mut 函数的限制。

属性的修饰符

属性跟函数一样可以使用修饰符修饰,但只允许对整个属性修饰,不能对 getter 或 setter 独立修饰。

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

属性可以使用访问控制修饰符有 privateprotectedpublic

class Foo {
    private prop a: Int64 { // ok
        get() { 0 }
    }
    protected prop b: Int64 { // ok
        get() { 0 }
    }
    public static prop c: Int64 { // ok
        get() { 0 }
    }
}

实例属性像实例函数一样,可以使用 openoverride 修饰。

使用 open 修饰的属性,子类型可以使用 override 覆盖父类型的实现(override 是可选的)。

open class A {
    public open mut prop i: Int64 {
        get() { 0 }
        set(v) {}
    }
}
class B <: A {
    override mut prop i: Int64 {
        get() { 1 }
        set(v) {}
    }
}

静态属性像静态函数一样,可以使用 redef 修饰(redef 是可选的),子类型可以重新实现父类型的静态属性。

open class A {
    static mut prop i: Int64 {
        get() { 0 }
        set(v) {}
    }
}
class B <: A {
    redef static mut prop i: Int64 {
        get() { 1 }
        set(v) {}
    }
}

子类型 override/redef 使用 let 声明的实例属性必须要重新实现 getter。

子类型 override/redef 父类型中使用 mut 修饰符声明的的属性时,允许只重新实现 getter 或 setter,但不能均不重新实现。

open class A {
    public open mut prop i1: Int64 {
        get() { 0 }
        set(v) {}
    }
    static mut prop i2: Int64 {
        get() { 0 }
        set(v) {}
    }
}

// case 1
class B <: A {
    public override mut prop i1: Int64 {
        get() { 1 } // ok
    }
    redef static mut prop i2: Int64 {
        get() { 1 } // ok
    }
}

// case 2
class B <: A {
    public override mut prop i1: Int64 {
        set(v) {} // ok
    }
    redef static mut prop i2: Int64 {
        set(v) {} // ok
    }
}

// case 3
class B <: A {
    override mut prop i1: Int64 {} // error
    redef static mut prop i2: Int64 {} // error
}

子类型的属性override/redef必须与父类保持相同的mut修饰符,并且还必须保持相同的类型。

class P {}
class S {}

open class A {
    open prop i1: P {
        get() { P() }
    }
    static prop i2: P {
        get() { P() }
    }
}

// case 1
class B <: A {
    override mut prop i1: P { // error
        set(v) {}
    }
    redef static mut prop i2: P { // error
        set(v) {}
    }
}
// case 2
class B <: A {
    override prop i1: S { // error
        get() { S() }
    }
    redef static prop i2: S { // error
        get() { S() }
    }
}

子类 override 父类的属性时,可以使用 super 调用父类的实例属性。

open class A {
    open prop v: Int64 {
        get() { 1 }
    }
}
class B <: A {
    override prop v: Int64 {
        get() { super.v + 1 }
    }
}