# Why compile time type (typepof) is different in following case?

**URL:** <https://forum.crystal-lang.org/t/why-compile-time-type-typepof-is-different-in-following-case/4756>\
**Category:** Help & Support\
**Created:** [July 4, 2022, 4:46pm UTC](https://forum.crystal-lang.org/t/why-compile-time-type-typepof-is-different-in-following-case/4756 "2022-07-04T16:46:33Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [July 4, 2022, 4:46pm UTC](https://forum.crystal-lang.org/t/why-compile-time-type-typepof-is-different-in-following-case/4756/1 "2022-07-04T16:46:33Z")

</div>

```crystal
num2 : Int32 | Nil = 1
p! typeof(num2) # => (Int32 | Nil)
num2 = nil

```

```crystal
num2 : Int32 | Nil
num2 = 1
p! typeof(num2) # => Int32
num2 = nil

```

Why runtime type of num2 is different?

---

<div class="post-metadata">

**Author:** ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)\
**Post date:** [July 4, 2022, 4:59pm UTC](https://forum.crystal-lang.org/t/why-compile-time-type-typepof-is-different-in-following-case/4756/2 "2022-07-04T16:59:23Z")

</div>

I think it’s because there is no way to declare a variable w/o defining it. So the 2nd example is the same as:

```auto
num2 = 1
p! typeof(num2) # => Int32
num2 = nil

```

which the compiler is able to figure out it’ll only ever be an `Int32`. Whereas in the first one, you’ve explicitly gave a type to the local var, so that is reflected in `typeof`.

---

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [July 4, 2022, 5:08pm UTC](https://forum.crystal-lang.org/t/why-compile-time-type-typepof-is-different-in-following-case/4756/3 "2022-07-04T17:08:11Z")

</div>

> [@Blacksmoke16](#):
>
> I think it’s because there is no way to declare a variable w/o defining it. So the 2nd example is the same as:

Oops, if that true, any uninitialized declare like following:

```auto
num2 : Int32 | Nil

```

Always no-op if type inference is happen later (e.g. assign a variable), right?  
this seem like not make sense, your’s example, define a two variable, both of them name is `num2`, but, that not true for my second example.

---

<div class="post-metadata">

**Author:** ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)\
**Post date:** [July 4, 2022, 5:12pm UTC](https://forum.crystal-lang.org/t/why-compile-time-type-typepof-is-different-in-following-case/4756/4 "2022-07-04T17:12:14Z")

</div>

> [@zw963](#):
>
> Always no-op if type inference is happen later (e.g. assign a variable), right?

Seems like it yea, as if you do

```auto
num : Int32 | Nil
pp typeof(num)

```

You get an error because you’re trying to read before assignment.

> [@zw963](#):
>
> this seem like not make sense, your’s example, define a two variable, both of them name is `num2`, but, that not true for my second example.

Your second example is the same as mine was, just without the `num2 : Int32 | Nil` line. So not sure what you mean.

---

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [July 4, 2022, 5:26pm UTC](https://forum.crystal-lang.org/t/why-compile-time-type-typepof-is-different-in-following-case/4756/5 "2022-07-04T17:26:18Z")

</div>

> [@Blacksmoke16](#):
>
> Your second example is the same as mine was, just without the `num2 : Int32 | Nil` line. So not sure what you mean.

What i means is,

```crystal
num2 : Int32 | Nil

```

this line code told compiler, i declare a variable, it name is: num2, it type can be Int32 or nil, without this line (as example write by you), no this info told compiler, it should not same?

We can extend this example to a Array. (though, not match completely)

```crystal
y : Array(Int32 | String | Char) # declare y here.
y = [1, "hello", 'A'] # => Ok
y = [1] # => Error: type must be Array(Char | Int32 | String), not Array(Int32)

```

So, i consider, my example, 1 and 2, should give same result.

---

<div class="post-metadata">

**Author:** ![asterite](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/asterite/32/60_2.png) [@asterite](https://forum.crystal-lang.org/u/asterite)\
**Post date:** [July 4, 2022, 5:36pm UTC](https://forum.crystal-lang.org/t/why-compile-time-type-typepof-is-different-in-following-case/4756/6 "2022-07-04T17:36:59Z")

</div>

Try assigning a different type in both cases. What happens?

---

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [July 4, 2022, 5:54pm UTC](https://forum.crystal-lang.org/t/why-compile-time-type-typepof-is-different-in-following-case/4756/7 "2022-07-04T17:54:54Z")

</div>

Both case told me, Error: variable ‘num2’ already declared

```crystal
num2 : Int32 | Nil = 1
p! typeof(num2) # => (Int32 | Nil)
num2 = nil
num2 : String

```

```crystal
num2 : Int32 | Nil
num2 = 1
p! typeof(num2) # => Int32
num2 = nil
num2 : String

```

---

<div class="post-metadata">

**Author:** ![asterite](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/asterite/32/60_2.png) [@asterite](https://forum.crystal-lang.org/u/asterite)\
**Post date:** [July 4, 2022, 6:34pm UTC](https://forum.crystal-lang.org/t/why-compile-time-type-typepof-is-different-in-following-case/4756/8 "2022-07-04T18:34:59Z")

</div>

Oh, sorry, I meant **assigning a value of a different type**.

Check this:

```crystal
num2 : Int32 | Nil = 1
num2 = nil
num2 = "hello" # You get an error

```

and…

```crystal
num2 : Int32 | Nil
num2 = 1
num2 = "hello" # You get an error

```

In both cases the type must be that declared. The difference is that when you assign a specific value the compiler knows its type and does flow typing, which is reducing the type of the variable to the one the compiler knows about.

That said, it’s debatable whether when you do `num : Int32 | Nil = 1` you’d want `num` to be `Int32` or `Int32 | Nil`. The current behavior is probably a mistake. Feel free to report a bug for this.

---

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [July 4, 2022, 7:33pm UTC](https://forum.crystal-lang.org/t/why-compile-time-type-typepof-is-different-in-following-case/4756/9 "2022-07-04T19:33:51Z")

</div>

Don’t ensure what is the mistake you means.

so, i will use original post create a issue.

BTW: if comment the last line, it get Int32

```auto
num2 : Int32 | Nil = 1
p! typeof(num2) # => Int32
# num2 = nil

```

---

<div class="post-metadata">

**Author:** ![beta-ziliani](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/beta-ziliani/32/1821_2.png) [@beta-ziliani](https://forum.crystal-lang.org/u/beta-ziliani)\
**Post date:** [July 4, 2022, 7:47pm UTC](https://forum.crystal-lang.org/t/why-compile-time-type-typepof-is-different-in-following-case/4756/10 "2022-07-04T19:47:05Z")

</div>

I think the current behavior is reasonable. Or perhaps I’m missing what would the alternative look like.

If the type is not restricted after setting a specific member of the union, then you’ll get a real weird behavior:

```crystal
def an_int : Int32
  1
end

maybe_an_int : Int32? = nil
maybe_an_int = an_int # here, we know for sure there's an int there
p maybe_an_int + 1 # Should this fail? Why? We know there _is_ an int

```

---

<div class="post-metadata">

**Author:** ![HertzDevil](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/hertzdevil/32/1023_2.png) [@HertzDevil](https://forum.crystal-lang.org/u/HertzDevil)\
**Post date:** [July 9, 2022, 6:37pm UTC](https://forum.crystal-lang.org/t/why-compile-time-type-typepof-is-different-in-following-case/4756/11 "2022-07-09T18:37:01Z")

</div>

The current behaviour is technically consistent with def parameter restrictions: they restrict an expression’s type, but never perform any casts. If I want a cast, this is much clearer:

```crystal
num2 = 1.as(Int32 | Nil)
typeof(num2) # => (Int32 | Nil)

```

The only use case for `TypeDeclaration` of a local variable I could think of is when one wants to trigger autocasting explicitly:

```crystal
enum Foo
  Bar
end

x : Foo = :bar
x # => Bar

```

Otherwise I would go as far as saying `TypeDeclaration`s of local variables are unidiomatic.

---

<div class="post-metadata">

**Author:** ![asterite](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/asterite/32/60_2.png) [@asterite](https://forum.crystal-lang.org/u/asterite)\
**Post date:** [July 9, 2022, 7:49pm UTC](https://forum.crystal-lang.org/t/why-compile-time-type-typepof-is-different-in-following-case/4756/12 "2022-07-09T19:49:42Z")

</div>

I think we just made them work for completeness. There was a time they didn’t work.

One use case is to prevent the variable from taking an undesired type. But personally I find it rarely useful.
