Crystal: Global variable type is not guessed as nilable when it is only assigned inside a begin-rescue-end

Created on 17 Jul 2016  路  6Comments  路  Source: crystal-lang/crystal

begin
  raise "foo"
  $var = "aaa"
rescue
end

p $var

This crashes at runtime. I assume it happens because the type of $var is guessed as String. As not all codepaths lead to an assignment, it should actually be String?. Or not be guessed at all with an error message.

Adding $var : String? fixes the crash.

bug compiler

Most helpful comment

Shall we remove global variables once and for all? :-)

All 6 comments

Shall we remove global variables once and for all? :-)

It does complain when the variable is anything but global :)

module A
  begin
    raise ""
    @@a = "a"
  rescue
  end

  p @@a
end
Error class variable '@@a' of A is not nilable (it's String) so it must have an initializer

I'm not against removing then, only note is that it will make writing small, quick and dirt scripts less quick (but also less dirt).

The problem with global variables is that they are, well, global, so they can be used anywhere and they don't have a specific point where they are declared and initialized. With class variables this is more controlled: they belong to a class, and they must be initialized there, and if they are not initialized there then it's easy to figure out that they are nilable.

Also, once we make crystal work with multiple threads, global variables will always need some kind of lock so it'll be pretty bug-prone to use them. Class variables are more controllable, because one can't access them anywhere, and inside a class one could provide locking or concurrency mechanism to prevent data races.

Global variables are implemented and useful in many languages, PHP, Ruby, Python, or even we can use extern in C.

Maybe we can set the type of global variables always be T | Nil (that we need to do #.not_nil! by ourself or just do something with Nil supported operations), or just do assert on every read-operation?

But I have no idea with the multi-thread solution.

Scoping a class-variable in a module, or type, has the same practical use as a global var, so it's not like we'd be without them. Since these always goes through getters and setters, you will after LLVM-optimization have a straight memory access if that's all they do. If you later parallelize your application you can simply add locking to the getters and setters and it just works. With globals you'd have to rewrite them to classvars. So, I think using globals scoped under a module/type, as classvars, and thereby always accessing via getter/setter, would be a good thing.
Setting globals to always be T | Nil seems very dodgy imo.

Global vars are no more, so I'll close this

Was this page helpful?
0 / 5 - 0 ratings