Possible RFC: UniqueInterface that throws exception at refcount > 1

php.internals

Olle Härstedt

6 years ago
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

Chuck Adams

6 years ago
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

tyson andre

6 years ago
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

Max Semenik

6 years ago
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

Olle Härstedt

6 years ago
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>:

tyson andre

6 years ago

Olle Härstedt

6 years ago
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

Rowan Collins

6 years ago
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. 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]

Olle Härstedt

6 years ago
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

Rowan Collins

6 years ago
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.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]

Olle Härstedt

6 years ago
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.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] > > -- > PHP Internals - PHP Runtime Development Mailing List > 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.

Chuck Adams

6 years ago
On Sat, Jul 11, 2020 at 8:56 AM Rowan Tommins <rowan.collins@gmail.com> wrote:
> (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

Larry Garfield

6 years ago
On Sat, Jul 11, 2020, at 9:06 AM, Olle Härstedt 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 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

Olle Härstedt

6 years ago
2020-07-11 19:08 GMT, Larry Garfield <larry@garfieldtech.com>:
> On Sat, Jul 11, 2020, at 9:06 AM, Olle Härstedt 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 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 > > -- > PHP Internals - PHP Runtime Development Mailing List > 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

Larry Garfield

6 years ago
On Sat, Jul 11, 2020, at 4:04 PM, Olle Härstedt wrote:
> > 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 > > > > -- > > PHP Internals - PHP Runtime Development Mailing List > > 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

Olle Härstedt

6 years ago
2020-07-11 21:25 GMT, Larry Garfield <larry@garfieldtech.com>:
> On Sat, Jul 11, 2020, at 4:04 PM, Olle Härstedt wrote: > >> > 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 >> > >> > -- >> > PHP Internals - PHP Runtime Development Mailing List >> > 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 > > -- > PHP Internals - PHP Runtime Development Mailing List > 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

Max Semenik

6 years ago
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

Rowan Collins

6 years ago
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? 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]

Olle Härstedt

6 years ago
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!

Rowan Collins

6 years ago
On Mon, 13 Jul 2020 at 22:32, Olle Härstedt <olleharstedt@gmail.com> wrote:
> 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]

Marco Pivetta

6 years ago
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 http://twitter.com/Ocramius http://ocramius.github.com/