Re: RFC: Marking return values as important (#[\NoDiscard])

From: Date: Wed, 12 Feb 2025 16:43:03 +0000
Subject: Re: RFC: Marking return values as important (#[\NoDiscard])
References: 1 2  Groups: php.internals 
Request: Send a blank email to [email protected] to get a copy of this message
Hi

Am 2025-02-12 15:07, schrieb Kamil Tekiela:
I'd be honest, I have a very negative attitude towards this proposal and I'd be voting against it. It seems to me like it's creating a problem and then trying to find a solution for it.
Given that even Rust with a modern stdlib and ergonomic language features has such an attribute, we do not believe it's an invented problem. With the bulk_process() example, we also tried to provide coherent user-story with a use-case that the readers might've already encountered themselves in the same or a similar fashion. Personally I encountered situations where I wished such an attribute would've warned me both in PHP and JavaScript/TypeScript. Also in C of course, but given that C doesn't have Exceptions, the situation is somewhat different. For an additional example use-case, consider a function returning a resource object with side-effects which will automatically clean up the resource when being destructed. Specifically a function to spawn a new process or thread:
    class Process {
        public function __destruct() { /* kill the process */ }
    }
    #[NoDiscard("the process will be terminated when the Process object goes out of scope")]
    function start_process(string $executable): Process {
        return new Process($executable);
    }
    start_process('/my/background/process');
Depending on timing, the process might or might not run to completion before PHP gets around to kill it, for example when a step debugger is attached to PHP, making the bug disappear when debugging. Related to the above example and also to the flock() function: A developer might want to write a locking function that returns a lock resource that will automatically unlock when it goes out of scope, preventing the unlock from being forgotten. It might not be immediately obvious that the locking function returns a lock resource, especially when it's associated with another object:
    $cacheDriver->lock('someCacheEntry');
Is there a $cacheDriver->unlock('someCacheEntry') or will ->lock() return a lock resource? Having the attribute on the lock() method can help. Searching for language:rust "must_use =" on GitHub reveals that a common use-case in Rust is lazy iterators, where you can call higher-order functions (e.g. map() or filter()), but they will not do anything unless you explicitly “poll” them at least once. As far as I know, Java has lazy streams that behave identically.
A return value is always supposed to be used. If some API is returning a value that can be safely ignored, it's a badly designed API. If a
We agree that in a greenfield ecosystem, we would make ignoring the return value of a function a warning by default (with an opt-out mechanism), as done in Swift. However in PHP we do not have a greenfield situation, there a number of functions where the return value is useless and only exists for historical reasons. This includes all functions which nowadays have a return-type of just true, which was only added for this singular purpose. There's cases where ignoring the return value is safe and reasonable, without being a badly designed API. array_pop() would come to mind: If one is only interested in the side-effect of removing the last element in the array, one does not need the return value without necessarily doing something unsafe.
doesn't feel like the solution. In fact, it's creating a problem for users who want to ignore the value, which you then propose to solve with (void) cast.
Ignoring the return value of functions having the attribute should be a rare occurrence given the intended use-case of pointing out unsafe situations. But as with static analyzers there needs to solution to suppress warnings, after carefully verifying that the warning is not applicable in a given situation.
the compilation phase. In PHP warnings are runtime errors. The code should emit an exception instead of a warning. It would also make it
See Volker's reply to Derick.
much easier to handle and you wouldn't need any special construct to allow users to ignore the new attribute. And I am really not a fan of
Even if an Exception would be thrown, there would be a mechanism to suppress the Exception. A catch-block wouldn't work, because if the Exception is thrown, the function is not executed / control flow is interrupted.
the PHP engine generating E_USER_WARNING which should be reserved only for warnings triggered by trigger_error.
This follows the lead of the #[\Deprecated] attribute, which emits E_USER_DEPRECATED for userland functions and E_DEPRECATED for native functions, despite being triggered by the engine.
The examples you used don't support the need for the new attribute. Regarding the DateTimeImmutable methods, you said yourself: "The methods are badly named, because they do not actually set the updated value". So your proposal suggests adding a brand new thing to PHP to deal with bad method names?
DateTimeImmutable is not part of the “Use Cases” section: Our intended use-case is the kind of bulk_process() functionality that is used for all the code snippets. But given that the attribute is also useful for DateTimeImmutable, we made it part of the proposal, without it being part of the intended “user-story”.
This problem should be solved using static analysers, IDE, and proper code refactoring.
See Volker's reply to Matteo. Best regards Tim Düsterhus

Thread (48 messages)

« previous php.internals (#126380) next »