注解

仓颉中提供了一些属性宏用来支持一些特殊情况的处理。

确保正确使用整数运算溢出策略的注解

仓颉中提供三种属性宏来控制整数溢出的处理策略,即 @OverflowThrowing@OverflowWrapping@OverflowSaturating ,这些属性宏当前只能标记于函数声明之上,作用于函数内的整数运算和整型转换。它们分别对应以下三种溢出处理策略:

(1) 抛出异常(throwing):当整数运算溢出时,抛出异常。

@OverflowThrowing
main() {
    let res: Int8 = Int8(100) + Int8(29)
    /* 100 + 29 在数学上等于 129,
     * 在 Int8 的表示范围上发生了上溢出,
     * 程序抛出异常
     */
    let con: UInt8 = UInt8(-132)
    /* -132 在 UInt8 的表示范围上发生了下溢出,
     * 程序抛出异常
     */
    0
}

(2) 高位截断(wrapping):当整数运算的结果超出用于接收它的内存空间所能表示的数据范围时,则截断超出该内存空间的部分。

@OverflowWrapping
main() {
    let res: Int8 = Int8(105) * Int8(4)
    /* 105 * 4 在数学上等于 420,
     * 对应的二进制为 1 1010 0100,
     * 超过了用于接收该结果的 8 位内存空间,
     * 截断后的结果在二进制上表示为 1010 0100,
     * 对应为有符号整数 -92
     */
    let temp: Int16 = Int16(-132)
    let con: UInt8 = UInt8(temp)
    /* -132 对应的二进制为 1111 1111 0111 1100,
     * 超过了用于接收该结果的 8 位内存空间,
     * 截断后的结果在二进制上表示为 0111 1100
     * 对应为有符号整数 124
     */
    0
}

(3) 饱和(saturating):当整数运算溢出时,选择对应固定精度的极值作为结果。

@OverflowSaturating
main() {
    let res: Int8 = Int8(-100) - Int8(45)
    /* -100 - 45 在数学上等于 -145,
     * 在 Int8 的表示范围上发生了下溢出,
     * 选择 Int8 的最小值 -128 作为结果
     */
    let con: Int8 = Int8(1024)
    /* 1024 在 Int8 的表示范围上发生了上溢出,
     * 选择 Int8 的最大值 127 作为结果
     */
    0
}

默认情况下(即未标注该类属性宏时),采取抛出异常(@OverflowThrowing)的处理策略。

实际情况下需要根据业务场景的需求正确选择溢出策略。例如要在 Int32 上实现某种安全运算,使得计算结果和计算过程在数学上相等,就需要使用抛出异常的策略。

【反例】

// 计算结果被高位截断
@OverflowWrapping
func operation(a: Int32, b: Int32): Int32 {
    a + b // No exception will be thrown when overflow occurs
}

该错误例子使用了高位截断的溢出策略,比如当传入的参数 ab 较大导致结果溢出时,会产生高位截断的情况,导致函数返回结果和计算表达式 a + b 在数学上不是相等关系。

【正例】

// 安全
@OverflowThrowing
func operation(a: Int32, b: Int32): Int32 {
    a + b
}

main() {
    try {
        operation(a, b)
    } catch (e: ArithmeticException) {
        //Handle error
    }
    0
}

该正确例子使用了抛出异常的溢出策略,当传入的参数 ab 较大导致整数溢出时,operation 函数会抛出异常。

下面总结了可能造成整数溢出的数学操作符。

操作符溢出操作符溢出操作符溢出操作符溢出
+Y-=Y<<N<N
-Y*=Y>>N>N
*Y/=Y&N>=N
/Y%=N|N<=N
%N<<=N^N==N
++Y>>=N**=Y
--Y&=N!N
=N|=N!=N
+=Y^=N**Y

性能优化注解

为了提升与 C 语言互操作的性能,仓颉提供属性宏 @FastNative 控制 cjnative 后端优化对于 C 函数的调用。值得注意的是,属性宏 @FastNative 只能用于 foreign 声明的函数。

@FastNative 使用限制

开发者在使用 @FastNative 修饰 foreign 函数时,应确保对应的 C 函数满足以下两点要求。

  • 首先,函数的整体执行时间不宜太长。例如:
    • 不允许函数内部存在很大的循环;
    • 不允许函数内部产生阻塞行为,如,调用 sleepwait 等函数。
  • 其次,函数内部不能调用仓颉方法。

自定义注解

自定义注解机制用来让反射(详见反射章节)获取标注内容,目的是在类型元数据之外提供更多的有用信息,以支持更复杂的逻辑。

开发者可以通过自定义类型标注 @Annotation 方式创建自己的自定义注解。@Annotation 只能修饰 class,并且不能是 abstractopensealed 修饰的 class。当一个 class 声明它标注了 @Annotation,那么它必须要提供至少一个 const init 函数,否则编译器会报错。

下面的例子定义了一个自定义注解 @Version,并用其修饰 A, BC。在 main 中,我们通过反射获取到类上的 @Version 注解信息,并将其打印出来。

package pkg

from std import reflect.TypeInfo

@Annotation
public class Version {
    let code: String
    const init(code: String) {
        this.code = code
    }
}

@Version["1.0"]
class A {}

@Version["1.1"]
class B {}

main() {
    let objects = [A(), B()]
    for (obj in objects) {
        let annOpt = TypeInfo.of(obj).findAnnotation("pkg.Version")
        if (let Some(ann) <- annOpt) {
            if (let Some(version) <- ann as Version) {
                println(version.code)
            }
        }
    }
}

编译并执行上述代码,输出结果为:

1.0
1.1

注解信息需要在编译时生成信息并绑定到类型上,自定义注解在使用时必须使用 const init 构建出合法的实例。注解声明语法与声明宏语法一致,后面的 [] 括号中需要按顺序或命名参数规则传入参数,且参数必须是 const 表达式(详见常量求值章节)。对于拥有无参构造函数的注解类型,声明时允许省略括号。

下面的例子中定义了一个拥有无参 const init 的自定义注解 @Deprecated,使用时 @Deprecated@Deprecated[] 这两种写法均可。

package pkg

from std import reflect.TypeInfo

@Annotation
public class Deprecated {
    const init() {}
}

@Deprecated
class A {}

@Deprecated[]
class B {}

main() {
    if (TypeInfo.of(A()).findAnnotation("pkg.Deprecated").isSome()) {
        println("A is deprecated")
    }
    if (TypeInfo.of(B()).findAnnotation("pkg.Deprecated").isSome()) {
        println("B is deprecated")
    }
}

编译并执行上述代码,输出结果为:

A is deprecated
B is deprecated

对于同一个注解目标,同一个注解类不允许声明多次,即不可重复。

@Deprecated
@Deprecated // error
class A {}

Annotation 不会被继承,因此一个类型的注解元数据只会来自它定义时声明的注解。如果需要父类型的注解元数据信息,需要开发者自己用反射接口查询。

下面的例子中,A@Deprecated 注解修饰,B 继承 A,但是 B 没有 A 的注解。

package pkg

from std import reflect.TypeInfo

@Annotation
public class Deprecated {
    const init() {}
}

@Deprecated
open class A {}

class B <: A {}

main() {
    if (TypeInfo.of(A()).findAnnotation("pkg.Deprecated").isSome()) {
        println("A is deprecated")
    }
    if (TypeInfo.of(B()).findAnnotation("pkg.Deprecated").isSome()) {
        println("B is deprecated")
    }
}

编译并执行上述代码,输出结果为:

A is deprecated

自定义注解可以用在类型声明(classstructenuminterface)、成员函数/构造函数中的参数、构造函数声明、成员函数声明、成员变量声明、成员属性声明。也可以限制自己可以使用的位置,这样可以减少开发者的误用,这类注解需要在声明 @Annotation 时标注 target 参数,参数类型为 Array<AnnotationKind>。其中,AnnotationKind 是标准库中定义的 enum。当没有限定 target 的时候,该自定义注解可以用在以上全部位置。当限定 target 时,只能用在声明的列表中。

public enum AnnotaitionKind {
    | Type
    | Parameter
    | Init
    | MemberProperty
    | MemberFunction
    | MemberVariable
}

下面的例子中,自定义注解通过 target 限定只能用在成员函数上,用在其他位置会编译报错。

@Annotation[target: [MemberFunction]]
public class Deprecated {
    const init() {}
}

class A {
    @Deprecated // ok, member funciton
    func deprecated() {}
}

@Deprecated // error, type
class B {}

main() {}