Feature #17397
closed`shareable_constant_value: literal` should check at runtime, not at parse time
Added by marcandre (Marc-Andre Lafortune) almost 4 years ago. Updated almost 4 years ago.
Description
I think shareable_constant_value: literal
is too strict because it has too crude checks at parse time.
I wish the following code would parse and run:
# shareable_constant_value: literal
class Foo < RuntimeError
end
# Similar code, but does not parse:
Bar = Class.new(RuntimeError) # => unshareable expression
Baz = Ractor.make_shareable(anything_here) # => unshareable expression
Qux = Set[1, 2, 3].freeze # => unshareable expression
Could we instead raise some sort of RuntimeError when an assignment is made that is not shareable?
Updated by marcandre (Marc-Andre Lafortune) almost 4 years ago
Ideally, the following would also be accepted:
# shareable_constant_value: literal
Map = {
int: Integer,
str: String,
# ...
}
Map.frozen # => true
Literals that are arguments to method calls should not be frozen:
# shareable_constant_value: literal
def check(arg)
p arg.frozen?
arg.freeze
end
X = check([1,2,3]) # => prints false
Y = [1] + [2] # => error
Updated by marcandre (Marc-Andre Lafortune) almost 4 years ago
- Subject changed from shareable_literal_constant should check at runtime, not at parse time to `shareable_constant_value: literal` should check at runtime, not at parse time
Updated by marcandre (Marc-Andre Lafortune) almost 4 years ago
After discussion with ko1, the following options came up:
(1) raises an error for non-literals on parse time (current)
(2) raises an error for unshareable non-literals on runtime (my proposal)
(3) just ignore non-literals
(4) = (3) + (1) with literal-strict
(5) = (3) + (2) with literal-strict
I like all options that do not include (1).
Solution 2 is as I presented. Solution 3 is more permissive. Potential downsides include:
# shareable_constant_value: literal
FOO = [1, 2, 3]
BAR = [4, 5, 6, 7, 8, 9, 10]
BAZ = FOO + BAR # => not frozen / shareable
QUX = Set[11, 12, 13] # => not frozen / shareable
It would be easy to call ractor.send(BAZ) # or QUX
and assume that no copy is being made.
My opinion remains that many rubyists will want all their constants to be immutable, so option (2) would be a good way to do this and enforce it at the same time. I expect that global registry / cache are better implemented as attributes of singleton class.
Updated by Eregon (Benoit Daloze) almost 4 years ago
- Related to Feature #17273: shareable_constant_value pragma added
Updated by nobu (Nobuyoshi Nakada) almost 4 years ago
Your proposal seems shareable_constant_value: everything
(it's prefixed with experimental_
now though).
Updated by marcandre (Marc-Andre Lafortune) almost 4 years ago
nobu (Nobuyoshi Nakada) wrote in #note-5:
Your proposal seems
shareable_constant_value: everything
(it's prefixed withexperimental_
now though).
No, that value deep-freezes implicitly all constants. Non-literals are not frozen in my proposal.
Here's a code summary of the differences (scenarios 2, 3 or 5 == 2+3):
# shareable_constant_value: experimental_everything (current, ok)
X = [1, 2, 3]
p X.frozen? # => true
Y = Set[4, 5, 6]
p Y.frozen? # => true
# shareable_constant_value: literal (2) or literal_strict (5)
X = [1, 2, 3]
p X.frozen? # => true
(Y = Set[4, 5, 6]) rescue :error # => :error (at runtime, currently at parse time)
Z = Set[8, 9].freeze # => no error (currently parse error)
# shareable_constant_value: literal (3 or 5)
X = [1, 2, 3]
p X.frozen? # => true
Y = Set[4, 5, 6] # => no error (currently parse error)
p Y.frozen? # => false
Updated by shyouhei (Shyouhei Urabe) almost 4 years ago
marcandre (Marc-Andre Lafortune) wrote:
I think
shareable_constant_value: literal
is too strict because it has too crude checks at parse time.
This is true. Current restriction is very conservative not to break things (See #17273 for the background). If we can gain safety and usability at once, I think it's OK to relax. But experimental_everything is too lax (-1 for nobu's comment). There must be a better safety-usability trade-off than that. This pragma must not be an all-or-nothing flag I believe.
Updated by ko1 (Koichi Sasada) almost 4 years ago
By discussion with Matz and Nobu, we choose "(2) raises an error for unshareable non-literals on runtime (my proposal)".
* on compile time: for `FOO = expr`
* case `FOO = literal_only`
* compile to => `FOO = RubyVM::FrozenCore.make_shareable(literal_only)`
* otherwise
* compile to => `FOO = RubyVM::FrozenCore.check_shareable(expr)`
RubyVM::FrozenCore.check_shareable(obj)
is obj.tap{|o| raise Ractor::Error unless Ractor.shareable?(o)}
.
One concern was "literal" doesn't mean anything about checking unshareable feature ("otherwise" behavior).
We challenged to find out good naming, but we determined to choose "literal" as is.
The logic is here:
-
shareable_constant_value: none
means there is no checking. Other options force constants are sharable (or an error) -
shareable_constant_value: literal
means we do unshareable checking and make all literals sharable.
We can list the following possible options:
- (default) none: do nothing
- (not contained) check:
FOO = RubyVM::FrozenCore.check_shareable(expr)
- literal
- if
expr
is literal expr,FOO = RubyVM::FrozenCore.make_shareable(expr)
- else,
FOO = RubyVM::FrozenCore.check_shareable(expr)
- if
- (not contained) copy:
FOO = RubyVM::FrozenCore.make_shareable(expr, copy: true)
- experimental_everything:
FOO = RubyVM::FrozenCore.make_shareable(expr)
I think "copy" here can be introduced as experimental.
Updated by ko1 (Koichi Sasada) almost 4 years ago
By seeing the list, about experimental_everything
, experimental_shareable
seems better naming...
Updated by marcandre (Marc-Andre Lafortune) almost 4 years ago
Great, thank you for the update.
One last concern with experimental_everything
/experimental_shareable
is for Ruby 3.1... If no longer experimental, we then accept everything
. But if someone want to write code also compatible with Ruby 3.0, they still have to use experimental_everything
(assuming Ruby 3.1 also allow it). And in Ruby 3.2 same thing.
Only in ~3 years can people start writing everything
(assuming they want to be compatible with non-EOL rubies).
Could be use everything
(or my favorite: all
) and either warn (once) that it is experimental, or document it?
Updated by nobu (Nobuyoshi Nakada) almost 4 years ago
Updated by nobu (Nobuyoshi Nakada) almost 4 years ago
- Status changed from Open to Closed
Applied in changeset git|7a094146e6ef38453a7e475450d90a9c83ea2277.
Changed shareable literal semantics [Feature #17397]
When literal
, check if the literal about to be assigned to a
constant is ractor-shareable, otherwise raise Ractor::Error
at
runtime instead of SyntaxError
.