[#116016] [Ruby master Bug#20150] Memory leak in grapheme clusters — "peterzhu2118 (Peter Zhu) via ruby-core" <ruby-core@...>
Issue #20150 has been reported by peterzhu2118 (Peter Zhu).
7 messages
2024/01/04
[#116382] [Ruby master Feature#20205] Enable `frozen_string_literal` by default — "byroot (Jean Boussier) via ruby-core" <ruby-core@...>
Issue #20205 has been reported by byroot (Jean Boussier).
77 messages
2024/01/23
[ruby-core:116074] [Ruby master Feature#20160] rescue keyword for case expressions
From:
"kddnewton (Kevin Newton) via ruby-core" <ruby-core@...>
Date:
2024-01-08 15:21:14 UTC
List:
ruby-core #116074
Issue #20160 has been updated by kddnewton (Kevin Newton).
```ruby
case (parsed = parse(input))
when Integer then handle_int(parsed)
when Float then handle_float(parsed)
rescue ParseError
# ...
rescue ArgumentError
# ...
else
# ... fallthrough for all rescue and when cases
ensure
# ... called always
end
```
This would make me very uncomfortable. `else` for `case`/`when` is used as a default case. `else` for `begin` is used when error are not raised. These are fundamentally different concerns and concepts, and this would be overloading them.
If it were consistent with `case`/`when` it would jump to the else case if it did not match. If it were consistent with `begin`/`else` it would jump to the `else` case if no error was raised. This would be very confusing and difficult to educate people about.
It's also not clear to me if a `rescue` clause is attached to a `case` statement if the rescue applies to just the value of the `case` or if it applies to the entire statement. If an error is raised inside `parse_int` in your example, does it go through the `rescue`? What if the `rescue` is added after the `in`/`when` clause?
I think this loses quite a bit more clarity than it gains.
----------------------------------------
Feature #20160: rescue keyword for case expressions
https://bugs.ruby-lang.org/issues/20160#change-106071
* Author: lloeki (Loic Nageleisen)
* Status: Open
* Priority: Normal
----------------------------------------
It is frequent to find this piece of hypothetical Ruby code:
```
case (parsed = parse(input))
when Integer then handle_int(parsed)
when Float then handle_float(parsed)
end
```
What if we need to handle `parse` raising a hypothetical `ParseError`? Currently this can be done in two ways.
Either option A, wrapping `case .. end`:
```
begin
case (parsed = parse(input))
when Integer then handle_int(parsed)
when Float then handle_float(parsed)
# ...
end
rescue ParseError
# ...
end
```
Or option B, guarding before `case`:
```
begin
parsed = parse(input)
rescue ParseError
# ...
end
case parsed
when Integer then handle_int(parsed)
when Float then handle_float(parsed)
# ...
end
```
The difference between option A and option B is that:
- option A `rescue` is not localised to parsing and also covers code following `when` (including calling `===`), `then`, and `else`, which may or may not be what one wants.
- option B `rescue` is localised to parsing but moves the definition of the variable (`parsed`) and the call to what is actually done (`parse(input)`) far away from `case`.
With option B in some cases the variable needs to be introduced even though it might not be needed in `then` parts (e.g if the call in `case` is side-effectful or its value simply leading to branching decision logic).
The difference becomes important when rescued exceptions are more general (e.g `Errno` stuff, `ArgumentError`, etc..), as well as when we consider `ensure` and `else`. I feel like option B is the most sensible one in general, but it adds a lot of noise and splits the logic in two parts.
I would like to suggest a new syntax:
```
case (parsed = parse(input))
when Integer then handle_int(parsed)
when Float then handle_float(parsed)
rescue ParseError
# ...
rescue ArgumentError
# ...
else
# ... fallthrough for all rescue and when cases
ensure
# ... called always
end
```
If more readability is needed as to what these `rescue` are aimed to handle - being more explicit that this is option B - one could optionally write like this:
```
case (parsed = parse(input))
rescue ParseError
# ...
rescue ArgumentError
# ...
when Integer then handle_int(parsed)
when Float then handle_float(parsed)
...
else
# ...
ensure
# ...
end
```
Keyword `ensure` could also be used without `rescue` in assignment contexts:
```
foo = case bar.perform
when A then 1
when B then 2
ensure bar.done!
end
```
Examples:
- A made-up pubsub streaming parser with internal state, abstracting away reading from source:
```
parser = Parser.new(io)
loop do
case parser.parse # blocks for reading io in chunks
rescue StandardError => e
if parser.can_recover?(e)
# tolerate failure, ignore
next
else
emit_fail(e)
break
end
when :integer
emit_integer(parser.last)
when :float
emit_float(parser.last)
when :done
# e.g EOF reached, IO closed, YAML --- end of doc, XML top-level closed, whatever makes sense
emit_done
break
else
parser.rollback # e.g rewinds io, we may not have enough data
ensure
parser.checkpoint # e.g saves io position for rollback
end
end
```
- Network handling, extrapolated from [ruby docs](https://ruby-doc.org/stdlib-2.7.1/libdoc/net/http/rdoc/Net/HTTP.html#class-Net::HTTP-label-Following+Redirection):
```
case (response = Net::HTTP.get_response(URI(uri_str))
rescue URI::InvalidURIError
# handle URI errors
rescue SocketError
# handle socket errors
rescue
# other general errors
when Net::HTTPSuccess
response
when Net::HTTPRedirection then
location = response['location']
warn "redirected to #{location}"
fetch(location, limit - 1)
else
response.value
ensure
@counter += 1
end
```
Credit: the idea initially came to me from [this article](https://inside.java/2023/12/15/switch-case-effect/), and thinking how it could apply to Ruby.
--
https://bugs.ruby-lang.org/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/