[#100689] [Ruby master Feature#17303] Make webrick to bundled gems or remove from stdlib — hsbt@...

Issue #17303 has been reported by hsbt (Hiroshi SHIBATA).

11 messages 2020/11/02

[#100715] [Ruby master Bug#17306] TestGCCompact#test_ast_compacts test failures — v.ondruch@...

Issue #17306 has been reported by vo.x (Vit Ondruch).

11 messages 2020/11/05

[#100720] [Ruby master Feature#17307] A way to mark C extensions as thread-safe, Ractor-safe, or unsafe — eregontp@...

Issue #17307 has been reported by Eregon (Benoit Daloze).

22 messages 2020/11/05

[#100744] [Ruby master Bug#17310] Closed ractors should die — marcandre-ruby-core@...

Issue #17310 has been reported by marcandre (Marc-Andre Lafortune).

12 messages 2020/11/08

[#100753] [Ruby master Feature#17312] New methods in Enumerable and Enumerator::Lazy: flatten, product, compact — zverok.offline@...

Issue #17312 has been reported by zverok (Victor Shepelev).

11 messages 2020/11/09

[#100763] [Ruby master Feature#17314] Provide a way to declare visibility of attributes defined by attr* methods in a single expression — radek.bulat@...

Issue #17314 has been reported by radarek (RadosナBw BuナBt).

17 messages 2020/11/10

[#100777] [Ruby master Feature#17316] On memoization — sawadatsuyoshi@...

Issue #17316 has been reported by sawa (Tsuyoshi Sawada).

18 messages 2020/11/11

[#100788] [Ruby master Misc#17319] Rename Random::urandom to os_random and document random data sources — zofrex@...

Issue #17319 has been reported by zofrex (James Sanderson).

11 messages 2020/11/11

[#100807] [Ruby master Feature#17322] Deprecate `Random::DEFAULT` and introduce `Random.default()` method to provide Ractor-supported default random generator — ko1@...

Issue #17322 has been reported by ko1 (Koichi Sasada).

14 messages 2020/11/12

[#100816] [Ruby master Feature#17323] Ractor::LVar to provide ractor-local storage — ko1@...

Issue #17323 has been reported by ko1 (Koichi Sasada).

19 messages 2020/11/12

[#100849] [Ruby master Feature#17325] Adds Fiber#cancel, which forces a Fiber to break/return — nicholas.evans@...

Issue #17325 has been reported by nevans (Nicholas Evans).

17 messages 2020/11/14

[#100852] [Ruby master Feature#17326] Add Kernel#must! to the standard library — zimmerman.jake@...

Issue #17326 has been reported by jez (Jake Zimmerman).

24 messages 2020/11/14

[#100858] [Ruby master Feature#17327] The Queue constructor should take an initial set of items — chris@...

Issue #17327 has been reported by chrisseaton (Chris Seaton).

10 messages 2020/11/15

[#100897] [Ruby master Feature#17330] Object#non — zverok.offline@...

Issue #17330 has been reported by zverok (Victor Shepelev).

21 messages 2020/11/17

[#100925] [Ruby master Feature#17331] Let Fiber#raise work with transferring fibers — nicholas.evans@...

Issue #17331 has been reported by nevans (Nicholas Evans).

12 messages 2020/11/18

[#100930] [Ruby master Feature#17333] Enumerable#many? — masafumi.o1988@...

Issue #17333 has been reported by okuramasafumi (Masafumi OKURA).

10 messages 2020/11/18

[#100971] [Ruby master Bug#17337] Don't embed Ruby build time configuration into Ruby — v.ondruch@...

Issue #17337 has been reported by vo.x (Vit Ondruch).

16 messages 2020/11/20

[#100999] [Ruby master Feature#17339] Semantic grouping on BigDecimal#to_s — co.chuma@...

Issue #17339 has been reported by chumaltd (Takahiro Chuma).

9 messages 2020/11/21

[#101071] [Ruby master Feature#17342] Hash#fetch_set — hunter_spawn@...

Issue #17342 has been reported by MaxLap (Maxime Lapointe).

26 messages 2020/11/25

[#101093] [Ruby master Misc#17346] DevelopersMeeting20201210Japan — mame@...

Issue #17346 has been reported by mame (Yusuke Endoh).

17 messages 2020/11/26

[#101141] [Ruby master Bug#17354] Module#const_source_location is misleading for constants awaiting autoload — tom@...

Issue #17354 has been reported by tomstuart (Tom Stuart).

21 messages 2020/11/29

[#101143] [Ruby master Feature#17355] Or-patterns (pattern matching like Foo(x) | Bar(x)) — fg@...

Issue #17355 has been reported by decuplet (Nikita Shilnikov).

8 messages 2020/11/29

[#101153] [Ruby master Feature#17356] Alignment of memory allocated through Fiddle struct's malloc — andrea.ribuoli@...

Issue #17356 has been reported by AndreaRibuoli (Andrea Ribuoli).

8 messages 2020/11/30

[ruby-core:100918] [Ruby master Feature#17326] Add Kernel#must! to the standard library

From: merch-redmine@...
Date: 2020-11-17 20:50:44 UTC
List: ruby-core #100918
Issue #17326 has been updated by jeremyevans0 (Jeremy Evans).


This method is trivial to write in Ruby.  While it can be useful even in codebases that do not use static typing, I think the benefit of adding it is smaller than the cost of adding another method to Kernel.  I think it should probably be in a separate gem, or an optional part of Sorbet.  Doing so makes even easier to support in older Ruby versions, and doesn't break backwards compatibility for users that are currently using the method name.

If we do consider this feature for inclusion in Kernel, I think that outside of Stripe/Sorbet users (a small fraction of Ruby programmers), `.must!` meaning `raise if nil?` is not intuitive.  I think a better name is needed that would be more intuitive to the average Ruby programmer if we do want this method in Kernel.


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

* 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 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 annotating 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:

[→ 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.nilable(Thing)).void%7D%0Adef%20example1(thing)%0A%20%20%23%20error%2C%20might%20be%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%20%20%23%20no%20error%2C%20because%20it's%20after%20the%20%60if%20.nil%3F%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%20error%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.  It leads to a weird outward spiral of flow control, which disrupts method chains:

    ```ruby
    # ┌─────────────────┐
    # │      ┌────┐     │
    # ▼      ▼    │     │
    T.must(T.must(task).mailing_params).fetch('template_context')
    # │      │          ▲               ▲
    # │      └──────────┘               │
    # └─────────────────────────────────┘
    ```

    compare that control flow with this:

    ```ruby
    # ┌────┐┌────┐┌─────────────┐┌────┐
    # │    ▼│    ▼│             ▼│    ▼
      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 = array_of_nilable_integers.map {|x| T.must(x) }
    ```

    Compare that with this:

    ```ruby
    array_of_integers = 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 so 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 have 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:

[→ 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%20must!%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%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%20Integer%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%20%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 discussion:

Summarizing:

- Sorbet team frequently recommends people to use `xs.fetch(0)` instead of `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 (as a new Ruby developer). Also: it
  is very short—having something slightly shorter makes people think 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 of 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 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 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—it 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: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>

In This Thread