Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:127204 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by qa.php.net (Postfix) with ESMTPS id 557C71A00BC for ; Sun, 27 Apr 2025 18:49:39 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1745779641; bh=cI+4Jlsb6fzPmMtB786HED+TRu+Pdi5C7qlo2IarcYQ=; h=Date:From:To:In-Reply-To:References:Subject:From; b=WVD2mHLVhaxwJuPfCtaDujBVvFVH1FbEJmPVJhoEhzJzxiUn+kJXolJ+D25voM2nN VPM2KCdcZCLs2rs0JivJ9gMfdnGr1qtHtKXTWhkXgECoA5hUTLIGCm84OYb/pgy5LM OJXwh59lbg4wOJuEsGH7ybBEFllEJnucKnEmutM7uvZaFaGJtZ7gU1ZzcJiy1hl/6C /r/Qtiyc2HoU0EXYM4rnN0QgNop5gUHlJLCRAUy+Z/rvjkbwv3OThatBIV4HPHXO7g baNBzh6ErfXxoFP9m9qLoFgVZIN3ubghmiOlTunC4PVMG0LCFPZ91tomGvYzboVHWp tPfxbfSAXOg0g== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id EBEC5180068 for ; Sun, 27 Apr 2025 18:47:19 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: Error (Cannot connect to unix socket '/var/run/clamav/clamd.ctl': connect: Connection refused) X-Envelope-From: Received: from fout-b8-smtp.messagingengine.com (fout-b8-smtp.messagingengine.com [202.12.124.151]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Sun, 27 Apr 2025 18:47:19 +0000 (UTC) Received: from phl-compute-05.internal (phl-compute-05.phl.internal [10.202.2.45]) by mailfout.stl.internal (Postfix) with ESMTP id 6B7E8114018A for ; Sun, 27 Apr 2025 14:49:36 -0400 (EDT) Received: from phl-imap-09 ([10.202.2.99]) by phl-compute-05.internal (MEProxy); Sun, 27 Apr 2025 14:49:36 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bottled.codes; h=cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm1; t=1745779776; x=1745866176; bh=ThA60F1oCv WlavDNTUWDS4j4eiEvLUx8qPwlpii2XZk=; b=U9xcqdUNb3rMpYTcWFXjxEY8Y9 pKysvTTNtEV/S2KTOJhbQ4RaYckaejHTSaDo1nEGxHJ17y9KZvGmBz7HN7UeQgCz CO9ISNCHP1njkCtyikOVzEKMfAYAGLiMM/UTP1iQjJ+vonUAjaJ+rNUZVdYOHnCT D1B8wAROuHocaqHYa6669Qgs0VP9yorXgNmDoTUHDGosAdWbKRrdfnYZpPKS5OU5 tKpg+YbITpwg+z8mGiSpQ+H4SdDkUErHRte4HOmqwZ5YnnZKM+xZrmMdxslcveCW zkkyq2yOXsZPlEVleNGrrIFRoOxntJ45f863C+NT1GQNRy+FAgNoFmaRnZ4w== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t= 1745779776; x=1745866176; bh=ThA60F1oCvWlavDNTUWDS4j4eiEvLUx8qPw lpii2XZk=; b=tCmKb2wrxqkkMJzGx0qA2aJhlxjpKYMY4tVdZlB0/Q2uR/2YCit ojPbvGkW8CsRllg1sbRXvZqPDTuH7r29vkPmdlmt8CC7w3lMrnmoyeovDCdvBJBl 5gPHcyWkCn1M2pPCXyRx3CrrQzIux64di1AE6UsprE62OICKVJ/SgsJNHpw/WCp2 Pu97/WrKRGfjJ1M6jScnMFnVTqG0mMczaEgC10wskuEMVl6QwFjKBG4QM31Km+PV D7GU4DtGDaR7BNkSxInaTgdCn6S2cZ6hW1XCtM6oYIuOWSre3w25st5y3FJ4Kjz2 zc2mMDTdVM39XEZCfWhg2jaQAwIbynbYdPQ== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddvheekkeefucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucenucfjughrpefogg ffhffvkfgjfhfutgesrgdtreerredtjeenucfhrhhomhepfdftohgsucfnrghnuggvrhhs fdcuoehrohgssegsohhtthhlvggurdgtohguvghsqeenucggtffrrghtthgvrhhnpedtue ejtdethfeulefhtdelieduteelffdtudelheffgedtieehhfelieejgfevgeenucevlhhu shhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehrohgssegsohhtth hlvggurdgtohguvghspdhnsggprhgtphhtthhopedupdhmohguvgepshhmthhpohhuthdp rhgtphhtthhopehinhhtvghrnhgrlhhssehlihhsthhsrdhphhhprdhnvght X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id CC923780069; Sun, 27 Apr 2025 14:49:35 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 X-ThreadId: Tf8e68f75eb20cb92 Date: Sun, 27 Apr 2025 20:49:15 +0200 To: internals@lists.php.net Message-ID: In-Reply-To: References: <39597a9c-6854-40c6-a529-32b2b178cb27@app.fastmail.com> Subject: Re: [PHP-DEV] Concept: Lightweight error channels Content-Type: multipart/alternative; boundary=3313e48f97c3468ca198e6b54f2ca950 From: rob@bottled.codes ("Rob Landers") --3313e48f97c3468ca198e6b54f2ca950 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Sun, Apr 27, 2025, at 20:06, Larry Garfield wrote: > I'm going to respond to points raised by several people together; I'm = using Ed's message as a starting point but this is also i response to Ni= els, Rob, and Andreas. >=20 > On Sun, Apr 27, 2025, at 3:16 AM, Edmond Dantes wrote: > > Good afternoon, Larry. > > > > Looking at the comparison table, it seems that the two most importan= t=20 > > differences are: > > > > 1. Backtrace consumes a lot of resources. >=20 > Yes. And even if it can be made faster (as it looks like Niels is doi= ng, which is great), it will never be as fast as an empty constructor an= d a return. That's the level I'm proposing. >=20 > > 2. There is an explicit contract for exceptions thrown by a functio= n. >=20 > Yes. >=20 > > 3. I didn't fully understand the point about the exception hierarch= y,=20 > > but it seems important too. >=20 > I somewhat glossed over this point, but let me expand on it here. >=20 > Exceptions currently have a Java-inspired hierarchy. Everything MUST = extend either Error or Exception. The available exceptions are spread a= cross core and SPL, but internals is not supposed to use the SPL ones. =20 >=20 > For the use cases I'm talking about, "InvalidArgumentException" is the= most common case, I expect. Or rather, special cases of that. However= , it extends LogicException, which is specified in the docs as "Exceptio= n that represents error in the program logic." Which is... *wrong*, bec= ause half the time or more an InvalidArgumentException is a case of vali= dating user data that cannot be fully validated by the type system... an= d thus not really a programmer error. It's only a programmer error if t= hey don't handle it gracefully. >=20 > Moreover, all exceptions currently track: >=20 > * message > * code > * file > * line > * trace > * previous exception >=20 > Those are useful when they show up in logs read by a human. For the u= se cases I am describing, none of them are relevant, useful, or desireab= le. These values are for programmatic use only. But all of those are b= aked into the exception system at its core. Just having a third "kind" = of exception (in addition to `extends Error` and `extends Exception`) wo= uld not really solve anything. >=20 > > The issue with the missing contract could have been solved even for=20 > > exceptions, without creating a new entity. >=20 > This is incorrect, as making the current exception system checked woul= d be a ginormous BC break. And having some throwables be checked and so= me not, but using the same syntax and class hierarchy... well, that's th= e reason everyone detests Java's exceptions. Let's not do that. >=20 > > Regarding Backtrace, the following questions seem fair: > > > > 1. What if I want to know where the situation occurred? Can I just=20 > > ignore this information? >=20 > That is completely and utterly irrelevant, just as it is completely an= d utterly irrelevant on which line a `return` statement appeared. >=20 > I think the framing of this concept as "lighter exceptions" is the wro= ng lens. I probably contributed to that in my initial explanation, so l= et me try and clarify: >=20 > What I am proposing is not "Better exceptions." It's "a second kind o= f return value." It's closer to Rust's Result types or Go's multi-retur= ns, but spelled differently. That is, it turns out (see the Error Model= article previously), logically identical to checked exceptions. But if= the mental model of "what is an exception" is what PHP has today, then = that is more misleading than helpful. So let's not talk of that further. >=20 > Rather, consider this example: >=20 > class Repo { > public function getUser(int $id): User { > $record =3D $this->db->query("...")->fetchOne(); > if ($record) { > return new User(...$record); > } > // Uh, now what? > } > } >=20 > There's various ways to handle that case. Sticking an exception at th= e end of the method is one option, but as I've pointed out, a particular= ly bad one. "That user isn't here" is not an error case that should be = able to silently crash the application, just because someone put an inva= lid number in a URL. >=20 > We could make the return value ?User and then return null, but now we = have to manually check for null every frickin' time we call the method, = or we get random null errors in who knows where. And it doesn't let us = differentiate between "user not found" and "that is a negative int, whic= h is never correct you idiot." >=20 > Suppose a hypothetical world where we had generics: >=20 > // We literally need *nothing* on these classes other than their type. > // More complex cases might, but in this case, anything more than this= is a waste. > interface RepoErr {} > class NoSuchUser implements RepoErr {} > class NegativeId implements RepoErr {} >=20 > class Repo { > public function getUser(int $id): Result { > if ($id <=3D 0) { > return new Result::err(new NegativeId()); > } > $record =3D $this->db->query("...")->fetchOne(); > if ($record) { > return new Result::ok(new User(...$record)); > } > return new Result::err(new NoSuchUser()); > } > } >=20 > And then to use it, you MUST do: >=20 > function doStuff($id) { > $ret =3D $repo->getUser($id); > if ($user instanceof OK) { > $user =3D $ret->value; > } else { > if ($ret->err instanceof NoSuchUser) { > display_user_message('Who is that?'); > return; > } else if ($ret->err instanceof NegativeId) { > display_user_message('Buddy, that's not a thing."); > return; > } > } > // If you got here, it means the getUser call way way way up there was= valid. > } >=20 > I think it's reasonably obvious that is extremely clumsy, which is why= almost no one in PHP does it, including FP stans like me. Plus that wh= ole generics thing. >=20 > What I've started doing is using union returns as a sort of "naked res= ult": >=20 > class Repo { > public function getUser(int $id): User|UserErr { > if ($id <=3D 0) { > return new new NegativeId(); > } > $record =3D $this->db->query("...")->fetchOne(); > if ($record) { > return new User(...$record); > } > return new NoSuchUser(); > } > } >=20 > function doStuff($id) { > $user =3D $repo->getUser($id); > if ($user instanceof UserErr) { > ... > } > $user is now usable. > } >=20 > But that relies on me always remembering to do that check, and means s= tatic analysis tools can't know what the type of $user is until I check = it. Better, still not great. >=20 > What I am proposing is to take the Result type example and change the = spelling to: >=20 > class Repo { > // We get reliable type information without generics! > public function getUser(int $id): User raises UserErr { > if ($id <=3D 0) { > raise new NegativeId(); > } > $record =3D $this->db->query("...")->fetchOne(); > if ($record) { > return new User(...$record); > } > raise new NoSuchUser(); > } > } >=20 > And then something reasonable on the receiving side, which I've not fu= lly thought through yet. The whole point of this thread is "should I ta= ke the time to think that through?" :-) >=20 > If we used try-catch or something structured the same, then we at leas= t get: >=20 > function doStuff($id) { > try { > $user =3D $repo->getUser($id); > // Code that uses $user, which is guaranteed to be a User. > return ...; > } catch (NoSuchUser) { // Note not capturing the value, as we don't= need it in this case. > display_user_message('Who is that?'); > } catch (NegativeId) { > display_user_message('Buddy, that's not a thing."); > } > } >=20 > I'm not convinced that's the right syntax, but it's a possible syntax.= If you want to defer handling of a particular error to the caller, the= n you would explicitly do this: >=20 > function doStuff($id): string raises UserErr { > try { > $user =3D $repo->getUser($id); > // Code that uses $user, which is guaranteed to be a User. > return ...; > } catch (UserErr $e) { > raise $e; > } >=20 > Which is why I think we do want some kind of syntax similar to Rust's = ?, so the above could be shortened back to this: >=20 > function doStuff($id): string raises UserErr { > $user =3D $repo->getUser($id) reraise; > // We have a good user. > } >=20 > If you try to raise an error from a function that doesn't specify it..= . that is exactly the same as trying to return an array from that functi= on. `return array` would be a type error on the success channel. `rais= e new ProductErr` would be a type error on the failure channel. Same id= ea. >=20 > Again, I don't think try-catch in its current form is ideal. I'm not = sure what is. I'm trying to decide if it would be a waste of my time to= figure out what would be better. But again, this is *not an exception*= . This is a second return channel, aka a different way to spell a Resul= t type, such that we don't need a clumsy Result type. >=20 > --Larry Garfield >=20 Hmmm, Reminds me of working on wordpress's backend, where you would write some= thing like function get_user(): WP_User|WP_Error -- or something like that (it's been a long time). But if it was an exceptional error, you'd just throw. But, you'd have to= write something like this every time you called it: if (($user =3D get_user()) instanceof WP_Error) { /* handle error */ } // $user is WP_User What you're suggesting is basically providing this via a separate "track= " or "channel" so it would look like: function get_user(): WP_User raises WP_Error {} $user =3D get_user() reraise; I understand that these are mostly lightweight, programatical errors, no= t exceptions. So, for example, running out of disk space, "not found" re= sults, etc. These are things you should just handle ... not report. Howe= ver, there are cases where these do become exceptional further up the st= ack. For example, if you are supposed to be storing a profile image and = you cannot recover from a full disk or a user that the client thinks exi= sts -- you probably want to turn them into an exception. Maybe something= like this: $user =3D get_user() reraise (WP_Error as LogicException); where you can specify an error to be wrapped in an exception. The stack = trace and everything would come from this line, not where the error actu= ally came from. That shouldn't be an issue though, in most code. =E2=80=94 Rob --3313e48f97c3468ca198e6b54f2ca950 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable


On Sun, Apr 27, 2025, at 20:06, Larry Garfield wrote:<= /div>
I'm going to re= spond to points raised by several people together; I'm using Ed's messag= e as a starting point but this is also i response to Niels, Rob, and And= reas.

On Sun, Apr 27, 2025, at 3:16 AM, Edmond = Dantes wrote:
> Good afternoon, Larry.
>
=
> Looking at the comparison table, it seems that the two most im= portant 
> differences are:
>
&= gt;  1. Backtrace consumes a lot of resources.

=
Yes.  And even if it can be made faster (as it looks like Niel= s is doing, which is great), it will never be as fast as an empty constr= uctor and a return.  That's the level I'm proposing.

=
>  2. There is an explicit contract for exceptions th= rown by a function.

Yes.

>  3. I didn't fully understand the point about the exception= hierarchy, 
> but it seems important too.
<= br>
I somewhat glossed over this point, but let me expand on i= t here.

Exceptions currently have a Java-inspir= ed hierarchy.  Everything MUST extend either Error or Exception.&nb= sp; The available exceptions are spread across core and SPL, but interna= ls is not supposed to use the SPL ones.  

=
For the use cases I'm talking about, "InvalidArgumentException" is = the most common case, I expect.  Or rather, special cases of that.&= nbsp; However, it extends LogicException, which is specified in the docs= as "Exception that represents error in the program logic."  Which = is... *wrong*, because half the time or more an InvalidArgumentException= is a case of validating user data that cannot be fully validated by the= type system... and thus not really a programmer error.  It's only = a programmer error if they don't handle it gracefully.

Moreover, all exceptions currently track:

* message
* code
* file
* line
* trace
* previous exception

Those = are useful when they show up in logs read by a human.  For the use = cases I am describing, none of them are relevant, useful, or desireable.=   These values are for programmatic use only.  But all of thos= e are baked into the exception system at its core.  Just having a t= hird "kind" of exception (in addition to `extends Error` and `extends Ex= ception`) would not really solve anything.

>= The issue with the missing contract could have been solved even for&nbs= p;
> exceptions, without creating a new entity.
<= br>
This is incorrect, as making the current exception system = checked would be a ginormous BC break.  And having some throwables = be checked and some not, but using the same syntax and class hierarchy..= . well, that's the reason everyone detests Java's exceptions.  Let'= s not do that.

> Regarding Backtrace, the fo= llowing questions seem fair:
>
>  1. What= if I want to know where the situation occurred? Can I just 
<= div>> ignore this information?

That is compl= etely and utterly irrelevant, just as it is completely and utterly irrel= evant on which line a `return` statement appeared.

<= div>I think the framing of this concept as "lighter exceptions" is the w= rong lens.  I probably contributed to that in my initial explanatio= n, so let me try and clarify:

What I am proposi= ng is not "Better exceptions."  It's "a second kind of return value= ."  It's closer to Rust's Result types or Go's multi-returns, but s= pelled differently.  That is, it turns out (see the Error Model art= icle previously), logically identical to checked exceptions.  But i= f the mental model of "what is an exception" is what PHP has today, then= that is more misleading than helpful.  So let's not talk of that f= urther.

Rather, consider this example:

class Repo {
  public function getUser(i= nt $id): User {
    $record =3D $this->db-&g= t;query("...")->fetchOne();
    if ($record)= {
      return new User(...$record);=
    }
    // Uh, now = what?
  }
}

There's va= rious ways to handle that case.  Sticking an exception at the end o= f the method is one option, but as I've pointed out, a particularly bad = one.  "That user isn't here" is not an error case that should be ab= le to silently crash the application, just because someone put an invali= d number in a URL.

We could make the return val= ue ?User and then return null, but now we have to manually check for nul= l every frickin' time we call the method, or we get random null errors i= n who knows where.  And it doesn't let us differentiate between "us= er not found" and "that is a negative int, which is never correct you id= iot."

Suppose a hypothetical world where we had= generics:

// We literally need *nothing* on th= ese classes other than their type.
// More complex cases might= , but in this case, anything more than this is a waste.
interf= ace RepoErr {}
class NoSuchUser implements RepoErr {}
class NegativeId implements RepoErr {}

class = Repo {
  public function getUser(int $id): Result<User= , RepoErr> {
   if ($id <=3D 0) {
&n= bsp;     return new Result::err(new NegativeId());
    }
    $record =3D $= this->db->query("...")->fetchOne();
   = ; if ($record) {
      return new Res= ult::ok(new User(...$record));
    }
=     return new Result::err(new NoSuchUser());
&= nbsp; }
}

And then to use it, you MUS= T do:

function doStuff($id) {
  = $ret =3D $repo->getUser($id);
  if ($user instanceof O= K) {
    $user =3D $ret->value;
&n= bsp; } else {
    if ($ret->err instanceof N= oSuchUser) {
      display_user_messa= ge('Who is that?');
      return;
    } else if ($ret->err instanceof NegativeId)= {
       display_user_message('= Buddy, that's not a thing.");
     &n= bsp; return;
    }
  }
// If you got here, it means the getUser call way way way up there was = valid.
}

I think it's reasonably obvi= ous that is extremely clumsy, which is why almost no one in PHP does it,= including FP stans like me.  Plus that whole generics thing.
=

What I've started doing is using union returns as a = sort of "naked result":

class Repo {
=   public function getUser(int $id): User|UserErr {
 =   if ($id <=3D 0) {
      ret= urn new new NegativeId();
    }
 = ;   $record =3D $this->db->query("...")->fetchOne();<= /div>
    if ($record) {
   =    return new User(...$record);
    }=
    return new NoSuchUser();
  = }
}

function doStuff($id) {
  $user =3D $repo->getUser($id);
  if ($user ins= tanceof UserErr) {
    ...
  }
  $user is now usable.
}

But that relies on me always remembering to do that check, and means s= tatic analysis tools can't know what the type of $user is until I check = it.  Better, still not great.

What I am pr= oposing is to take the Result type example and change the spelling to:

class Repo {
  // We get reliable= type information without generics!
  public function get= User(int $id): User raises UserErr {
   if ($id <= =3D 0) {
      raise new NegativeId()= ;
    }
    $record =3D= $this->db->query("...")->fetchOne();
  &nb= sp; if ($record) {
      return new U= ser(...$record);
    }
  &n= bsp;   raise new NoSuchUser();
  }
}<= /div>

And then something reasonable on the receiving = side, which I've not fully thought through yet.  The whole point of= this thread is "should I take the time to think that through?" :-)

If we used try-catch or something structured the sa= me, then we at least get:

function doStuff($id)= {
  try {
    $user =3D $repo-&= gt;getUser($id);
    // Code that uses $user, w= hich is guaranteed to be a User.
    return ...= ;
  } catch (NoSuchUser) {  // Note not capturing th= e value, as we don't need it in this case.
   &= nbsp; display_user_message('Who is that?');
  } catch (Ne= gativeId) {
    display_user_message('Buddy, th= at's not a thing.");
  }
}

=
I'm not convinced that's the right syntax, but it's a possible synt= ax.  If you want to defer handling of a particular error to the cal= ler, then you would explicitly do this:

functio= n doStuff($id): string raises UserErr {
  try {
    $user =3D $repo->getUser($id);
 &n= bsp;  // Code that uses $user, which is guaranteed to be a User.
    return ...;
  } catch (UserErr= $e) {
    raise $e;
}

=
Which is why I think we do want some kind of syntax similar t= o Rust's ?, so the above could be shortened back to this:

=
function doStuff($id): string raises UserErr {
&nbs= p; $user =3D $repo->getUser($id) reraise;
  // We have= a good user.
}

If you try to raise a= n error from a function that doesn't specify it... that is exactly the s= ame as trying to return an array from that function.  `return array= ` would be a type error on the success channel.  `raise new Product= Err` would be a type error on the failure channel.  Same idea.

Again, I don't think try-catch in its current form = is ideal.  I'm not sure what is.  I'm trying to decide if it w= ould be a waste of my time to figure out what would be better.  But= again, this is *not an exception*.  This is a second return channe= l, aka a different way to spell a Result type, such that we don't need a= clumsy Result type.

--Larry Garfield


Hmmm,

Reminds me of working on wordpress's backend, where you would write s= omething like

function get_user(): WP_User|WP_E= rror

-- or something like that (it's been a lon= g time).

But if it was an exceptional error, yo= u'd just throw. But, you'd have to write something like this every time = you called it:

if (($user =3D get_user()) insta= nceof WP_Error) { /* handle error */ }
// $user is WP_User

What you're suggesting is basically providing this= via a separate "track" or "channel" so it would look like:
function get_user(): WP_User raises WP_Error {}
<= br>
$user =3D get_user() reraise;

I u= nderstand that these are mostly lightweight, programatical errors, not e= xceptions. So, for example, running out of disk space, "not found" resul= ts, etc. These are things you should just handle ... not report. However= , there are cases where these do become exceptional further up the stack= . For example, if you are supposed to be storing a profile image and you= cannot recover from a full disk or a user that the client thinks exists= -- you probably want to turn them into an exception. Maybe something li= ke this:

$user =3D get_user() reraise (WP_Error= as LogicException);

where you can specify an e= rror to be wrapped in an exception. The stack trace and everything would= come from this line, not where the error actually came from. That shoul= dn't be an issue though, in most code.

=E2=80=94 Rob
--3313e48f97c3468ca198e6b54f2ca950--