诊断的差异是由于编译器将表达式视为左值还是右值的差异。这些是用户不需要知道的内部编译器术语,但本质上,左值主要用于表示可变表达式——即可以使用= 分配给或传递inout 的表达式。左值由组件组成,例如表达式a.b[] 可以表示为具有变量基a、成员访问组件.b 和下标组件[] 的左值。
一个左值可以被加载来产生一个右值,它通常被称为一个值,并且是不可变的。例如,进行函数调用 foo(a.b[]) 将需要加载左值 a.b[] 以便作为参数传递。
由于您是否希望编译器执行加载(这可能触发副作用,例如对变量 getter 的调用)或您是否打算存储一个值,但尚未写入 = ...(不会调用 getter)。您只会收到未使用的右值的警告,因为它不是模棱两可的。
好的,让我们来谈谈你提出的每个例子:
class Foo {
let bar = "Hello world!"
func test () {
self.bar // Warning: Expression of type 'String' is unused
}
}
self.bar 在这里被视为右值,因为它是一个不可变的表达式,因为属性 bar 是一个已经初始化的常量。
class Foo {
init () {
self // Warning: Expression of type 'Foo' is unused
}
}
self 在这里被视为右值,因为self 在类声明的主体中是不可变的。
好的,现在进入有趣的:
class Foo {
let bar = "Hello world!"
init () {
self.bar // Error: Expression resolves to an unused l-value
}
}
(请注意,最近#14227 改进了此诊断)
self.bar 在这里被视为左值,尽管它是不可变的。还记得上面我说过左值主要用于表示可变表达式吗?好吧,事实证明,它们也可以用来表示适合左值模型的不可变表达式(即可以使用左值组件建模的表达式,例如self.bar)。
那么为什么 self.bar 在这里被视为左值呢?结果是it is in order to avoid triggering a load of the base expressionself,这将被视为对self的读取,因此使以下代码无效:
struct S {
let x: Int = 1
let y: Int
init() {
self.y = self.x
}
}
由于self.x 将在完全初始化之前执行self 的加载,如果它被视为右值。
通过被视为左值,DefiniteInitialization(诊断无效初始化的编译器传递)能够识别self.x 是对已初始化属性的读取,因此允许代码编译。但是话虽这么说,但没有真正的原因 DefiniteInitialization 无法识别 self 被加载的模式,然后是对已初始化属性的成员访问。最终,self.x 在这里被视为左值是一个实现细节。