Swift &引用;表达式解析为未使用的l值;vs";“表达式未使用”;

Swift &引用;表达式解析为未使用的l值;vs";“表达式未使用”;,swift,compiler-errors,lvalue,Swift,Compiler Errors,Lvalue,考虑以下代码: 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: Expre

考虑以下代码:

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
    }
}

为什么消息不同,为什么只有一条消息是错误的?我明白他们的意思,我只是不明白为什么编译器会以不同的方式处理它们。

编译器将
self.bar
视为初始值设定项中的l值的原因,因为在初始化过程中您可以在初始化过程中的任何时候为常量属性赋值,即使您已将其声明为常量,因此编译器会将其视为错误,因为有可能修改常量

test()
函数中的
self.bar
被视为r值,因为属性声明为常量,编译器知道您无法修改它,因此不会生成错误,它将被视为r值,返回值未使用

如果您需要更好的理解,请尝试将属性更改为变量而不是常量,您会发现,即使是
test()
方法中的
self.bar
语句也会出错,因为编译器猜测该值也可能是左值

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
    }
}

诊断上的差异是由于编译器将表达式视为左值还是右值的差异造成的。这些是用户不需要知道的内部编译器术语,但本质上,左值主要用于表示可变表达式–也就是说,可以使用
=
分配或传递
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
是一个已初始化的常量


self
在这里被视为右值,因为
self
在类声明体中是不可变的


好的,现在来看一个有趣的问题:

class Foo {
  let bar = "Hello world!"
  init () {
    self.bar // Error: Expression resolves to an unused l-value
  }
}
(请注意,该诊断最近通过以下方式得到了改进)

self.bar
此处被视为左值,尽管它是不可变的。还记得上面我说过左值主要用于表示可变表达式吗?事实证明,它们还可以用来表示适合左值模型的不可变表达式(也就是说,可以使用左值组件建模的表达式,例如
self.bar

那么,为什么在这里,
self.bar
被视为左值呢?结果是
self
,这将被视为读取
self
,因此导致以下代码无效:

struct S { 
  let x: Int = 1
  let y: Int

  init() {
    self.y = self.x 
  } 
}
由于
self.x
将在完全初始化之前执行
self
加载,如果将其视为右值


通过将DefiniteInitialization(诊断无效初始化的编译器过程)视为左值,DefiniteInitialization能够识别
self.x
是对已初始化属性的读取,因此允许对代码进行编译。但也就是说,DefiniteInitialization没有真正的理由不能识别
self
被加载,然后成员访问已经初始化的属性的模式。最后,
self.x
在这里被视为左值是一个实现细节。

@Hamish关于你最后的评论,这不是只有在它还没有被赋值的情况下才会发生吗?@Hamish如果我读对了,这只是编译器在检查l值时不检查已初始化的
let
s的一个限制?当我将其更改为
var
时,发生了什么,您是对的,我没有考虑到这一点,这在某种程度上解释了这一点。但是,当我尝试在初始值设定项中将其指定为
let
时,我得到“不可变值'self.bar'可能只初始化一次”,因此该部分没有意义,除非出于某种原因,编译器没有发现它在我的原始情况下已经初始化。是的,你是对的,我想编译器可能会先检查l值,然后再检查存储的属性是否已初始化或已初始化为初始值!也许当一切正常时,在完成初始化之前,编译器想要检查是否设置了所有存储的属性,以便在这里捕获属性已经被分配了初始值。。。
struct S { 
  let x: Int = 1
  let y: Int

  init() {
    self.y = self.x 
  } 
}