Based on: https://sorbet.org/docs/type-assertions
In my mind, T.cast
seems like a more powerful version of T.let
. T.cast(expr, type)
forces Sorbet to accept expr
as type
, even if expr
cannot be statically verified as type
. T.let(expr, type)
hints to Sorbet that expr
has type
, and Sorbet will be able to typecheck that hint is valid. Based on this understanding, I would assume that T.cast
could always be used in place of T.let
, but T.let
is preferred because Sorbet is providing the most type safety. However, I recently found a case where that is not true (see below).
This example is based on: https://sorbet.org/docs/error-reference#7001
# typed: true
class Main
extend T::Sig
sig { params(elem: Integer).returns(T::Boolean) }
def valid?(elem)
elem == 0
end
sig { params(list: T::Array[Integer]).returns(T::Boolean) }
def works(list)
# Declare `all_valid` with type `T::Boolean`
all_valid = T.let(false, T::Boolean)
list.each do |elem|
# Does not change the type of `all_valid`
all_valid &&= valid?(elem) # ok
end
all_valid
end
sig { params(list: T::Array[Integer]).returns(T::Boolean) }
def does_not_work(list)
all_valid = T.cast(false, T::Boolean)
list.each do |elem|
# ERROR! Changing the type of a variable in a loop is not permitted
all_valid &&= valid?(elem)
end
all_valid
end
end
Why does this work with T.let
but not with T.cast
?