# [RFC] Nullable Casting

- Thread: https://externals.io/message/105122
- Original archive: https://news-web.php.net/php.internals/105122
- Started by Guilliam Xavier on 2019-04-06T07:52:42+00:00
- Messages: 31
- RFC: Nullable Casting (Inactive) — https://externals.io/rfc/nullable-casting.md
- Page 1 of 2: messages 1 to 25 (25 per page, add `?page=N` to the URL)
- Next page: https://externals.io/message/105122.md?page=2
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #105122 — Guilliam Xavier — 2019-04-06T07:52:42+00:00

URL: https://externals.io/message/105122#105122

Hello internals,

David and I would like to open the discussion on our joint RFC:

https://wiki.php.net/rfc/nullable-casting

Mainly, it would enable to use e.g. `(?int)$x` besides `(int)$x`.

We are looking forward to your feedback and, if possible, help to fill
in the "RFC Impact" subsections (hopefully "None" to all) as well as a
review of the proposed implementation:

https://github.com/php/php-src/pull/3764

Thank you,

-- 
Guilliam Xavier

---

## #105136 — Dan Ackroyd — 2019-04-08T02:54:44+00:00

URL: https://externals.io/message/105122#105136

>
> Hello internals,
>
> David and I would like to open the discussion on our joint RFC:
>
> https://wiki.php.net/rfc/nullable-casting
>
> Mainly, it would enable to use e.g. `(?int)$x` besides `(int)$x`.
>

I'm guessing you don't actually have ths function getIntOrNull() in
your code-base? To help me understand where this would be useful,
could you provide some 'real-world' code where this would be useful?

By the way, this RFC is a special case of something that could be far
more generic. If it was possible to register callbacks to be used when
casting, people could do something like this:

function castToIntOrNull($value)
{
    if ($value === null) {
        return null;
    }

    return (int)$int;
}

register_cast_function('?int', 'castToIntOrNull');

$x = (?int)getIntOrNull();


> Additionally, it was requested on the mailing list to consider adding
> support of nullable types to the `settype()` function,
> e.g. settype($variable, "?int")

Someone probably needs to make an argument for it to be in core,
rather than just saying that it's something that could be done.

cheers
Dan
Ack

---

## #105137 — David Rodrigues — 2019-04-08T05:05:07+00:00

URL: https://externals.io/message/105122#105137

Real world example:

class Paginator {
    // If null, will use the default value (eg. 15), else, it will requires
an int.
    public function setItemsPerPage(?int $itemsPerPage) { ... }
}

// Info that came from user input.
// It will be a string, and not a integer, like we need.
$itemsPerPage = Input::get('itemsPerPage');

// If '<empty>' then $itemsPerPage = string '<empty>'
// If '123' then $itemsPerPage = string '123'

$paginator->setItemsPerPage($itemsPerPage); // Error: expected int, got
string.

Current solution:

$itemsPerPage = Input::get('itemsPerPage') ?: null;
$itemsPerPage = $itemsPerPage !== null ? (int) $itemsPerPage : null;

$paginator->setItemsPerPage($itemsPerPage); // OK

With this new feature: just...

$paginator->setItemsPerPage((?int) Input::get('itemsPerPage')); // OK

Question: why just not check if $itemsPerPage is set like:

$itemsPerPage = Input::get('itemsPerPage');

if ($itemsPerPage) {
    $paginator->setItemsPerPage((int) $itemsPerPage); // OK
}

Answer: because in this example we could do that (although the new solution
is much more practical). In another case, I have a static factory method
that depends of an ?int to be created, and I can't just skip it with an
if() if user input is empty.

And about "settype($variable, "?int")" it was requested on original mailing
by Claude Pache.


Em dom, 7 de abr de 2019 às 23:54, Dan Ackroyd <Danack@basereality.com>
escreveu:

> On Sat, 6 Apr 2019 at 08:53, Guilliam Xavier <guilliam.xavier@gmail.com>
> wrote:
> > […]
>
> I'm guessing you don't actually have ths function getIntOrNull() in
> your code-base? To help me understand where this would be useful,
> could you provide some 'real-world' code where this would be useful?
>
> By the way, this RFC is a special case of something that could be far
> more generic. If it was possible to register callbacks to be used when
> casting, people could do something like this:
>
> function castToIntOrNull($value)
> {
>     if ($value === null) {
>         return null;
>     }
>
>     return (int)$int;
> }
>
> register_cast_function('?int', 'castToIntOrNull');
>
> $x = (?int)getIntOrNull();
>
>
> > […]
>
> Someone probably needs to make an argument for it to be in core,
> rather than just saying that it's something that could be done.
>
> cheers
> Dan
> Ack
>


-- 
David Rodrigues

---

## #105141 — Benjamin Morel — 2019-04-08T09:35:31+00:00

URL: https://externals.io/message/105122#105141

> register_cast_function('?int', 'castToIntOrNull');

I would quite oppose such a generic implementation, as it might have
unexpected consequences in other parts of the codebase : it's a global
configuration set at runtime.

I quite like the idea of nullable casting though, as I've come across a few
cases myself where this would have been useful and would have made the code
more concise/readable.

Ben



> On Sat, 6 Apr 2019 at 08:53, Guilliam Xavier <guilliam.xavier@gmail.com>
> wrote:
> > […]
>
> I'm guessing you don't actually have ths function getIntOrNull() in
> your code-base? To help me understand where this would be useful,
> could you provide some 'real-world' code where this would be useful?
>
> By the way, this RFC is a special case of something that could be far
> more generic. If it was possible to register callbacks to be used when
> casting, people could do something like this:
>
> function castToIntOrNull($value)
> {
>     if ($value === null) {
>         return null;
>     }
>
>     return (int)$int;
> }
>
> register_cast_function('?int', 'castToIntOrNull');
>
> $x = (?int)getIntOrNull();
>
>
> > […]
>
> Someone probably needs to make an argument for it to be in core,
> rather than just saying that it's something that could be done.
>
> cheers
> Dan
> Ack

---

## #105143 — Stephen Reay — 2019-04-08T10:24:47+00:00

URL: https://externals.io/message/105122#105143

> 
> 
> > […]
> 
> I would quite oppose such a generic implementation, as it might have
> unexpected consequences in other parts of the codebase : it's a global
> configuration set at runtime.
> 
> I quite like the idea of nullable casting though, as I've come across a few
> cases myself where this would have been useful and would have made the code
> more concise/readable.
> 
> Ben
> 
> 
> 
> > […]

I’ve come across this pattern (string variable contains a number or nothing) myself, but it also then makes me think - if your target ’type’ is `?int` and you pass an empty string, wouldn’t you expect to get `null` rather than `0` ?

---

## #105144 — Benjamin Morel — 2019-04-08T11:53:46+00:00

URL: https://externals.io/message/105122#105144

>  if your target ’type’ is `?int` and you pass an empty string, wouldn’t
you expect to get `null` rather than `0` ?

I'd personally expect an `Error`, but this is not very much in line with
current casting semantics.

You have a point here, there is no clear answer for this edge case.

Maybe before nullable casting, should be discussed throwing errors when
attempting to cast a bad value, such as `(int) ""` ?
I think we're getting one step closer with the string to number comparison
RFC <https://wiki.php.net/rfc/string_to_number_comparison>, which suggests
that `"" != "0"`.

Ben





>
> > […]
> wrote:
> > […]
> few
> > […]
> code
> > […]
>
> I’ve come across this pattern (string variable contains a number or
> nothing) myself, but it also then makes me think - if your target ’type’ is
> `?int` and you pass an empty string, wouldn’t you expect to get `null`
> rather than `0` ?

---

## #105148 — Dan Ackroyd — 2019-04-08T14:44:31+00:00

URL: https://externals.io/message/105122#105148

>
> Hello,
>
> looks like you are living in a pig stall; php would be benefit from type safety when explicitly required.
>
> Regards.

Hi Moe,

It seems you:

i) accidentally hit reply, instead of reply all. This is an easy
mistake to make.

ii) Appear to be trying to compare me to a pig? I'm not sure that this
is contributes to a productive discussion. Technical arguments are
much better received than animal comparisons.

cheers
Dan
Ack

---

## #105149 — Dan Ackroyd — 2019-04-08T15:35:27+00:00

URL: https://externals.io/message/105122#105149

>
> Question: why just not check if $itemsPerPage is set like:
> ...
> Answer: because in this example we could do that (although the new solution is much more practical).

Well, I disagree on that. That code is perfectly fine to me, and
having more operators to understand and remember is harder than having
fewer operators to understand and remember.

Also, I have a suspicion that the reason why you think this might be
needed is that you're doing too much work outside `Input::get`. If you
made a separate function that for `Input::getInt` that did all the
work of casting the value to int when appropriate, you wouldn't have
to repeat all the work in the functions that are calling `Input::get`.
i.e. you're causing a lot of typing to be required to further process
the result of a function, when you should be making new function that
does more precisely what you want.

> In another case, I have a static factory method that depends of an ?int to be created, and I can't just skip it with an if() if user input is empty.

But please can you post the actual use-case you _need_ this for. Or
possibly update the RFC with that example. Currently, imo, the RFC is
just presenting an alternative syntax for something that is not 100%
needed.

I do kind of like the idea in the RFC, but I think it needs a better
argument for it.

cheers
Dan
Ack


btw, the code you posted as:

> $itemsPerPage = Input::get('itemsPerPage');
>
> if ($itemsPerPage) {
>     $paginator->setItemsPerPage((int) $itemsPerPage); // OK
> }

isn't quite the same as what the RFC does, right? The code equivalent
for the RFC would be this I think:

$itemsPerPage = Input::get('itemsPerPage');
if ($itemsPerPage !== null) {
    $itemsPerPage = (int)$itemsPerPage;
}
$paginator->setItemsPerPage($itemsPerPage);

which even avoids the magic/horribleness of having to understand what
PHP thinks is empty.

---

## #105150 — Claude Pache — 2019-04-08T15:41:22+00:00

URL: https://externals.io/message/105122#105150

> Le 8 avr. 2019 à 12:24, Stephen Reay <php-lists@koalephant.com> a écrit :
> 
> if your target ’type’ is `?int` and you pass an empty string, wouldn’t you expect to get `null` rather than `0` ? 


You should get the same value than you get in:

```php
declare(strict_types=0);

function foo(?int $x) {
    return $x;
}

foo("");
```

BTW, this is an argument to the have nullable casting functionality in core, rather than leave each developer to craft their own solution. Because PHP does already such nullable castings in several occasions (typed parameters, typed return values, and soon typed class properties, when you are in strict_types=0 mode), we should offer the programmers a built-in evident way to do the same type of casting, with the same semantics in occasion they want to do it manually. Otherwise, one developer will decide to map the empty string to null, the other to 0 and the third to NAN, and they get problems when they refactor the code in a way that a manual casting is replaced by an automatic implicit casting or vice versa, and the semantics differs on some cases.

—Claude

---

## #105151 — Claude Pache — 2019-04-08T16:17:19+00:00

URL: https://externals.io/message/105122#105151

> Le 8 avr. 2019 à 07:05, David Rodrigues <david.proweb@gmail.com> a écrit :
> 
> And about "settype($variable, "?int")" it was requested on original mailing
> by Claude Pache.

PHP has several ways to perform casting:
1. (int) $foo
2. settype($foo, 'int');
3. intval($foo)

They are occasions where the programmer will prefer one method to the other. For example:
1. foo((int) $bar)
2. settype($someNonTrivial[$expr]['ession'], 'int');
3. array_map('intval', $foo)

The `intval($foo)` family of functions might be replaced by `function ($foo) { return (int) $foo; }` (or maybe soon by `fn($foo) => (int) $foo`), so that it might not be a necessity to double them with `intvalIfNotNull($foo)`, etc. functions (also, finding a good name is not evident). But as for `settype()`, it cannot be replaced by casting syntax in case the target type is not written literally in source code; so that we should continue to fully support it. (And see https://externals.io/message/103779#103805 <https://externals.io/message/103779#103805> for real world usages.)

—Claude

---

## #105153 — mail@pmmaga.net — 2019-04-08T17:46:20+00:00

URL: https://externals.io/message/105122#105153

On Mon, Apr 8, 2019 at 6:05 AM David Rodrigues <david.proweb@gmail.com>
wrote:

> Current solution:
>
> $itemsPerPage = Input::get('itemsPerPage') ?: null;
> $itemsPerPage = $itemsPerPage !== null ? (int) $itemsPerPage : null;
>
> $paginator->setItemsPerPage($itemsPerPage); // OK
>
> With this new feature: just...
>
> $paginator->setItemsPerPage((?int) Input::get('itemsPerPage')); // OK
>
> Question: why just not check if $itemsPerPage is set like:
>
> $itemsPerPage = Input::get('itemsPerPage');
>
> if ($itemsPerPage) {
>     $paginator->setItemsPerPage((int) $itemsPerPage); // OK
> }
>
> Answer: because in this example we could do that (although the new solution
> is much more practical). In another case, I have a static factory method
> that depends of an ?int to be created, and I can't just skip it with an
> if() if user input is empty.
>

Hi!

I'm not a fan of the approach for a few reasons:
- First and foremost, we would introduce a cast where you can't be sure of
what you'll get back. I understand the use-case for when you want to pass
something to a nullable parameter, but if you think about this cast in
isolation, it hardly makes sense.
- Second, maybe this is particular to your example, but your are forcing
yourself to process something that the user didn't set. If your paginator
already has a sane default(15) in a property, just don't call its setter
when the user didn't ask you to. If you insist that you really want to save
LoC, just assume that 0 means "use the default" and you can use the cast in
all cases.

Regards,
Pedro

---

## #105154 — M. W. Moe — 2019-04-08T21:55:03+00:00

URL: https://externals.io/message/105122#105154

Hello,

i) was intentional.

ii) Not our fault, we do not have this function in our code-base and won't
ever; we would not even dare 8).

Cheers!



> 
> > […]
> safety when explicitly required.
> > […]
>
> Hi Moe,
>
> It seems you:
>
> i) accidentally hit reply, instead of reply all. This is an easy
> mistake to make.
>
> ii) Appear to be trying to compare me to a pig? I'm not sure that this
> is contributes to a productive discussion. Technical arguments are
> much better received than animal comparisons.
>
> cheers
> Dan
> Ack

---

## #105155 — Guilliam Xavier — 2019-04-09T06:28:17+00:00

URL: https://externals.io/message/105122#105155

>
> [redacted]
>
> 
>
> > […]

As the initiator of this thread (who hasn't had the time to answer
questions yet, sorry), I would ask everyone to please try to avoid
sarcasm and provocation (intentional or not), especially if you
actually like the idea =(

Thank you in advance

-- 
Guilliam Xavier

---

## #105157 — Guilliam Xavier — 2019-04-09T07:32:16+00:00

URL: https://externals.io/message/105122#105157

Dan Ackroyd <Danack@basereality.com> wrote:
>
> I'm guessing you don't actually have ths function getIntOrNull() in
> your code-base? To help me understand where this would be useful,
> could you provide some 'real-world' code where this would be useful?

Hello, thanks for digging in :)

Here's a "real-world" situation I happened to come across (*NOTE:
simplified for sanity*):

vendor/bar/api/src/Dest.php (code we don't own):

    <?php
    namespace Bar\Api;
    interface Dest {
        public function send(?string $reference, string $label, /* ... */);
    }

vendor/foo/api/src/Source.php (code we can't modify either):

    <?php
    namespace Foo\Api;
    interface Source {
        public function getNumber(): ?int;
        public function getName(): string;
        /* ... */
    }

src/Service.php (our code):

    <?php
    declare(strict_types=1); // FIXME
    namespace App;
    use Bar\Api\Dest;
    use Foo\Api\Source;
    class Service {
        public function action(Dest $dest, Source $source) {
            $dest->send(/* FIXME */ $source->getNumber(),
$source->getName(), /* ... */);
        }
    }

As you can see, it's actually pretty close to the RFC example; I'm not
sure it's worth to add complexity...

> But please can you post the actual use-case you _need_ this for. Or
> possibly update the RFC with that example. Currently, imo, the RFC is
> just presenting an alternative syntax for something that is not 100%
> needed.

You have a point, but all the current alternatives I can think of
(namely: use a ternary (or an `if`), possibly with a temporary
variable; write (and autoload) custom casting functions; or give up on
strict typing) suffer one or more of the following downsides:
  - increased risk of mistakes
  - verbosity / more maintenance
  - performance overhead

> I do kind of like the idea in the RFC, but I think it needs a better
> argument for it.

For your other points, I find that Claude Pache has given pretty good
arguments in the meantime (thanks!) =) (By the way, I also thought
about say, "nullable_intval()", "nullable_strval()" etc. but we're
missing "arrayval()" and "objectval()"...)

Best regards,

--
Guilliam Xavier

---

## #105159 — Lester Caine — 2019-04-09T08:26:19+00:00

URL: https://externals.io/message/105122#105159

> > […]
> For your other points, I find that Claude Pache has given pretty good
> arguments in the meantime (thanks!) =) (By the way, I also thought
> about say, "nullable_intval()", "nullable_strval()" etc. but we're
> missing "arrayval()" and "objectval()"...)

The debate as always is between those for who 'NULL' is a key STATE that 
is part of the interaction between a database and program and those who 
do not have that need. In my world a variable has a number of states and 
 `NULL` is simply one of them. Thinking of `NULL` as 'uninitialized' is a 
reasonable match, and when pushing a variable to a database it's `NULL` 
state can be used to trigger loading a default value or to leave the 
field unset for later handling. HAVING to put a value or a string into 
the variable because 'it must have a type' is simply wrong. But the need 
now to HAVE to cast at times is equally wrong? I'm still hitting errors 
in stuff being moved to 7.2 where the 'fix' is adding a cast, to keep 
PHP happy, but then the value may well be a `NULL` return from the data 
set so one almost ends up needing two versions of a variable ... one to 
keep the now stricter typing happy and one to store the actual data 
returned from the database. This also links up with the leading and 
trailing spaces discussion. That the working value is a left justified 
or right justified number but the logic now requires an 'int' to keep it 
quiet is pushing towards needing these separate versions of essentially 
the same variable :( Making the associative array view of the data 
difficult to manage ...

-- 
Lester Caine - G8HFL
-----------------------------
Contact - https://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - https://lsces.co.uk
EnquirySolve - https://enquirysolve.com/
Model Engineers Digital Workshop - https://medw.co.uk
Rainbow Digital Media - https://rainbowdigitalmedia.co.uk

---

## #105202 — Nikita Popov — 2019-04-10T10:52:22+00:00

URL: https://externals.io/message/105122#105202

On Sat, Apr 6, 2019 at 9:53 AM Guilliam Xavier <guilliam.xavier@gmail.com>
wrote:

> Hello internals,
>
> David and I would like to open the discussion on our joint RFC:
>
> https://wiki.php.net/rfc/nullable-casting
>
> Mainly, it would enable to use e.g. `(?int)$x` besides `(int)$x`.
>
> We are looking forward to your feedback and, if possible, help to fill
> in the "RFC Impact" subsections (hopefully "None" to all) as well as a
> review of the proposed implementation:
>
> https://github.com/php/php-src/pull/3764


I'm a bit concerned that there are two possible semantics for what (?int)
does:

 1. What I would intuitively expect: A fallible integer cast. I.e. try to
cast to integer and if not possible return null. So (?int) "foobar" becomes
null rather than 0.
 2. What this RFC proposes: A normal integer cast that leaves null alone.

Both behaviors make sense to me generally ... and if there are two ways to
interpret a piece of syntax, I'd say the syntax is not explicit enough.

Nikita

---

## #105203 — Benjamin Morel — 2019-04-10T12:03:27+00:00

URL: https://externals.io/message/105122#105203

>
> I'm a bit concerned that there are two possible semantics for what (?int)
> does:



There is only one, if we align the semantics of explicit and implicit
casting: you wouldn't expect this:

    function test(?int $bar) { var_export($bar); }
    test("abc");

...to output 'null'?

--
Ben

---

## #105207 — Mark Randall — 2019-04-10T14:33:58+00:00

URL: https://externals.io/message/105122#105207

> There is only one, if we align the semantics of explicit and implicit
> casting: you wouldn't expect this:
> 
>      function test(?int $bar) { var_export($bar); }
>      test("abc");
> 
> ...to output 'null'?

(Continuing from my previous post in a different thread...)

IMHO a cast such as ([?]type) would be better described as passing a 
mixed value through a function which has a return type of whatever is in 
the cast... the mechanics of that function determine what is returned, 
so long as it conforms to the cast type.

A cast should always either return a value that would be accepted by a 
function argument of an identical type to the cast, or it should throw 
an exception.

function x(T $value) { ... }
x(T) - Always OK
x((T)$var) - Always OK
x($var) - Function may throw a TypeError if it cannot be converted in a 
very strict manner.

To my mind, (?int) is quite explicit, in that it returns an integer or 
null, and it is quite logical to expect it to return null in lieu of 
throwing a TypeError if a conversion is not possible. Naturally, 
(?int)null would return null.

I see a lot of potential use in (?int)$value ?? $default.

Equally I see a lot of use in the following to nullify unexpected inputs 
such as if $_GET['something'] was an array:

$input = (?string)$_GET['input'] ?? '';

--
Mark Randall

---

## #105208 — Guilliam Xavier — 2019-04-10T14:40:18+00:00

URL: https://externals.io/message/105122#105208

>
> I'm a bit concerned that there are two possible semantics for what (?int)
> does:
>
>  1. What I would intuitively expect: A fallible integer cast. I.e. try to
> cast to integer and if not possible return null. So (?int) "foobar" becomes
> null rather than 0.
>  2. What this RFC proposes: A normal integer cast that leaves null alone.
>
> Both behaviors make sense to me generally ... and if there are two ways to
> interpret a piece of syntax, I'd say the syntax is not explicit enough.

The syntax mirrors nullable type declarations, but the thing is
implicit and explicit conversions don't always behave the same today:

```php
<?php
declare(strict_types=0); // weak mode
error_reporting(-1);

$non_numeric_string = "foobar";

var_dump(array_slice([1,2,3], $non_numeric_string, null, true)); //
Warning, then prints `NULL`
var_dump(array_slice([1,2,3], 0, $non_numeric_string, true)); //
prints `array(0) { }`

var_dump(mb_substr("abc", $non_numeric_string, null, "UTF-8")); //
Warning, then prints `NULL`
var_dump(mb_substr("abc", 0, $non_numeric_string, "UTF-8")); //
Warning, then prints `NULL`

var_dump(+$non_numeric_string); // Warning, then prints `int(0)`
// (I can't think of a nullable equivalent for unary +)

(function (int $x) { var_dump($x); })($non_numeric_string); // TypeError
(function (?int $x) { var_dump($x); })($non_numeric_string); // TypeError

var_dump((function ($x): int { return $x; })($non_numeric_string)); // TypeError
var_dump((function ($x): ?int { return $x; })($non_numeric_string));
// TypeError

var_dump((int) $non_numeric_string); // prints `int(0)`
var_dump((?int) $non_numeric_string); // ...what would you expect?
```

-- 
Guilliam Xavier

---

## #105210 — Guilliam Xavier — 2019-04-10T15:16:13+00:00

URL: https://externals.io/message/105122#105210

>
> (Continuing from my previous post in a different thread...)
>
> IMHO a cast such as ([?]type) would be better described as passing a
> mixed value through a function which has a return type of whatever is in
> the cast... the mechanics of that function determine what is returned,
> so long as it conforms to the cast type.
>
> A cast should always either return a value that would be accepted by a
> function argument of an identical type to the cast, or it should throw
> an exception.
>
> function x(T $value) { ... }
> x(T) - Always OK
> x((T)$var) - Always OK
> x($var) - Function may throw a TypeError if it cannot be converted in a
> very strict manner.
>
> To my mind, (?int) is quite explicit, in that it returns an integer or
> null, and it is quite logical to expect it to return null in lieu of
> throwing a TypeError if a conversion is not possible. Naturally,
> (?int)null would return null.
>
> I see a lot of potential use in (?int)$value ?? $default.
>
> Equally I see a lot of use in the following to nullify unexpected inputs
> such as if $_GET['something'] was an array:
>
> $input = (?string)$_GET['input'] ?? '';

So, for example:

```php
$obj = (object) ["foo" => 42];


(function (int $x) { var_dump($x); })($obj); // TypeError
(function (?int $x) { var_dump($x); })($obj); // TypeError

var_dump((function ($x): int { return $x; })($obj)); // TypeError
var_dump((function ($x): ?int { return $x; })($obj)); // TypeError

var_dump((int) $obj); // Notice, then prints `int(1)`
var_dump((?int) $obj); // would you want it to print `NULL`?


(function (string $x) { var_dump($x); })($obj); // TypeError
(function (?string $x) { var_dump($x); })($obj); // TypeError

var_dump((function ($x): string { return $x; })($obj)); // TypeError
var_dump((function ($x): ?string { return $x; })($obj)); // TypeError

var_dump((string) $obj); // Recoverable fatal error
var_dump((?string) $obj); // would you want it to print `NULL`?
```

I have no clear answer myself (other than the current proposal)...

-- 
Guilliam Xavier

---

## #105212 — Mark Randall — 2019-04-10T15:47:18+00:00

URL: https://externals.io/message/105122#105212

> var_dump((?int) $obj); // would you want it to print `NULL`?
> var_dump((?string) $obj); // would you want it to print `NULL`?

I believe returning null in those situations makes the most intuative 
sense, yes.

 > var_dump((int) $obj); // Notice, then prints `int(1)`

In the absense of a nullable modifier, that makes so little sense that 
it definitely needs to throw a TypeError IMO.

--
Mark Randall

---

## #105213 — Benjamin Morel — 2019-04-10T16:15:46+00:00

URL: https://externals.io/message/105122#105213

>
> I believe returning null in those situations makes the most intuative
> sense, yes.



No offense, but how can casting semantics different from those already used
in implicit casting be intuitive?

    function a(int $x) { var_export($x); }
    function b(?int $x) { var_export($x); }

    a("123"); // 123
    a(null); // TypeError
    a("abc"); // TypeError

    b("123"); // 123
    b(null); // `NULL` 
    b("abc"); // TypeError

IMO, the only reasonable choice is therefore:

    (int) "123"; // 123
    (int) null; // TypeError
    (int) "abc"; // TypeError

    (?int) "123"; // 123
    (?int) null; // `NULL` 
    (?int) "abc"; // TypeError

At least, if we want to fix the current behaviour, and not introduce
another inconsistency.
In other words, *strictly apply to explicit casting the rules of implicit
casting when strict_types is disabled*.

--
Ben

---

## #105214 — Mark Randall — 2019-04-10T16:55:01+00:00

URL: https://externals.io/message/105122#105214

> No offense, but how can casting semantics different from those already used
> in implicit casting be intuitive?

As is implied from the name, an implicit cast is done because the code 
needs it to, not because it has been told to by the caller. It is 
natural to be more conservative when doing something that has not been 
explicitly specified.

I expect a difference in the behaviour of function arguments vs casts 
for the reason that although they share a type syntax, they do have two 
very different behaviours.

Function arguments specify what goes *in* to a function, whereas casts 
and return types specify what comes *out* of the function.

I would still expect the following:

function x(): ?int { return "foo"; } // TypeError because return is 
effectively a function

function x(): ?int { return (int?)"foo"; } // null

I don't think it would go down very well to have non-nullable casts 
throwing TypeErrors for common cases, without an elegant 
could-not-convert fallback that doesn't require a 5 line try-catch block.

--
Mark Randall

---

## #105217 — Benjamin Morel — 2019-04-10T17:34:44+00:00

URL: https://externals.io/message/105122#105217

> Function arguments specify what goes *in* to a function, whereas casts
> and return types specify what comes *out* of the function.

So why would you have different semantics for implicit `(?int)` cast vs `:
?int` function return type cast, if they're both *out*?
Return type cast has the same semantics as parameter type cast.

> I would still expect the following:
> function x(): ?int { return "foo"; } // TypeError because return is
> effectively a function
> function x(): ?int { return (int?)"foo"; } // null

This looks weird to me. I would expect this last line to throw anyway.
Having "foo" passed somehow where your code expects an int(ish) or null,
should not be silenced and converted to `NULL` IMO.
To me, this last line just says "ignore any error here" - a bit like
prefixing it with @.

> I don't think it would go down very well to have non-nullable casts
> throwing TypeErrors for common cases, without an elegant
> could-not-convert fallback that doesn't require a 5 line try-catch block.

I can't find a use case on top of my head, where I usually want to silence
a failed conversion, other than user input validation; and I don't think
type casts are specifically designed for this purpose.
Of course there could be helper functions with different semantics, that
would perform a cast-or-null-on-failure, such as `to_int("abc") === null`,
but having (?int) do this job is IMHO a bad idea which does not help moving
the language towards better consistency.

- Ben



> 
> > […]
> used
> > […]
>
> As is implied from the name, an implicit cast is done because the code
> needs it to, not because it has been told to by the caller. It is
> natural to be more conservative when doing something that has not been
> explicitly specified.
>
> I expect a difference in the behaviour of function arguments vs casts
> for the reason that although they share a type syntax, they do have two
> very different behaviours.
>
> Function arguments specify what goes *in* to a function, whereas casts
> and return types specify what comes *out* of the function.
>
> I would still expect the following:
>
> function x(): ?int { return "foo"; } // TypeError because return is
> effectively a function
>
> function x(): ?int { return (int?)"foo"; } // null
>
> I don't think it would go down very well to have non-nullable casts
> throwing TypeErrors for common cases, without an elegant
> could-not-convert fallback that doesn't require a 5 line try-catch block.
>
> --
> Mark Randall

---

## #105218 — Mark Randall — 2019-04-10T18:01:35+00:00

URL: https://externals.io/message/105122#105218

> So why would you have different semantics for implicit `(?int)` cast vs `:
> ?int` function return type cast, if they're both *out*?
> Return type cast has the same semantics as parameter type cast.

I would have to disagree with this as I think of "return" as a language 
construct function that takes one argument. When a return type is 
specified, that return function effectively inherits that type for its 
only argument, and limitations on implicit conversion should apply.

> This looks weird to me. I would expect this last line to throw anyway.
> Having "foo" passed somehow where your code expects an int(ish) or null,
> should not be silenced and converted to `NULL` IMO.
> To me, this last line just says "ignore any error here" - a bit like
> prefixing it with @.

That's exactly what it is, and thanks to null coalescence, you then have 
an easy available ability to either check if it succeeded, or default to 
another value.

$items = (?int)$_GET['items'];
if ($items === null) {
    // it didn't just resort to 0, which might be perfectly valid
    throw new Exception('Unsure how many items');
}

$value = (?string)$_GET['name'] ?? '';
if ($value === '') {
   // also, ?name[]= can sod off
   throw new Exception('You must provide a name');
}

--
Mark Randall

---

Next page: https://externals.io/message/105122.md?page=2
