mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
85 lines
4.8 KiB
ReStructuredText
85 lines
4.8 KiB
ReStructuredText
.. @raise litre.TestsAreMissing
|
|
|
|
Logical Objects
|
|
===============
|
|
|
|
Universal across programming languages, we have this concept of a value, which
|
|
is just some amount of fixed data. A value might be the int 5, or a pair of the
|
|
bool true and the int -20, or an NSRect with the component values (0, 0, 400,
|
|
600), or whatever.
|
|
|
|
In imperative languages we have this concept of an object. It's an
|
|
unfortunately overloaded term; here I'm using it like the standards do, which is
|
|
to say that it's a thing that holds a value, but which can be altered at any
|
|
time to hold a different value. It's tempting to use the word variable instead,
|
|
and a variable is indeed an object, but "variable" implies all this extra stuff,
|
|
like being its own independent, self-contained thing, whereas we want a word
|
|
that also covers fields and array elements and what-have-you. So let's just
|
|
suck it up and go with "object".
|
|
|
|
You might also call these "r-value" and "l-value". These have their own
|
|
connotations that I don't want to get into. Stick with "value" and "object".
|
|
|
|
In C and C++, every object is physical. It's actually a place in memory
|
|
somewhere. It's not necessarily easily addressable (because of bitfields), and
|
|
its lifetime may be tightly constrained (because of temporaries or
|
|
deallocation), but it's always memory.
|
|
|
|
In Objective-C, properties and subscripting add an idea of a logical object.
|
|
The only way you can manipulate it is by calling a function (with unrestricted
|
|
extra arguments) to either fetch the current value or update it with a new
|
|
value. The logical object doesn't promise to behave like a physical object,
|
|
either: for example, you can set it, then immediately get it, and the result
|
|
might not match the value you set.
|
|
|
|
Swift has logical objects as well. We have them in a few new places (global
|
|
objects can be logical), and sometimes we treat objects that are really physical
|
|
as logical (because resilience prevents us from assuming physicality), and we're
|
|
considering making some restrictions on how different a logical object can be
|
|
from a physical object (although set-and-get would still be opaque), but
|
|
otherwise they're pretty much just like they are in Objective-C.
|
|
|
|
That said, they do interact with some other language features in interesting
|
|
ways.
|
|
|
|
For example, methods on value types have a this parameter. Usually parameters
|
|
are values, but this is actually an object: if I call a method on an object, and
|
|
the method modifies the value of this, I expect it to modify the object I called
|
|
the method on. This is the high-level perspective of what [inout] really means:
|
|
that what we're really passing as a parameter is an object, not a value. With
|
|
one exception, everything that follows applies to any sort of [inout] parameter,
|
|
not just this on value types. More on that exception later.
|
|
|
|
How do you actually pass an object, though, given that even physical objects
|
|
might not be addressable, but especially given that an object might be logical?
|
|
|
|
Well, we can always treat a physical object like a logical object. It's
|
|
possible to come up with ways to implement passing a logical object (pass a
|
|
pointer to a struct, the first value of which is a getter, the second value of
|
|
which is a setter, and the rest of which is opaque to the callee; the struct
|
|
must be passed to the getter and setter functions). Unfortunately, the
|
|
performance implications would be terrible: accessing multiple fields would
|
|
involve multiple calls to the getter, each returning tons of extra information.
|
|
And getter and setter calls might be very expensive.
|
|
|
|
We could pass a hybrid format, using direct accesses when possible and a
|
|
getter/setter when not. Unfortunately, that's a lot of code bloat in every
|
|
single method implementation.
|
|
|
|
Or we can always pass a physical, addressable object. This avoids penalizing
|
|
the fast case where the object is really physical, which is great. For the case
|
|
where the object is logical, we just have to make it physical somehow. That
|
|
means materialization: calling the getter, storing the result into temporary
|
|
memory, passing the temporary, and then calling the setter with the new value in
|
|
the temporary when the method call is done. This last step is called writeback.
|
|
|
|
(About that one exception to this all applying equally to [inout]: in addition
|
|
to all this stuff about calling methods on different kinds of object, we also
|
|
want to support calling a method on a value. This is also implemented with a
|
|
form of materialization, which looks just like the logical-object kind except
|
|
without writeback, because there's nothing to write back to. This is a special
|
|
rule that only applies to passing this, because we assume that most types will
|
|
have lots of useful methods that don't involve writing to this, whereas we
|
|
assume that a function with an explicit [inout] parameter is almost certain to
|
|
want to write to it.)
|