If so, I don't remember the syntax.
The mutating
keyword is just for methods, right? A mutable property is just a var
instead of a let
.
One gets the sense that you’re just waiting for someone to ask what it would mean for a stored property to be marked as mutating. Isn’t every var
stored property on a struct inherently mutating set?
Yes. I'm talking about get
.
struct 🏛️ {
var property: Void {
mutating get { }
}
}
var instance = 🏛️() {
didSet { print("🏺") }
}
instance.property // 🏺
Ah, ok. That's not "stored property" btw.
I’m not sure what a property of Void type is but maybe you mean a property that’s a function
struct Pillars {
var property: () -> ()
}
func test() {
let p = Pillars() {
print("🤷🏻♀️")
}
p.property()
}
I know. That is why I asked the question. I already knew how to write a computed property with a mutating get
. The verbose form someone might actually need looks like
private var _property: Property
var property: Property {
mutating get { _property }
}
What does it mean for a stored property to have a mutating get
?
mutating get
does not imply that the get
mutates the thing being got, but instead that getting it mutates the entity that defined the property. This makes it salient to have a mutating get
on a computed property: there is code there that can mutate the container.
For a stored property, no code executes in get
besides the copy constructor, which does not mutate its container. Definitionally then, all stored properties have nonmutating get
s.
struct 🏛️ {
lazy var print🏺Once: Void = print("🏺")
}
var structure = 🏛️()
structure.print🏺Once // "🏺"
🏛️().print🏺Once // Cannot use mutating getter on immutable value: function call returns immutable value
Yup. I'm going to make a fine distinction here, but I think this example does more to point out that lazy
is a weird wart in the programming language than to undermine that argument.
Firstly, as noted in the introductory proposal for property wrappers, lazy
amounts to a compiler special-case for the functionality now managed by property wrappers. That proposal explicitly calls out that lazy
is isomorphic to a stored property and a computed property that wraps it, and provides an equivalent property wrapper for the feature.
So I think this sticks with my argument: lazy
is a special-case that is as-if there was a stored property and a computed property. The stored property itself has a nonmutating get, but the computed one has a mutating get.
There's nothing in the language which restricts us to making sure mutation is locally meaningful, e.g.
struct PropertyObserverTriggerer {
mutating func triggerThem() { }
}
Whatever other meaning we may use as convention, "let
" basically just represents a subset of the functionality of "var
". So I find it interesting that we're restricted from closing off get
access, the way we can with set
and subscripts/methods.
struct SupertypeOfLetAndVar {
var varOnlyProperty: String {
mutating get { _varOnlyProperty }
set { _varOnlyProperty = newValue }
}
private var _varOnlyProperty = ""
}
If only we could omit @
before property wrappers on the use site. Then one day we could silently introduce "lazy" wrapper, remove "lazy" support from the language and users won't notice anything happened!
You have this special talent.. Maybe that's just me, but a dozen messages down the thread I still don't understand where you are getting at. This?
struct S {
mutating func modify() {...}
mutating var hohoho: String {
// getter, where you can mutate
modify()
return "I just mutated self"
// the stored "hohoho" values is not used.
// alternatively "return value" where you use a spacial
// `value` to return the stored value
// similar to how we use `oldValue` / `newValue`
}
// setter for "hohoho" is normal.
// S.hohoho = "hello"
// S.hohoho // mutates
}
Story of my life! Certainly not just you. I'm not easy to deal with but at least I understand that, and still love you all anyway.
…But regarding your example, not quite. If there's any computation about it, we can write that already. I was asking if we could avoid the computed+stored combination.
struct S {
private let _c = C()
var c: C {
mutating get { _c }
}
final class C {
var hohoho = "🎅"
}
}
var s = S() {
didSet {
print("Obzurvabobble Object! 😵💫")
print(s.c.hohoho)
}
}
s.c.hohoho = "🦌"
Restrictions on stored properties don’t ever close off capabilities from the enclosing type itself. You get a mutating setter and a non-mutating getter because that’s what’s necessary for a var
; if you want to customize that, you do have to separate the “field” from the “property” and use two declarations.
“Wait, doesn’t let
work like a property with no setter at all?” Kinda! In my head the additional promise of “it won’t change for the lifetime of the object” makes it more than just a property with no setter, but of course that does fall out from being a stored property with no setter. (Though I can’t remember if public let
in a frozen class, an unsupported feature, also becomes an optimization promise today…) But even then, you could say let
and var
as stored properties both expose the minimum requirements they need to get the job done; after all, a maximally conservative getter now isn’t just mutating
but throws
as well.
Good point.
struct S {
var property: Void {
mutating get throws { try _property.get() }
}
private lazy var _property = Result { try Self.property }
private static var property: Void {
get throws { }
}
}