From: Austin Ziegler Date: 2020-11-17T16:46:10-05:00 Subject: [ruby-core:100921] Re: [Ruby master Feature#17326] Add Kernel#must! to the standard library --===============1205456113== Content-Type: multipart/alternative; boundary="0000000000004e8d0405b4546d15" --0000000000004e8d0405b4546d15 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable There=E2=80=99s a cognitive cost to every new method added to Ruby objects,= and when adding to Kernel, that cost is higher as it affects all objects and is *also* usable as a bare function. This latter suggests that you don=E2=80=99t actually want `Kernel#must!`, b= ut `Object#must!` and `Nil#must!` or even `BasicObject#must!`, as `must!` by itself would be `self.must!`, and would change per context. I=E2=80=99ve already said this, but I think that this has no place in Ruby= =E2=80=99s core method definitions as opposed to how Sorbet already provides it or how it could be provided as a common types assertion library used by Sorbet and other type checkers. I would have no problem with a bundled/default gem being added to Ruby that provides `T.must` and other common type checking tests (as suggested by zverok https://bugs.ruby-lang.org/issues/17326#note-10), but I don=E2=80=99t think= that the ability to use `must` in a point-free style is a sufficient reason to add this as a default method to Ruby objects with special behaviour on `nil`. -a On Tue, Nov 17, 2020 at 4:13 PM wrote: > Issue #17326 has been updated by jez (Jake Zimmerman). > > > jeremyevans0 (Jeremy Evans) wrote in #note-12: > > > the benefit of adding it is smaller than the cost of adding another > method to Kernel > > Can you speak more on the const of adding a method to Kernel? While I > understand the costs of something new syntax would bring, I am less > familiar with the cost to adding something to the standard library. > > The benefit to having something in the standard library instead of a gem > is specifically to make it a common idiom shared across typed and non-typ= ed > Ruby codebases. `T.must` already exists in `sorbet-runtime` for the peopl= e > who want to only use Sorbet. I am of the belief that third party gems > should refrain from monkey patching new methods into standard library > classes, which is why I proposed a change to the standard library directl= y. > > jeremyevans0 (Jeremy Evans) wrote in #note-12: > > > I think that outside of Stripe/Sorbet users (a small fraction of Ruby > programmers), .must! meaning raise if nil? is not intuitive. > > Again, I'm 100% fine to change the name to make it more intuitive. > `.not_nil`, `.not_nil!`, `.unwrap_nil`, `.drop_nil`, etc. If the only > blocker is the name, that is great: I will agree to any name. I am very > open to further suggestions of what the name should be. > > ---------------------------------------- > Feature #17326: Add Kernel#must! to the standard library > https://bugs.ruby-lang.org/issues/17326#change-88571 > > * Author: jez (Jake Zimmerman) > * Status: Open > * Priority: Normal > ---------------------------------------- > # Abstract > > We should add a method `Kernel#must!` (name TBD) which raises if `self` i= s > `nil` and returns `self` otherwise. > > > # Background > > Ruby 3 introduces type annotations for the standard library. > Type checkers consume these annotations, and report errors for type > mismatches. > One of the most common and most valuable type errors is whether `nil` is > allowed as an argument or return value. > Sorbet's type system tracks this, and RBS files have syntax for annotatin= g > whether `nil` is allowed or not. > > Since Sorbet checks proper usage of `nil`, it requires code that looks > like this: > > ```ruby > if thing.nil? > raise "The thing was nil" > end > > thing.do_something > ``` > > This is good because it forces the programmer to acknowledge that the > thing might be `nil`, and declare > that they'd rather raise an exception in that case than handle the `nil` > (of course, there are many other > times where `nil` is both possible and valid, which is why Sorbet forces > at least considering in all cases). > > It is annoying and repetitive to have to write these `if .nil?` checks > everywhere to ignore the type error, > so Sorbet provides it as a library function, called `T.must`: > > ```ruby > T.must(thing).do_something > ``` > > Sorbet knows that the call to `T.must` raises if `thing` is `nil`. > To make this very concrete, here's a Sorbet playground where you can see > this in action: > > [=E2=86=92 View on sorbet.run]( > https://sorbet.run/#%23%20typed%3A%20true%0Aextend%20T%3A%3ASig%0A%0Aclas= s%20Thing%0A%20%20def%20do_something%3B%20end%0Aend%0A%0Asig%20%7Bparams(th= ing%3A%20T.nilable(Thing)).void%7D%0Adef%20example1(thing)%0A%20%20%23%20er= ror%2C%20might%20be%20nil%3A%0A%20%20thing.do_something%0Aend%0A%0Asig%20%7= Bparams(thing%3A%20T.nilable(Thing)).void%7D%0Adef%20example2(thing)%0A%20%= 20if%20thing.nil%3F%0A%20%20%20%20raise%20%22The%20thing%20was%20nil%22%0A%= 20%20end%0A%0A%20%20%23%20no%20error%2C%20because%20it's%20after%20the%20%6= 0if%20.nil%3F%60%20check%3A%0A%20%20thing.do_something%0Aend%0A%0Asig%20%7B= params(thing%3A%20T.nilable(Thing)).void%7D%0Adef%20example3(thing)%0A%20%2= 0%23%20no%20error%2C%20because%20it's%20after%20the%20%60if%20.nil%3F%60%20= check%3A%0A%20%20T.must(thing).do_something%0Aend > ) > > You can read more about `T.must` in the [Sorbet documentation]( > https://sorbet.org/docs/type-assertions#tmust). > > > # Problem > > While `T.must` works, it is not ideal for a couple reasons: > > 1. It leads to a weird outward spiral of flow control, which disrupts > method chains: > > ```ruby > # =E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=90 > # =E2=94=82 =E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =90 =E2=94=82 > # =E2=96=BC =E2=96=BC =E2=94=82 =E2=94=82 > T.must(T.must(task).mailing_params).fetch('template_context') > # =E2=94=82 =E2=94=82 =E2=96=B2 =E2=96=B2 > # =E2=94=82 =E2=94=94=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=98 =E2= =94=82 > # =E2=94=94=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=98 > ``` > > compare that control flow with this: > > ```ruby > # =E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=90=E2=94=8C=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=90=E2=94=8C=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=90=E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=90 > # =E2=94=82 =E2=96=BC=E2=94=82 =E2=96=BC=E2=94=82 = =E2=96=BC=E2=94=82 =E2=96=BC > task.must!.mailing_params.must!.fetch('template_context') > ``` > > 2. It is not a method, so you can't `map` it over a list using > `Symbol#to_proc`. Instead, you have to expand the block: > > ```ruby > array_of_integers =3D array_of_nilable_integers.map {|x| T.must(x) } > ``` > > Compare that with this: > > ```ruby > array_of_integers =3D array_of_nilable_integers.map(&:must!) > ``` > > 3. It is in a Sorbet-specific gem. We do not intend for Sorbet to be the > only type checker. > It would be nice to have such a method in the Ruby standard library s= o > that it can be shared by all type checkers. > > 4. This method can make Ruby codebases that **don't** use type checkers > more robust! > `Kernel#must!` could be an easy way to assert invariants early. > Failing early makes it more likely that a test will fail, rather than > getting `TypeError`'s and `NoMethodError`'s in production. > This makes all Ruby code better, not just the Ruby code using types. > > > # Proposal > > We should extend the Ruby standard library with something like this:: > > ```ruby > module Kernel > def must!; self; end > end > > class NilClass > def must! > raise TypeError.new("nil.must!") > end > end > ``` > > These methods would get type annotations that look like this: > (using Sorbet's RBI syntax, because I don't know RBS well yet) > > ```ruby > module Kernel > sig {returns(T.self_type)} > def must!; end > end > > class NilClass > sig {returns(T.noreturn)} > def must!; end > end > ``` > > What these annotations say: > > - In `Kernel#must!`, the return value is `T.self_type`, or "whatever the > type of the receiver was." > That means that `0.must!` will have type `Integer`, `"".must!` will hav= e > type `String`, etc. > > - In `NilClass#must!`, there is an override of `Kernel#must!` with return > type `T.noreturn`. > This is a fancy type that says "this code either infinitely loops or > raises an exception." > This is the name for Sorbet's [bottom type]( > https://en.wikipedia.org/wiki/Bottom_type), if you > are familiar with that terminology. > > Here is a Sorbet example where you can see how these annotations behave: > > [=E2=86=92 View on sorbet.run]( > https://sorbet.run/#%23%20typed%3A%20true%0A%0Amodule%20Kernel%0A%20%20T%= 3A%3ASig%3A%3AWithoutRuntime.sig%20%7Breturns(T.self_type)%7D%0A%20%20def%2= 0must!%3B%20self%3B%20end%0Aend%0A%0Aclass%20NilClass%0A%20%20T%3A%3ASig%3A= %3AWithoutRuntime.sig%20%7Breturns(T.noreturn)%7D%0A%20%20def%20must!%0A%20= %20%20%20raise%20TypeError.new(%22nil.must!%22)%0A%20%20end%0Aend%0A%0Axs%2= 0%3D%20T%3A%3AArray%5BInteger%5D.new(%5B0%5D)%0AT.reveal_type(xs.first)%20%= 20%20%20%20%20%20%23%20T.nilable(Integer)%0AT.reveal_type(xs.first.must!)%2= 0%23%20Integer%0A%0Ays%20%3D%20T%3A%3AArray%5BT.nilable(Integer)%5D.new(%5B= 0%2C%20nil%2C%201%2C%20nil%2C%202%5D)%0AT.reveal_type(ys)%20%20%20%20%20%20= %20%20%20%20%20%20%20%20%23%20T%3A%3AArray%5BT.nilable(Integer)%5D%0AT.reve= al_type(ys.map(%26%3Amust!))%20%23%20T%3A%3AArray%5BInteger%5D > ) > > # Alternatives considered > > There was some discussion of this feature at the Feb 2020 Ruby Types > discussion: > > Summarizing: > > - Sorbet team frequently recommends people to use `xs.fetch(0)` instead o= f > `T.must(xs[0])` > on `Array`'s and `Hash`'s because it chains and reads better. > `.fetch` not available on other classes. > > - It's intentional that `T.must` requires as many characters as it does. > Making it slightly annoying to type encourages developers to refactor > their code so that `nil` never occurs. > > - There was a proposal to introduce new syntax like `thing.!!`. This is > currently a syntax error. > > **Rebuttal**: There is burden to introducing new syntax. Tools like > Rubocop, Sorbet, and syntax highlighting > plugins have to be updated. Also: it is hard to search for on Google (a= s > a new Ruby developer). Also: it > is very short=E2=80=94having something slightly shorter makes people th= ink about > whether they want to type it out > instead of changing the code so that `nil` can't occur. > > Another alternative would be to dismiss this as "not useful / common > enough". I don't think that's true. > Here are some statistics from Stripe's Ruby monolith (~10 million lines o= f > code): > > | methood | percentage of files mentioning method | number of occurrences > of method | > | --- | --- | --- | > | `.nil?` | 16.69% | 31340 | > | `T.must` | 23.89% | 74742 | > > From this, we see that > > - `T.must` is in 1.43x more files than `.nil?` > - `T.must` occurs 2.38x more often than `.nil?` > > > # Naming > > I prefer `must!` because it is what the method in Sorbet is already calle= d. > > I am open to naming suggestions. Please provide reasoning. > > > # Discussion > > In the above example, I used `T.must` twice. An alternative way to have > written that would have been using save navigation: > > ```ruby > T.must(task&.mailing_params).fetch('template_context') > ``` > > This works as well. The proposed `.must!` method works just as well when > chaining methods with safe navigation: > > ```ruby > task&.mailing_params.must!.fetch('template_context') > ``` > > However, there is still merit in using `T.must` (or `.must!`) twice=E2=80= =94it > calls out that the programmer > intended neither location to be `nil`. In fact, if this method had been > chained across multiple lines, > the backtrace would include line numbers saying specifically **which** > `.must!` failed: > > > ```ruby > task.must! > .mailing_params.must! > .fetch('template_context') > ``` > > > > > -- > https://bugs.ruby-lang.org/ > > Unsubscribe: > > --=20 Austin Ziegler =E2=80=A2 halostatue@gmail.com =E2=80=A2 austin@halostatue.c= a http://www.halostatue.ca/ =E2=80=A2 http://twitter.com/halostatue --0000000000004e8d0405b4546d15 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
There=E2=80=99s a cognitive cost to every= new method added to Ruby objects, and when adding to Kernel, that cost is = higher as it affects all objects and is also=C2=A0usable as a bare f= unction.

This latter suggests that you don=E2=80=99t act= ually want `Kernel#must!`, but `Object#must!` and `Nil#must!` or even `Basi= cObject#must!`, as `must!` by itself would be `self.must!`, and would chang= e per context.

I=E2=80=99ve already said this, but= I think that this has no place in Ruby=E2=80=99s core method definitions a= s opposed to how Sorbet already provides it or how it could be provided as = a common types assertion library used by Sorbet and other type checkers. I = would have no problem with a bundled/default gem being added to Ruby that p= rovides `T.must` and other common type checking tests (as suggested by zver= ok https://bugs= .ruby-lang.org/issues/17326#note-10), but I don=E2=80=99t think that th= e ability to use `must` in a point-free style is a sufficient reason to add= this as a default method to Ruby objects with special behaviour on `nil`.<= /div>

-a

On Tue, Nov 17, 2020 at 4:13 PM <= zimmerman.jak= e@gmail.com> wrote:
Issue #17326 has been = updated by jez (Jake Zimmerman).


jeremyevans0 (Jeremy Evans) wrote in #note-12:

> the benefit of adding it is smaller than the cost of adding another me= thod to Kernel

Can you speak more on the const of adding a method to Kernel? While I under= stand the costs of something new syntax would bring, I am less familiar wit= h the cost to adding something to the standard library.

The benefit to having something in the standard library instead of a gem is= specifically to make it a common idiom shared across typed and non-typed R= uby codebases. `T.must` already exists in `sorbet-runtime` for the people w= ho want to only use Sorbet. I am of the belief that third party gems should= refrain from monkey patching new methods into standard library classes, wh= ich is why I proposed a change to the standard library directly.

jeremyevans0 (Jeremy Evans) wrote in #note-12:

> I think that outside of Stripe/Sorbet users (a small fraction of Ruby = programmers), .must! meaning raise if nil? is not intuitive.

Again, I'm 100% fine to change the name to make it more intuitive. `.no= t_nil`, `.not_nil!`, `.unwrap_nil`, `.drop_nil`, etc. If the only blocker i= s the name, that is great: I will agree to any name. I am very open to furt= her suggestions of what the name should be.

----------------------------------------
Feature #17326: Add Kernel#must! to the standard library
https://bugs.ruby-lang.org/issues/17326#change-8= 8571

* Author: jez (Jake Zimmerman)
* Status: Open
* Priority: Normal
----------------------------------------
# Abstract

We should add a method `Kernel#must!` (name TBD) which raises if `self` is = `nil` and returns `self` otherwise.


# Background

Ruby 3 introduces type annotations for the standard library.
Type checkers consume these annotations, and report errors for type mismatc= hes.
One of the most common and most valuable type errors is whether `nil` is al= lowed as an argument or return value.
Sorbet's type system tracks this, and RBS files have syntax for annotat= ing whether `nil` is allowed or not.

Since Sorbet checks proper usage of `nil`, it requires code that looks like= this:

```ruby
if thing.nil?
=C2=A0 raise "The thing was nil"
end

thing.do_something
```

This is good because it forces the programmer to acknowledge that the thing= might be `nil`, and declare
that they'd rather raise an exception in that case than handle the `nil= ` (of course, there are many other
times where `nil` is both possible and valid, which is why Sorbet forces at= least considering in all cases).

It is annoying and repetitive to have to write these `if .nil?` checks ever= ywhere to ignore the type error,
so Sorbet provides it as a library function, called `T.must`:

```ruby
T.must(thing).do_something
```

Sorbet knows that the call to `T.must` raises if `thing` is `nil`.
To make this very concrete, here's a Sorbet playground where you can se= e this in action:

[=E2=86=92 View on sorbet.run](https://sorbet= .run/#%23%20typed%3A%20true%0Aextend%20T%3A%3ASig%0A%0Aclass%20Thing%0A%20%= 20def%20do_something%3B%20end%0Aend%0A%0Asig%20%7Bparams(thing%3A%20T.nilab= le(Thing)).void%7D%0Adef%20example1(thing)%0A%20%20%23%20error%2C%20might%2= 0be%20nil%3A%0A%20%20thing.do_something%0Aend%0A%0Asig%20%7Bparams(thing%3A= %20T.nilable(Thing)).void%7D%0Adef%20example2(thing)%0A%20%20if%20thing.nil= %3F%0A%20%20%20%20raise%20%22The%20thing%20was%20nil%22%0A%20%20end%0A%0A%2= 0%20%23%20no%20error%2C%20because%20it's%20after%20the%20%60if%20.nil%3= F%60%20check%3A%0A%20%20thing.do_something%0Aend%0A%0Asig%20%7Bparams(thing= %3A%20T.nilable(Thing)).void%7D%0Adef%20example3(thing)%0A%20%20%23%20no%20= error%2C%20because%20it's%20after%20the%20%60if%20.nil%3F%60%20check%3A= %0A%20%20T.must(thing).do_something%0Aend)

You can read more about `T.must` in the [Sorbet documentation](https://sorbet.org/docs/type-assertions#tmust).


# Problem

While `T.must` works, it is not ideal for a couple reasons:

1.=C2=A0 It leads to a weird outward spiral of flow control, which disrupts= method chains:

=C2=A0 =C2=A0 ```ruby
=C2=A0 =C2=A0 # =E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=90
=C2=A0 =C2=A0 # =E2=94=82=C2=A0 =C2=A0 =C2=A0 =E2=94=8C=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=90=C2=A0 =C2=A0 =C2=A0=E2=94=82
=C2=A0 =C2=A0 # =E2=96=BC=C2=A0 =C2=A0 =C2=A0 =E2=96=BC=C2=A0 =C2=A0 =E2=94= =82=C2=A0 =C2=A0 =C2=A0=E2=94=82
=C2=A0 =C2=A0 T.must(T.must(task).mailing_params).fetch('template_conte= xt')
=C2=A0 =C2=A0 # =E2=94=82=C2=A0 =C2=A0 =C2=A0 =E2=94=82=C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =E2=96=B2=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0=E2=96=B2
=C2=A0 =C2=A0 # =E2=94=82=C2=A0 =C2=A0 =C2=A0 =E2=94=94=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=98=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0=E2=94=82
=C2=A0 =C2=A0 # =E2=94=94=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=98
=C2=A0 =C2=A0 ```

=C2=A0 =C2=A0 compare that control flow with this:

=C2=A0 =C2=A0 ```ruby
=C2=A0 =C2=A0 # =E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=90=E2= =94=8C=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=90=E2=94=8C=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=90=E2=94=8C=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=90
=C2=A0 =C2=A0 # =E2=94=82=C2=A0 =C2=A0 =E2=96=BC=E2=94=82=C2=A0 =C2=A0 =E2= =96=BC=E2=94=82=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0=E2=96=BC=E2= =94=82=C2=A0 =C2=A0 =E2=96=BC
=C2=A0 =C2=A0 =C2=A0 task.must!.mailing_params.must!.fetch('template_co= ntext')
=C2=A0 =C2=A0 ```

2.=C2=A0 It is not a method, so you can't `map` it over a list using `S= ymbol#to_proc`. Instead, you have to expand the block:

=C2=A0 =C2=A0 ```ruby
=C2=A0 =C2=A0 array_of_integers =3D array_of_nilable_integers.map {|x| T.mu= st(x) }
=C2=A0 =C2=A0 ```

=C2=A0 =C2=A0 Compare that with this:

=C2=A0 =C2=A0 ```ruby
=C2=A0 =C2=A0 array_of_integers =3D array_of_nilable_integers.map(&:mus= t!)
=C2=A0 =C2=A0 ```

3.=C2=A0 It is in a Sorbet-specific gem. We do not intend for Sorbet to be = the only type checker.
=C2=A0 =C2=A0 It would be nice to have such a method in the Ruby standard l= ibrary so that it can be shared by all type checkers.

4.=C2=A0 This method can make Ruby codebases that **don't** use type ch= eckers more robust!
=C2=A0 =C2=A0 `Kernel#must!` could be an easy way to assert invariants earl= y.
=C2=A0 =C2=A0 Failing early makes it more likely that a test will fail, rat= her than getting `TypeError`'s and `NoMethodError`'s in production.=
=C2=A0 =C2=A0 This makes all Ruby code better, not just the Ruby code using= types.


# Proposal

We should extend the Ruby standard library with something like this::

```ruby
module Kernel
=C2=A0 def must!; self; end
end

class NilClass
=C2=A0 def must!
=C2=A0 =C2=A0 raise TypeError.new("nil.must!")
=C2=A0 end
end
```

These methods would get type annotations that look like this:
(using Sorbet's RBI syntax, because I don't know RBS well yet)

```ruby
module Kernel
=C2=A0 sig {returns(T.self_type)}
=C2=A0 def must!; end
end

class NilClass
=C2=A0 sig {returns(T.noreturn)}
=C2=A0 def must!; end
end
```

What these annotations say:

- In `Kernel#must!`, the return value is `T.self_type`, or "whatever t= he type of the receiver was."
=C2=A0 That means that `0.must!` will have type `Integer`, `"".mu= st!` will have type `String`, etc.

- In `NilClass#must!`, there is an override of `Kernel#must!` with return t= ype `T.noreturn`.
=C2=A0 This is a fancy type that says "this code either infinitely loo= ps or raises an exception."
=C2=A0 This is the name for Sorbet's [bottom type](http= s://en.wikipedia.org/wiki/Bottom_type), if you
=C2=A0 are familiar with that terminology.

Here is a Sorbet example where you can see how these annotations behave:
[=E2=86=92 View on sorbet.run](https://s= orbet.run/#%23%20typed%3A%20true%0A%0Amodule%20Kernel%0A%20%20T%3A%3ASig%3A= %3AWithoutRuntime.sig%20%7Breturns(T.self_type)%7D%0A%20%20def%20must!%3B%2= 0self%3B%20end%0Aend%0A%0Aclass%20NilClass%0A%20%20T%3A%3ASig%3A%3AWithoutR= untime.sig%20%7Breturns(T.noreturn)%7D%0A%20%20def%20must!%0A%20%20%20%20ra= ise%20TypeError.new(%22nil.must!%22)%0A%20%20end%0Aend%0A%0Axs%20%3D%20T%3A= %3AArray%5BInteger%5D.new(%5B0%5D)%0AT.reveal_type(xs.first)%20%20%20%20%20= %20%20%23%20T.nilable(Integer)%0AT.reveal_type(xs.first.must!)%20%23%20Inte= ger%0A%0Ays%20%3D%20T%3A%3AArray%5BT.nilable(Integer)%5D.new(%5B0%2C%20nil%= 2C%201%2C%20nil%2C%202%5D)%0AT.reveal_type(ys)%20%20%20%20%20%20%20%20%20%2= 0%20%20%20%20%23%20T%3A%3AArray%5BT.nilable(Integer)%5D%0AT.reveal_type(ys.= map(%26%3Amust!))%20%23%20T%3A%3AArray%5BInteger%5D)

# Alternatives considered

There was some discussion of this feature at the Feb 2020 Ruby Types discus= sion:

Summarizing:

- Sorbet team frequently recommends people to use `xs.fetch(0)` instead of = `T.must(xs[0])`
=C2=A0 on `Array`'s and `Hash`'s because it chains and reads better= .
=C2=A0 `.fetch` not available on other classes.

- It's intentional that `T.must` requires as many characters as it does= .
=C2=A0 Making it slightly annoying to type encourages developers to refacto= r their code so that `nil` never occurs.

- There was a proposal to introduce new syntax like `thing.!!`. This is cur= rently a syntax error.

=C2=A0 **Rebuttal**: There is burden to introducing new syntax. Tools like = Rubocop, Sorbet, and syntax highlighting
=C2=A0 plugins have to be updated. Also: it is hard to search for on Google= (as a new Ruby developer). Also: it
=C2=A0 is very short=E2=80=94having something slightly shorter makes people= think about whether they want to type it out
=C2=A0 instead of changing the code so that `nil` can't occur.

Another alternative would be to dismiss this as "not useful / common e= nough". I don't think that's true.
Here are some statistics from Stripe's Ruby monolith (~10 million lines= of code):

| methood | percentage of files mentioning method | number of occurrences o= f method |
| --- | --- | --- |
| `.nil?` | 16.69% | 31340 |
| `T.must` | 23.89% | 74742 |

From this, we see that

- `T.must` is in 1.43x more files than `.nil?`
- `T.must` occurs 2.38x more often than `.nil?`


# Naming

I prefer `must!` because it is what the method in Sorbet is already called.=

I am open to naming suggestions. Please provide reasoning.


# Discussion

In the above example, I used `T.must` twice. An alternative way to have wri= tten that would have been using save navigation:

```ruby
T.must(task&.mailing_params).fetch('template_context')
```

This works as well. The proposed `.must!` method works just as well when ch= aining methods with safe navigation:

```ruby
task&.mailing_params.must!.fetch('template_context')
```

However, there is still merit in using `T.must` (or `.must!`) twice=E2=80= =94it calls out that the programmer
intended neither location to be `nil`. In fact, if this method had been cha= ined across multiple lines,
the backtrace would include line numbers saying specifically **which** `.mu= st!` failed:


```ruby
task.must!
=C2=A0 .mailing_params.must!
=C2=A0 .fetch('template_context')
```




--
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=3Dunsubscribe= >
<http://lists.ruby-lang.org/cgi-bin/m= ailman/options/ruby-core>


--
--0000000000004e8d0405b4546d15-- --===============1205456113== Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline Unsubscribe: --===============1205456113==--