Relationship between names, variables and values


Are variables bound to their name in Julia? i.e. If I assign a different value to the same name in Julia will that cause an issue with type stability?

x = 100randn(100) #Vector{Float64}
x = round.(Int, x)    #Vector{Int}

In my understanding, the second call creates an entirely new array in memory, and redefines x to point to that, so the first x and the second x share nothing more than having the same name. But a recent conversation on gitter led me to doubt this. Does the julia parser reason about the names in such a way that the first and second x are somehow related (making the code in the example a poor idea)?


AFAIK semantically they are unrelated, but the compiler can have problems with type inference if you use the same name for something of a different type. Eg

function foo(x)
    y = fill(Int(x), 9)
    y = fill(Float64(x), 10)


julia>  @code_warntype(foo(1))

      y::Any = $(Expr(:invoke, LambdaInfo for fill!(::Array{Int64,1}, ::Int64), :(Base.fill!),
,(Core.svec)(Core.Any,Core.Int)::SimpleVector,Array{Int64,1},0,9,0)::Array{Int64,1}), :(x))) #
 line 3:                                                                                     
      y::Any = $(Expr(:invoke, LambdaInfo for fill!(::Array{Float64,1}, ::Float64), :(Base.fil
l!), :((Core.ccall)(:jl_alloc_array_1d,(Core.apply_type)(Core.Array,Float64,1)::Type{Array{Flo
1}), :((,(Base.sitofp)(Float64,x))))) # line 4:                             
      return y::Array{Float64,1}


So the issue here is the y::Any instead of y::Array{Float64, 1} and y::Array{Int64, 1}? Interesting.

julia> function foo(x)
           y = fill(Float64(x), 9)
           z = fill(Int64(x),10)

julia> @code_warntype(foo(1))

      y::Array{Float64,1} = $(Expr(:invoke, LambdaInfo for fill!(::Array{Float64,1}, ::Float64), :(Base.fill!), :((Core.ccall)(:jl_alloc_array_1d,(Core.apply_type)(Core.Array,Float64,1)::Type{Array{Float64,1}},(Core.svec)(Core.Any,Core.Int)::SimpleVector,Array{Float64,1},0,10,0)::Array{Float64,1}), :((,(Base.sitofp)(Float64,x))))) # line 3:
      z::Array{Int64,1} = $(Expr(:invoke, LambdaInfo for fill!(::Array{Int64,1}, ::Int64), :(Base.fill!), :((Core.ccall)(:jl_alloc_array_1d,(Core.apply_type)(Core.Array,Int64,1)::Type{Array{Int64,1}},(Core.svec)(Core.Any,Core.Int)::SimpleVector,Array{Int64,1},0,9,0)::Array{Int64,1}), :(x))) # line 4:
      return z::Array{Int64,1}

I know this isn’t what I asked about, but here are two extra semirelated pieces of information: 1) the two versions of that function benchmark identically, at about 80ns on my machine; 2) in a nanosecond, light travels just one foot. In the time julia calculates this function, light can only travel from one end of my office building to the other. Sorry for this unrelated information, I just thought it was cool.


This should not be surprising. This part of julia (parser, inference?) is not flow-sensitive (see

I am sure this this topic has come up before, either on github or on the old google groups mailing list. But I cannot find it at the moment.


It could be cool to have some more info on it. I guess what I am really looking for is a good mental model for the relationship betwen names and values.


You need two mental models:

  1. the values that names are resolved to, ie scope rules,
  2. what the compiler can infer about types.

In many languages you would only ever worry about (1). But in Julia the type system is such an integral part of the language that you care about (2), especially if you want to write fast code. The issue with (2) is that there is no spec, since the compiler gets smarter and smarter, so in a practical sense just learn about common pitfalls, benchmark and profile, and use @code_warntype, and you will be OK. Ideally, the result of your code should not depend on (2), but its speed may.


Thanks. I am aware of the things you describe but it’s useful to have an answer like this in the thread.