考虑以下代码:
class Foo {
let bar = "Hello world!"
init () {
self // Warning: Expression of type 'Foo' is unused
self.bar // Error: Expression resolves to an unused l-value
}
func test () {
self.bar // Warning: Expression of type 'String' is unused
}
}
为什么消息不同,为什么只有一个错误?我理解它们的含义,但我不明白为什么编译器以不同的方式处理它们。
答案 0 :(得分:2)
自初始化期间,编译器在初始化器中将self.bar
视为l值的原因即使您已声明,也可以在初始化期间的任何时候将值分配给常量属性将其作为常量,因此编译器会将其视为错误,因为有机会修改常量...
self.bar
函数中的test()
被视为r值,因为该属性被声明为常量,并且编译器知道您无法对其进行修改,因此不会产生任何错误,并且将被视为r值,未使用返回值。
如果您需要更好的理解,请尝试将属性更改为变量而不是常量,并且您会发现,即使self.bar
方法中的test()
语句也会出错,因为编译器会猜测此值也可以是左值。
class Foo {
var bar = "Hello world!"
init () {
self // Warning: Expression of type 'Foo' is unused
self.bar // Error: Expression resolves to an unused l-value
}
func test () {
self.bar // Changed to : Error: Expression resolves to an unused l-value
}
}
答案 1 :(得分:0)
诊断方面的差异是由于编译器将表达式视为左值还是右值而导致的。这些是用户不需要知道的内部编译器术语,但实际上,左值主要用于表示可变表达式-即可以使用=
或传递的{{ 1}}。左值由组件组成,例如表达式inout
可以表示为具有变量基数a.b[]
,成员访问组件a
和下标组件.b
的左值。>
可以左加载左值以生成右值,该右值通常称为值,并且是不可变的。例如,进行函数调用[]
将需要加载左值foo(a.b[])
才能作为参数传递。
由于要编译器执行加载(这可能会引发副作用,例如调用变量getter),或者是否要存储,所以模棱两可的事实会导致未使用的左值错误一个值,但尚未写出a.b[]
(这不会称为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
(请注意,#14227最近对此诊断进行了改进)
尽管 class Foo {
let bar = "Hello world!"
init () {
self.bar // Error: Expression resolves to an unused l-value
}
}
是不可变的,但仍被视为左值。还记得上面的内容,当我说过一个lvalue主要是用来表示可变表达式的吗?嗯,事实证明,它们还可以用于表示适合左值模型的不可变表达式(即可以使用左值分量建模的表达式,例如self.bar
)。
那为什么self.bar
到底被当作左值呢?结果是it is in order to avoid triggering a load of the base expression self.bar
,将被视为对self
的读取,因此使以下代码无效:
self
由于以下事实:struct S {
let x: Int = 1
let y: Int
init() {
self.y = self.x
}
}
如果被视为右值,则会在完全初始化之前执行self.x
的加载。
通过被视为左值,DefiniteInitialization(诊断无效初始化的编译器遍历)能够识别出self
是对已初始化属性的读取,因此允许代码进行编译。但是,话虽如此,DefiniteInitialization不能真正识别出self.x
的加载方式以及随后对已初始化属性的成员访问之后,才没有真正的原因。最终,self
在此处被视为左值是实现细节。