The compiler is technically right here. There’s a few things going on, but I don’t really think this is a compiler bug at least. Adding method parameter and return type restrictions makes the problem more clear.
Using loop do creates its own scope within the block, so the compiler has to handle the case where it doesn’t execute I guess, using while true does not suffer from this and works just fine.
Your variable p is basically conflicting with the ::p() method which returns the arguments passed to it, and since its only assigned in the loop do, if that doesn’t execute the compiler will call ::p() with no arguments so it returns an empty tuple, which without a type restriction on the method or n parameter, is allowed to pass thru, turning the type of n in to Tuple() | UInt64.
I initialized p before the loop and it works.
But the error message isn’t very helpful because it points to n, which is not the cause of the error (it’s also defined before loop do), it’s apparently an artifact of it.
It’s these subtle differences like between things like loop do and while true that for people coming from dynamic languages like Ruby, et al, can be a PITA.
def mpgen(n, steps = 1u64)
puts "Starting Mp gen exponent = #{n}"
puts "Starting Mp gen process at step #{steps}"
primes_checked = 0u64
p = 0u64 # <---- need for it to compile
loop do
mp = (3.to_big_i << n) + (1.to_big_i << n) - 1
p = mp.bit_length.to_u64
print "\rsteps = #{steps}; checking prime #{p}"
if prime?(p)
primes_checked += 1
break if mp_prime?(p, mp)
end
steps += 1
n += 2
end
print "\rsteps = #{steps}\n"
puts "Primes checked = #{primes_checked}"
puts "Next Mp prime = #{p}"
p
end
If mpgen is recursive (for example, if mp_prime? calls back into it) or if something else in your program is feeding the return value of one mpgen call to another, then pointing to n makes sense. The method p with no arguments returns an empty Tuple.
p typeof(p)
# => Tuple()
So if you’re doing something like this:
n = 0
while n < some_value
n = mpgen(n)
# ...
end
Then the compiler will infer from the fact that you returned a Tuple() from an invocation of mpgen that n inside of mpgen can be a Tuple().
def mpgen(n)
loop do
p = n
break
end
p
end
n = 0
3.times do
puts n
n = mpgen(n)
end
# => 0
# => {}
# => {}
How so? The scoping rules between loop do and while true in Ruby are the same as in Crystal. This would’ve failed in Ruby, too, except the value would’ve been nil (p with no args returns nil).
def preinitialized
p = :preinitialized
loop do
p = 0
break
end
p
end
def uninitialized
loop do
p = 0
break
end
p
end
p preinitialized # => 0
p uninitialized # => nil
The issue for me was the error message was very confusing. As soon as @Blacksmoke16 said the word scope I knew exactly what the problem was, and fixed it.
If the error message had said something like: p not in scope
or even variable in loop not in scope
I would have known exactly what the problem was.
These are the specific types of error messages the Rust compiler gives.
Hopefully I’ll be cognizant of these loop differences (again) and won’t repeat this mistake.