Simple question that probably someone on the internet may have on the future. Why does this happen? Is the compiler not able to detect that I’m exiting the program using exit(1)?
In this example @somevar is @redis.
def initialize
begin
@redis = Redis::PooledClient.new(unixsocket: CONFIG.redis_socket || nil, url: CONFIG.redis_url || nil)
if @redis.ping
LOGGER.info "Connected to Redis compatible DB via unix domain socket at '#{CONFIG.redis_socket}'" if CONFIG.redis_socket
LOGGER.info "Connected to Redis compatible DB via TCP socket at '#{CONFIG.redis_url}'" if CONFIG.redis_url
end
rescue ex
LOGGER.info "Failed to connect to a Redis compatible DB: #{ex.message}"
exit(1)
end
end
Error: instance variable '@redis' of Invidious::Database::Videos::CacheMethods::Redis_ must be Redis::PooledClient, not Nil
Instance variable '@redis' is initialized inside a begin-rescue, so it can potentially be left uninitialized if an exception is raised and rescued
The intended way to handle this is letting the program crash by itself without begin?
Does it work if you don’t exit(1) but instead raise ex and let that bubble up? it doesn’t. My guess is the compiler just isn’t taking the exit/raise into account.
But maybe it would be a better design to pass in the redis client to use to the constructor vs doing that within the constructor itself? Then your error handling wouldn’t be intertwined with instantiation of the obj itself.
I guess it depends on your background. I come from a more Java-like background so my preference is to rely on Dependency Inversion so something along the lines of:
def initialize(@client : Redis::ClientInterface); end
Where the client you provide it is already validated to be good. This makes it easy to test the type in that you could easily provide it a mock client.
But in your case maybe just remove the begin/rescue and handle logging failures higher up? This way obj instantiation would still fail if redis fails to connect or whatever, but the compiler can still guarantee it won’t be nilable.
class Foo
def initialize
begin
@foo = 1
rescue exc
raise exc
end
end
end
Foo.new # Error: instance variable '@foo' of Foo must be Int32, not Nil
# Instance variable '@foo' is initialized inside a begin-rescue, so it can potentially be left uninitialized if an exception is raised and rescued
It seems the compiler doesn’t correctly consider that the rescue block may not return control flow to the outer scope when considering this.
It does so in other cases, though. For example as a workaround you could pull out the ivar an assign the value of the begin-rescue block. That’ll require an explicit type type declaration; the compiler can’t infer it.
class Foo
@foo : Int32
def initialize
@foo = begin
1
rescue esc
raise esc
end
end
end
Foo.new