Dear internals,
I'd like to discuss the concept of ownership in PHP, from the point of
view of a new interface UniqueInterface (or SplUniqueInterface or
something), which will throw a UniqueException if refcount > 1.
Use-case: Mutable objects that cannot be shared (without (deep) cloning).
Rational: Immutable objects are considered "easier to reason about"
and generally more safe, and as a way to avoid
spooky-action-at-a-distance. I argue that mutability is totally fine
as long as it's not being shared. Currently, there's no way to
enforce this in PHP, but PHP has a refcounting GC which could do
this. This would be a more performant alternative than to build lots
of immutable objects using $obj->whereSomething('bla') constructs.
Cons: If PHP switches to tracing GC, it will be unusable.
What do you think? Should I write it down as an RFC? Implementation
should be straight-forward, assuming refcount is manipulated at a
single place in the source.
Regards
Olle
Dear internals,
I'd like to discuss the concept of ownership in PHP, from the point of
view of a new interface UniqueInterface (or SplUniqueInterface or
something), which will throw a UniqueException if refcount > 1.Use-case: Mutable objects that cannot be shared (without (deep) cloning).
I don't think you want to do this on refcount, since that would
prevent even passing it to another function or using it in any number
of expressions. What would make more sense to me would be hooking
into copy-on-write, which would also be a slightly less hot code path
than reference counting (but still a pretty critical one).
It almost sounds like a uniqueness type, something well beyond the
capabilities of PHP's type system (and most others).
--c
Hi Olle Härstedt,
I'd like to discuss the concept of ownership in PHP, from the point of
view of a new interface UniqueInterface (or SplUniqueInterface or
something), which will throw a UniqueException if refcount > 1.
It'd seem like it would cause a lot of places where UniqueException could be thrown unexpectedly.
Additionally, inspecting the stack trace of an exception would also increment the refcount,
causing yet another exception.
<?php
class UniqueObject{}
function test(UniqueObject $x) {
// ...
// handle some edge case
// $trace includes an array with a reference to $x at $trace[0]['args'][0],
// increasing the refcount to 2, throwing.
//
// Error handlers and issue handlers would also have this issues,
// as well as Throwable->getTrace()
$trace = `debug_backtrace()`;
var_dump($trace);
}
test(new UniqueObject());
Implementation should be straight-forward, assuming refcount is manipulated at a
single place in the source.
This would also introduce some performance overhead if every function that incremented the reference count of a value
now had to check if objects implemented that interface. I'd assume it would be significant.
This is something that makes more sense in compiled languages like rust that can enforce uniqueness at compile time,
and the dynamic nature of php makes that less practical.
- Tyson
On Sat, Jul 11, 2020 at 5:32 PM tyson andre tysonandre775@hotmail.com
wrote:
This is something that makes more sense in compiled languages like rust
that can enforce uniqueness at compile time,
and the dynamic nature of php makes that less practical.
Also, crucially, Rust has language features that require a programmer to
specify whether each variable is mutable or not. Without it, Rust's borrow
checker wouldn't be possible. PHP has nothing like it, and introducing it
at this stage would be prohibitively hard.
--
Best regards,
Max Semenik
Thanks for your answers!
Yes, it would be a dynamic uniqueness system, kind of.
Another more flexible alternative would be to add magic methods at
refcount = 1 and refcount > 1, possibly __owned() and __shared(int
$refcount), for classes that implement a TrackReferencesInterface or
similar. But this would need to do ~0 performance hit if not used,
don't know if possible.
Olle
2020-07-11 14:29 GMT, Chuck Adams cja987@gmail.com:
On Sat, Jul 11, 2020 at 8:06 AM Olle Härstedt olleharstedt@gmail.com
wrote:Dear internals,
I'd like to discuss the concept of ownership in PHP, from the point of
view of a new interface UniqueInterface (or SplUniqueInterface or
something), which will throw a UniqueException if refcount > 1.Use-case: Mutable objects that cannot be shared (without (deep) cloning).
I don't think you want to do this on refcount, since that would
prevent even passing it to another function or using it in any number
of expressions. What would make more sense to me would be hooking
into copy-on-write, which would also be a slightly less hot code path
than reference counting (but still a pretty critical one).It almost sounds like a uniqueness type, something well beyond the
capabilities of PHP's type system (and most others).--c
Yes, it would be a dynamic uniqueness system, kind of.
Another more flexible alternative would be to add magic methods at
refcount = 1 and refcount > 1, possibly __owned() and __shared(int
$refcount), for classes that implement a TrackReferencesInterface or
similar. But this would need to do ~0 performance hit if not used,
don't know if possible.
Then again, php already calls __destruct if it exists when the refcount goes to 0,
so I may be overestimating the potential performance impact.
There's a lot of edge cases - probably way more than I mention here.
Running into those edge cases, you might find the feature unsatisfactory if it were to be implemented.
For example,
- The UniqueReferenceException itself creates a trace, that increments reference counts, which might itself cause a UniqueReferenceException.
Workarounds for that would probably involve allowing multiple objects to reference a UniqueInterface, and then it would be a weaker guarantee. -
unserialize()
keeps extra references to ensure that it can delete the created objects if the original references were overwritten. __wakeup() and __unserialize() - The reference count is incremented when calling __destruct(), I think.
<?php
class UniqueObject implements UniqueInterface{
public function __construct() {
debug_zval_dump($this); // refcount 3 - and then what about `$GLOBALS['something'] = $this` inside __construct
}
public function someUtilMethod() {
// object(UniqueObject)#1 (0) refcount(2){
// }
debug_zval_dump($this);
}
}
// The reference count goes to 2 in someUtilMethod because there's 2 references.
// the temporary and $this.
(new UniqueObject())->someUtilMethod();
- Tyson
2020-07-11 14:55 GMT, tyson andre tysonandre775@hotmail.com:
Yes, it would be a dynamic uniqueness system, kind of.
Another more flexible alternative would be to add magic methods at
refcount = 1 and refcount > 1, possibly __owned() and __shared(int
$refcount), for classes that implement a TrackReferencesInterface or
similar. But this would need to do ~0 performance hit if not used,
don't know if possible.Then again, php already calls __destruct if it exists when the refcount goes
to 0,
so I may be overestimating the potential performance impact.There's a lot of edge cases - probably way more than I mention here.
Running into those edge cases, you might find the feature unsatisfactory if
it were to be implemented.For example,
- The UniqueReferenceException itself creates a trace, that increments
reference counts, which might itself cause a UniqueReferenceException.
Workarounds for that would probably involve allowing multiple objects to
reference a UniqueInterface, and then it would be a weaker guarantee.unserialize()
keeps extra references to ensure that it can delete the
created objects if the original references were overwritten. __wakeup() and
__unserialize()- The reference count is incremented when calling __destruct(), I think.
<?php class UniqueObject implements UniqueInterface{ public function __construct() { debug_zval_dump($this); // refcount 3 - and then what about `$GLOBALS['something'] = $this` inside __construct } public function someUtilMethod() { // object(UniqueObject)#1 (0) refcount(2){ // } debug_zval_dump($this); } } // The reference count goes to 2 in someUtilMethod because there's 2 references. // the temporary and $this. (new UniqueObject())->someUtilMethod();
- Tyson
All good points. It's better to have a magic method that catches
changes in the refcount instead, just like __destruct but on any
change, not only at zero.
Olle
Hi Olle,
I'd like to discuss the concept of ownership in PHP, from the point of
view of a new interface UniqueInterface (or SplUniqueInterface or
something), which will throw a UniqueException if refcount > 1.
It's an intriguing idea, but I'm not sure how well it would work in practice. Maintaining a strict refcount of one means never passing the value to another function or scope, which seems very limiting.
It would also run into the usual problem PHP has that everything is determined at run-time, so every time you pass an object anywhere, the engine would need to check it wasn't marked in this way. That could be avoided if instead of a special type of value, this was a special type of variable; then it could probably be determined at compile time that you were passing a "local only" variable to another scope.
Use-case: Mutable objects that cannot be shared (without (deep)
cloning).
This is another tricky aspect - like enforcing deep immutability, it implies some recursive constraints which are easy to enforce for scalars, but tricky once you look at things like arrays, which can contain references, objects, other arrays, etc.
As described, the user would have to manually perform a clone whenever the object was passed anywhere, even if they weren't mutating it anywhere. If you let the language do that automatically, you can use an automatic copy-on-write system, like PHP already does for arrays (and, funnily enough, PHP 4 did for objects).
Alternatively, you can make the object purely immutable once it has passed out of its initial scope, or once a special function has been called to "freeze" it. This would be an interesting solution to the "modified clone" problem of full immutability (how to implement withFoo methods), since any "frozen" object could be cloned to get a mutable copy, which would be frozen before passing on.
Regards,
--
Rowan Tommins
[IMSoP]
2020-07-11 14:56 GMT, Rowan Tommins rowan.collins@gmail.com:
Hi Olle,
On 11 July 2020 15:06:13 BST, "Olle Härstedt" olleharstedt@gmail.com
wrote:I'd like to discuss the concept of ownership in PHP, from the point of
view of a new interface UniqueInterface (or SplUniqueInterface or
something), which will throw a UniqueException if refcount > 1.It's an intriguing idea, but I'm not sure how well it would work in
practice. Maintaining a strict refcount of one means never passing the value
to another function or scope, which seems very limiting.
Yes, the point is to be limiting. :) Just like with immutability, you
apply certain limitations to avoid access abuse.
It would also run into the usual problem PHP has that everything is
determined at run-time, so every time you pass an object anywhere, the
engine would need to check it wasn't marked in this way. That could be
avoided if instead of a special type of value, this was a special type of
variable; then it could probably be determined at compile time that you were
passing a "local only" variable to another scope.Use-case: Mutable objects that cannot be shared (without (deep)
cloning).This is another tricky aspect - like enforcing deep immutability, it implies
some recursive constraints which are easy to enforce for scalars, but tricky
once you look at things like arrays, which can contain references, objects,
other arrays, etc.As described, the user would have to manually perform a clone whenever the
object was passed anywhere, even if they weren't mutating it anywhere. If
you let the language do that automatically, you can use an automatic
copy-on-write system, like PHP already does for arrays (and, funnily enough,
PHP 4 did for objects).Alternatively, you can make the object purely immutable once it has passed
out of its initial scope, or once a special function has been called to
"freeze" it. This would be an interesting solution to the "modified clone"
problem of full immutability (how to implement withFoo methods), since any
"frozen" object could be cloned to get a mutable copy, which would be frozen
before passing on.Regards,
--
Rowan Tommins
[IMSoP]
Regarding "freezing" an object, I actually already wrote about this in
a blog post, using dependency injection as a use-case, and a trait to
add a method to check the refcount:
http://olleharstedt.github.io/php/2020/07/08/immutable-at-borrow.html
Yes, the point is to be limiting. :) Just like with immutability, you
apply certain limitations to avoid access abuse.
Taken to extremes, this is the "secure a server by encasing it in concrete" strategy - the hard part about designing useful limitations is not the parts you want to make impossible, it's the parts you want to remain easy.
There is a reason Rust has a whole set of mechanisms for managing ownership, not just a single "mine/yours" split, because they allow the programmer to express what they want to do with the object.
Regarding "freezing" an object, I actually already wrote about this in
a blog post, using dependency injection as a use-case, and a trait to
add a method to check the refcount:
http://olleharstedt.github.io/php/2020/07/08/immutable-at-borrow.html
The difference in my mind between freezing an object and tracking ownership is that "frozen/unfrozen" would be an explicit state set on the object, not a measurement that differs as it is used. So either the user says "freeze $object", and it is then immutable for the rest of the program, or some point after construction implicitly freezes it with the same effect.
This eliminates the dependence on tracking ownership or giving meaning to the refcount, because no matter how you received the object, you must no longer cause it to be mutated.
It also has much lower performance impact. Even the automatic form can probably be performed by the compiler - e.g. if "new" was called in this function, insert a call to "freeze" before the first time the object appears as a function parameter
Regards,
--
Rowan Tommins
[IMSoP]
2020-07-11 16:50 GMT, Rowan Tommins rowan.collins@gmail.com:
On 11 July 2020 16:13:24 BST, "Olle Härstedt" olleharstedt@gmail.com
wrote:Yes, the point is to be limiting. :) Just like with immutability, you
apply certain limitations to avoid access abuse.Taken to extremes, this is the "secure a server by encasing it in concrete"
strategy - the hard part about designing useful limitations is not the parts
you want to make impossible, it's the parts you want to remain easy.There is a reason Rust has a whole set of mechanisms for managing ownership,
not just a single "mine/yours" split, because they allow the programmer to
express what they want to do with the object.Regarding "freezing" an object, I actually already wrote about this in
a blog post, using dependency injection as a use-case, and a trait to
add a method to check the refcount:
http://olleharstedt.github.io/php/2020/07/08/immutable-at-borrow.htmlThe difference in my mind between freezing an object and tracking ownership
is that "frozen/unfrozen" would be an explicit state set on the object, not
a measurement that differs as it is used. So either the user says "freeze
$object", and it is then immutable for the rest of the program, or some
point after construction implicitly freezes it with the same effect.This eliminates the dependence on tracking ownership or giving meaning to
the refcount, because no matter how you received the object, you must no
longer cause it to be mutated.It also has much lower performance impact. Even the automatic form can
probably be performed by the compiler - e.g. if "new" was called in this
function, insert a call to "freeze" before the first time the object appears
as a function parameterRegards,
--
Rowan Tommins
[IMSoP]--
To unsubscribe, visit: https://www.php.net/unsub.php
OK, but then there's no need for any compiler changes - just make
trait CanBeFrozenTrait, add a property $isFrozen : bool, and then make
sure all methods check this property and throw an exception if it's
violated.
(and, funnily enough, PHP 4 did for objects).
The funny thing about it for me is that this behavior caused me to
throw such a fit when I discovered it -- more to say burned by it --
that I swore off PHP forever at the time. Now I actually want this
behavior back, though as an opt-in. Maybe that would address some of
the intent of this proposal. It'd be easier to know with some solid
examples.
--c
Dear internals,
I'd like to discuss the concept of ownership in PHP, from the point of
view of a new interface UniqueInterface (or SplUniqueInterface or
something), which will throw a UniqueException if refcount > 1.Use-case: Mutable objects that cannot be shared (without (deep) cloning).
Rational: Immutable objects are considered "easier to reason about"
and generally more safe, and as a way to avoid
spooky-action-at-a-distance. I argue that mutability is totally fine
as long as it's not being shared. Currently, there's no way to
enforce this in PHP, but PHP has a refcounting GC which could do
this. This would be a more performant alternative than to build lots
of immutable objects using $obj->whereSomething('bla') constructs.Cons: If PHP switches to tracing GC, it will be unusable.
What do you think? Should I write it down as an RFC? Implementation
should be straight-forward, assuming refcount is manipulated at a
single place in the source.Regards
Olle
I like the goal of addressing shared mutable state is a laudable one and I think most here share it. However, I think uniqueness checking is the wrong approach, for reasons others have already explained.
You're correct that the thing to avoid is shared mutable state. That can be avoided either by getting rid of mutable state or shared state. Either would work. (Channeling Kevlin Henny here.)
In the case of PHP, since we don't have threads to worry about the main source of shared state is reference variables, or refrence-esque objects. (Or globals, but I'm assuming most here know to avoid those already.) That is, mutability or shared-ness within a function is, meh, who cares. It's at function boundaries that we care.
As others noted, a ref count check wouldn't help with that much, because the act of passing an object to another function necessarily increases the refcount, thus eliminating the main benefit.
Rather, the way to eliminate shared-ness is to have "structs," that is, objects that pass by value-ish rather than pass-by-reference-ish. (Yes it's more complicated than that, I know, I'm talking about the user-space implications.) PHP 4 objects all worked that way, which was a problem for anything with services our resources behind it. That's why it changed in PHP 5 to the more common by-ref-ish behavior, which... is a problem for value objects.
Both use cases are valid. It's why newer languages like Go and Rust take a completely different approach to what "object" even means in the first place.
It's been discussed before but never really implemented; I do think the way to help break the "shared state" side is to allow objects to get flagged as "will use by-value passing" or "will use by-reference passing" (again, implications, not implementation). That is, if you have a by-val object and pass it to a function, it behaves the same way as passing a string or an array: Copy-on-write of the object when any changes are made to properties of the object. Probably we'd also need to require that properties of by-val objects cannot be by-ref objects or resources, which seems reasonable to me.
I think that would do a far better job of addressing the shared-mutable-state issue than reference counting, because it attacks the root problem rather than a symptom.
--Larry Garfield
2020-07-11 19:08 GMT, Larry Garfield larry@garfieldtech.com:
Dear internals,
I'd like to discuss the concept of ownership in PHP, from the point of
view of a new interface UniqueInterface (or SplUniqueInterface or
something), which will throw a UniqueException if refcount > 1.Use-case: Mutable objects that cannot be shared (without (deep) cloning).
Rational: Immutable objects are considered "easier to reason about"
and generally more safe, and as a way to avoid
spooky-action-at-a-distance. I argue that mutability is totally fine
as long as it's not being shared. Currently, there's no way to
enforce this in PHP, but PHP has a refcounting GC which could do
this. This would be a more performant alternative than to build lots
of immutable objects using $obj->whereSomething('bla') constructs.Cons: If PHP switches to tracing GC, it will be unusable.
What do you think? Should I write it down as an RFC? Implementation
should be straight-forward, assuming refcount is manipulated at a
single place in the source.Regards
OlleI like the goal of addressing shared mutable state is a laudable one and I
think most here share it. However, I think uniqueness checking is the wrong
approach, for reasons others have already explained.You're correct that the thing to avoid is shared mutable state. That can be
avoided either by getting rid of mutable state or shared state. Either
would work. (Channeling Kevlin Henny here.)In the case of PHP, since we don't have threads to worry about the main
source of shared state is reference variables, or refrence-esque objects.
(Or globals, but I'm assuming most here know to avoid those already.) That
is, mutability or shared-ness within a function is, meh, who cares. It's at
function boundaries that we care.As others noted, a ref count check wouldn't help with that much, because the
act of passing an object to another function necessarily increases the
refcount, thus eliminating the main benefit.Rather, the way to eliminate shared-ness is to have "structs," that is,
objects that pass by value-ish rather than pass-by-reference-ish. (Yes it's
more complicated than that, I know, I'm talking about the user-space
implications.) PHP 4 objects all worked that way, which was a problem for
anything with services our resources behind it. That's why it changed in
PHP 5 to the more common by-ref-ish behavior, which... is a problem for
value objects.Both use cases are valid. It's why newer languages like Go and Rust take a
completely different approach to what "object" even means in the first
place.It's been discussed before but never really implemented; I do think the way
to help break the "shared state" side is to allow objects to get flagged as
"will use by-value passing" or "will use by-reference passing" (again,
implications, not implementation). That is, if you have a by-val object and
pass it to a function, it behaves the same way as passing a string or an
array: Copy-on-write of the object when any changes are made to properties
of the object. Probably we'd also need to require that properties of by-val
objects cannot be by-ref objects or resources, which seems reasonable to
me.I think that would do a far better job of addressing the
shared-mutable-state issue than reference counting, because it attacks the
root problem rather than a symptom.--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
I think freezing objects might be better fit for my imagined use-case.
The only problem I see is that you can't really unfreeze them. Imagine
a database connection that only can be open/closed at refcount = 1:
$connection = new OwnershipConnection();
$connection->open();
$ps = new PostService($connection);
$ps->updateAllPosts(); // Throws exception if $connection->close()
$connection->close();
With freeze, you could also do
$ps = new PostService($connection->freeze());
to ensure it's not closed by mistake. But then you couldn't close the
connection at all, except in __destruct.
Especially, CoW for objects (at opt-in) is not a replacement where
ownership is supposed to replace immutability for performance reason,
e.g. creating a separate immutable database connection for every class
that uses it.
Immutable builder are already part of PSR, e.g. here:
https://www.php-fig.org/psr/psr-7/
I have to wonder how reasonable this is, when freezing or ownership
are also relevant solutions, with different trade-offs.
Olle
I think that would do a far better job of addressing the
shared-mutable-state issue than reference counting, because it attacks the
root problem rather than a symptom.--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
I think freezing objects might be better fit for my imagined use-case.
The only problem I see is that you can't really unfreeze them. Imagine
a database connection that only can be open/closed at refcount = 1:$connection = new OwnershipConnection(); $connection->open(); $ps = new PostService($connection); $ps->updateAllPosts(); // Throws exception if $connection->close() $connection->close();
With freeze, you could also do
$ps = new PostService($connection->freeze());
to ensure it's not closed by mistake. But then you couldn't close the
connection at all, except in __destruct.Especially, CoW for objects (at opt-in) is not a replacement where
ownership is supposed to replace immutability for performance reason,
e.g. creating a separate immutable database connection for every class
that uses it.Immutable builder are already part of PSR, e.g. here:
https://www.php-fig.org/psr/psr-7/I have to wonder how reasonable this is, when freezing or ownership
are also relevant solutions, with different trade-offs.Olle
A database connection is the textbook example of an object that should be shared; giving a separate connection to every service object is only going to waste time opening multiple connections and waste open connections. Please don't do that.
Your example seems directly contrary to the goal you're describing.
--Larry Garfield
2020-07-11 21:25 GMT, Larry Garfield larry@garfieldtech.com:
I think that would do a far better job of addressing the
shared-mutable-state issue than reference counting, because it attacks
the
root problem rather than a symptom.--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
I think freezing objects might be better fit for my imagined use-case.
The only problem I see is that you can't really unfreeze them. Imagine
a database connection that only can be open/closed at refcount = 1:$connection = new OwnershipConnection(); $connection->open(); $ps = new PostService($connection); $ps->updateAllPosts(); // Throws exception if $connection->close() $connection->close();
With freeze, you could also do
$ps = new PostService($connection->freeze());
to ensure it's not closed by mistake. But then you couldn't close the
connection at all, except in __destruct.Especially, CoW for objects (at opt-in) is not a replacement where
ownership is supposed to replace immutability for performance reason,
e.g. creating a separate immutable database connection for every class
that uses it.Immutable builder are already part of PSR, e.g. here:
https://www.php-fig.org/psr/psr-7/I have to wonder how reasonable this is, when freezing or ownership
are also relevant solutions, with different trade-offs.Olle
A database connection is the textbook example of an object that should be
shared; giving a separate connection to every service object is only going
to waste time opening multiple connections and waste open connections.
Please don't do that.Your example seems directly contrary to the goal you're describing.
--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
You're misunderstanding. :) Yes, it should be shared, but not with the
same access level as the owner. If a dependent class closes the
connection it will cause spooky-action-at-a-distance somewhere else in
the program. Sorry, it's related to refcount as ownership checking and
not to throwing exception at refcount > 1. More like, "limit access at
refcount > 1" (at borrow, that is). Reference counting can be used for
both poor man's ownership semantics and to enforce non-aliasing. So
yeah, maybe I should just suggest that something like this extension
gets merged into vanilla PHP: https://github.com/qix/php_refcount
(proper way to get refcount instead of a zval dump). Or maybe make
zval dump return something instead of dumping a string.
Olle
On Sun, Jul 12, 2020 at 12:47 AM Olle Härstedt olleharstedt@gmail.com
wrote:
You're misunderstanding. :) Yes, it should be shared, but not with the
same access level as the owner. If a dependent class closes the
connection it will cause spooky-action-at-a-distance somewhere else in
the program. Sorry, it's related to refcount as ownership checking and
not to throwing exception at refcount > 1.
If your class doesn't know whether it created its connection or it was
passed to it, it's badly designed. Not only would that mix concerns (making
DB requests and managing connections), it would also be hard to test and
hard to keep in the programmer's head. Use dependency injection to avoid
that. I'm not convinced by this example. You can't use ownership as a
protection against code doing something stupid. Imagine for a second you've
prevented non-owners from closing your connection. Will that also prevent
non-owners from dropping your database? What about them messing with your
connection object's properties? Language features can only prevent
developers from making accidental mistakes. Bad code is not an accidental
mistake.
--
Best regards,
Max Semenik
You're misunderstanding. :) Yes, it should be shared, but not with the
same access level as the owner. If a dependent class closes the
connection it will cause spooky-action-at-a-distance somewhere else in
the program.
I think that particular example is solved adequately by destructors: if you know you hold the last reference to an object, you can unset() that reference and the destructor will fire.
You'd only need the ability to "unfreeze" an object if you were going to mutate it and then carry on using it.
Sorry, it's related to refcount as ownership checking
I still don't think those concepts map well. If you store two references to an object in an array, and then unset one of them at random, does it make sense to say the remaining reference "owns" that value, because the object now has a refcount of one?
As I understand it, ownership in Rust implies not just the right to free something, but the obligation to do so. That only works if ownership is explicitly assigned, rather than being a side effect of other references being released.
Thinking about destructors, a better description of how PHP currently works might be that the owner of an object is the object itself, and the refcount is a count of how many times it has been borrowed. So once the constructor completes, all we can say is that there is at least one "borrowed" reference somewhere in the code; once that stops being true, the "owner" is notified, and runs the code in the destructor.
You could create a wrapper object and have a way to check that its destructor had been called - anything from setting a global variable to holding a WeakRef and calling its valid() method - but if something does hold onto a reference, there's not much you can do about it.
I can't think of a scenario where the code that wants to "own" the object wouldn't end up equivalent to either full immutability (don't mutate it after initialisation because you can't guarantee yours is the only reference) or an optimised copy-on-write (mutate if you happen to have the last reference, otherwise clone). It would be better for the language to implement those in a user-friendly way than exposing the implementation details of refcounting.
Regards,
--
Rowan Tommins
[IMSoP]
2020-07-11 23:24 GMT, Rowan Tommins rowan.collins@gmail.com:
On 11 July 2020 22:47:21 BST, "Olle Härstedt" olleharstedt@gmail.com
wrote:You're misunderstanding. :) Yes, it should be shared, but not with the
same access level as the owner. If a dependent class closes the
connection it will cause spooky-action-at-a-distance somewhere else in
the program.I think that particular example is solved adequately by destructors: if you
know you hold the last reference to an object, you can unset() that
reference and the destructor will fire.You'd only need the ability to "unfreeze" an object if you were going to
mutate it and then carry on using it.Sorry, it's related to refcount as ownership checking
I still don't think those concepts map well. If you store two references to
an object in an array, and then unset one of them at random, does it make
sense to say the remaining reference "owns" that value, because the object
now has a refcount of one?
Well, yes, to do it properly you'd have to make sure the owner remains
in scope longer than the borrower. Otherwise you're "leaking"
ownership. :)
As I understand it, ownership in Rust implies not just the right to free
something, but the obligation to do so. That only works if ownership is
explicitly assigned, rather than being a side effect of other references
being released.
That's due to move semantics. You can have benefit of ownership and
uniqueness without having move.
Thinking about destructors, a better description of how PHP currently works
might be that the owner of an object is the object itself, and the refcount
is a count of how many times it has been borrowed. So once the constructor
completes, all we can say is that there is at least one "borrowed" reference
somewhere in the code; once that stops being true, the "owner" is notified,
and runs the code in the destructor.You could create a wrapper object and have a way to check that its
destructor had been called - anything from setting a global variable to
holding a WeakRef and calling its valid() method - but if something does
hold onto a reference, there's not much you can do about it.I can't think of a scenario where the code that wants to "own" the object
wouldn't end up equivalent to either full immutability (don't mutate it
after initialisation because you can't guarantee yours is the only
reference) or an optimised copy-on-write (mutate if you happen to have the
last reference, otherwise clone). It would be better for the language to
implement those in a user-friendly way than exposing the implementation
details of refcounting.
Yeah, I guess. :(
In the end I need uniqueness (non-aliasing) to finish my series of
patches about typestate and type-safe builders to Psalm, but I guess I
can live without any runtime representation of this, or just suffer
the hack I already did with zval dump.
Thanks for the feedback!
Regards,
--
Rowan Tommins
[IMSoP]--
To unsubscribe, visit: https://www.php.net/unsub.php
In the end I need uniqueness (non-aliasing) to finish my series of
patches about typestate and type-safe builders to Psalm, but I guess I
can live without any runtime representation of this, or just suffer
the hack I already did with zval dump.
I think this kind of thing is probably best left to static analysis anyway.
PHP ends up doing much more checking at run-time than most languages
because it has no official static analyser, and no build step where static
analysis can be mandatory. But in the end what you're trying to catch here
is programmer mistakes, not unexpected run-time behaviour.
Regards,
Rowan Tommins
[IMSoP]
Hey Olle,
On Sat, Jul 11, 2020 at 4:06 PM Olle Härstedt olleharstedt@gmail.com
wrote:
Dear internals,
I'd like to discuss the concept of ownership in PHP, from the point of
view of a new interface UniqueInterface (or SplUniqueInterface or
something), which will throw a UniqueException if refcount > 1.Use-case: Mutable objects that cannot be shared (without (deep) cloning).
Rational: Immutable objects are considered "easier to reason about"
and generally more safe, and as a way to avoid
spooky-action-at-a-distance. I argue that mutability is totally fine
as long as it's not being shared. Currently, there's no way to
enforce this in PHP, but PHP has a refcounting GC which could do
this. This would be a more performant alternative than to build lots
of immutable objects using $obj->whereSomething('bla') constructs.Cons: If PHP switches to tracing GC, it will be unusable.
What do you think? Should I write it down as an RFC? Implementation
should be straight-forward, assuming refcount is manipulated at a
single place in the source.Regards
Olle
I consider the problem problem of singleton enforcement to be solved
elegantly in userland, such as in
https://github.com/marc-mabe/php-enum/blob/v4.3.0/src/Enum.php
Considering that, what does a language-level construct bring in?
Marco Pivetta