As the title suggests, I don't understand why it's so difficult to implement, but my understanding is that the compiler only needs to add the rule to disable the setter on “lazy let” for developers and the rest is just consistent with “lazy var”.
One of the things that's somewhat implied by a var
of a struct is the concept that a mutation of part of a struct is considered a mutation of the entire value. Ergo, a lazy let
mutating would cause didSet
s to execute where the struct is stored.
Consider:
struct MyStruct {
lazy let a = Something()
}
Passing that to a function may cause a
to be realized, which would be considered a mutation. As such, all uses of a lazy
let would be considered mutating
and need to be marked as such. This struct would need to be passed inout
still.
I could see it for classes though. Arguably, this should be done in a property wrapper nowadays instead of a special modifier applied to the property.
I'm open to any way to implement it, lazy and @lazy are fine, but given the consistency it's definitely better to be lazy.
As the topic says, lazy let should be implemented the same as lazy var, so the restrictions on sturct should be the same and Since developers are prohibited from using setters, willSet and didSet should also be prohibited.
This is very closely related to the recent discussion about weak let
.
Swift has evolved such that let
and var
really refer to ownership, not mutability. A let
variable/property only exposes operations that can be done with shared ownership. A var
variable/property can also expose operations which require exclusive ownership. Since a lazy
variable mutates its storage on first access, it must be a var
.
Atomics are the exception that proves this rule: they must be declared let
, but they support mutating operations. This is because a mutating atomic operation is defined to not require exclusive ownership. Shared mutability is the whole point of atomics, after all. But it does mean if your type contains a public atomic property, you can no longer rely on the law of exclusivity to protect that value’s in-memory representation.
lazy
is not currently thread-safe, but a hypothetical thread-safe version of lazy
could theoretically be declared let
. But that would significantly expand the aforementioned hole in the law of exclusivity. The Language Workgroup argued that anyone writing an atomic variable is opting into the paradox of mutability under non-exclusive ownership. I doubt this would be considered true of lazy let
.
Hi @ksluder, you're missing my point, which was: why can't a lazy let be equivalent to a lazy var that disables setters?
One difference between ‘weak let’ and a hypothetical ‘lazy let’ is that the latter requires mutable ownership over the value to read the property while the former does not. So this might be more confusing to teach, for sure.
At the implementation level, we could make ‘lazy let’ work easily enough; it would still desugar to a computed property with a mutable getter, and it would continue to behave as one, so nothing would violate the law of exclusivity.
However, in my opinion, this use case would be better serviced by macros, instead of generalizing either lazy properties or property wrappers.
I wonder why the OP needs a let
in the first place. If the complier allows lazy let
the spelling, but accessing the property is still considered mutating, then what does this syntax unlock? Would this property be accepted in any call site that wouldn't have been accepted if it was a var
? Perhaps OP can provide some examples to motivate this change.
I directly addressed that question, but I’ll restate it more succinctly: Even without exposing a setters, a lazy getter modifies the containing storage on first access. This requires exclusive access to the storage, which Swift models as var
for all types except atomics.
I view the Law of Exclusivity as encompassing more than just the prohibition of more than one simultaneous exclusive access, but also what you are allowed to do with exclusive or non-exclusive accesses. Perhaps those are officially separate rules or corollaries.
Either way, it was originally not permissible to call a mutating method via non-exclusive access. (Methods on classes are never mutating.) That changed with the introduction of atomics. Permitting lazy let
would require changing it yet again.
A ‘lazy let’ would still require exclusive access to call the getter. You can think of the law of exclusivity as a statement about the immutable “platonic ideal” world of language semantics, whereas the ‘let’ keyword is just sugar that we can interpret in any way we want (as long as it’s self-consistent at the very least, but hopefully also easy to explain).
My understanding is that a lazy let is more of a way to tell the developer not to modify the variable under any circumstances, and that it is up to the compiler to figure out how to implement it.
The effect of a lazy let on the developer should be the same as a lazy var that disables (or hides) the setter. Currently we can do this with a private(set) lazy var, but that doesn't disable modifying the variable in the type it's in.
So to get back to my question, why can't we make a lazy let equivalent to a lazy var with the setter disabled at the compiler level?
I realized that weak let is in swift-evolution, does that mean that lazy let is also a possible thing?
Atomic operations are not formally mutating, either. They behave like methods on classes.
Or do you mean something else?
Related Q: is there any reason why the current lazy
keyword couldn't be re-implemented as a property wrapper or macro?
I think it's because none of them support capturing themselves in the initial stages now. There would be more work needed to implement lazy with them?