[#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:116076] [Ruby master Feature#20160] rescue keyword for case expressions
From:
"lloeki (Loic Nageleisen) via ruby-core" <ruby-core@...>
Date:
2024-01-08 16:04:33 UTC
List:
ruby-core #116076
Issue #20160 has been updated by lloeki (Loic Nageleisen).
> If #parse is defined as:
This requires:
a) parse to be in your control
b) parse to handle every possible exception (including whatever it calls) for which one would want a rescuing clause to control flow.
> extracting the get to a separate method
A somewhat generic alternative would be:
```
def wrap_error
yield
rescue StandardError => e
[:error, e]
end
case wrap_error { parse(input) }
when ...
```
That is the point: exceptions are a first class Ruby concept. To me it feels off to create wrappers (e.g with this new get method or the wrapper above), munge return values with in band metadata (this [:error, exception] return value), or split control flow in two parts (begin rescue followed by case when) when logically it is one control flow, this is why I felt there may be interest in such a proposal.
I do agree that pattern matching feels as much of a good potential candidate as any for such an exception rescuing feature. A specific pattern syntax could be used to match a given exception, and `else` would make sense.
----------------------------------------
Feature #20160: rescue keyword for case expressions
https://bugs.ruby-lang.org/issues/20160#change-106073
* 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/