Explicit denotation of variability and immutability

julia> struct foo{T}
           x::T
       end

julia> mutable struct bar{T}
           x::T
       end

julia> g(x::foo) = foo(3x.x)
g (generic function with 2 methods)

julia> function g(x::bar)
           x.x *= 3
           x
       end
g (generic function with 2 methods)

julia> function f(x)
           g(x)
       end
f (generic function with 1 method)

julia> foo1 = foo(9.2)
foo{Float64}(9.2)

julia> bar1 = bar(9.2)
bar{Float64}(9.2)

julia> f(foo1)
foo{Float64}(27.599999999999998)

julia> f(bar1)
bar{Float64}(27.599999999999998)

julia> foo1
foo{Float64}(9.2)

julia> bar1
bar{Float64}(27.599999999999998)

julia> @code_warntype f(bar1)
Variables:
  #self# <optimized out>
  x::bar{Float64}

Body:
  begin 
      $(Expr(:inbounds, false))
      # meta: location REPL[28] g 2
      SSAValue(0) = (Base.mul_float)((Core.getfield)(x::bar{Float64}, :x)::Float64, (Base.sitofp)(Float64, 3)::Float64)::Float64
      (Core.setfield!)(x::bar{Float64}, :x, SSAValue(0))::Float64
      # meta: pop location
      $(Expr(:inbounds, :pop))
      return x::bar{Float64}
  end::bar{Float64}

julia> @code_warntype f(foo1)
Variables:
  #self# <optimized out>
  x::foo{Float64}

Body:
  begin 
      return $(Expr(:new, foo{Float64}, \:((Base.mul_float)((Base.sitofp)(Float64, 3)::Float64, (Core.getfield)(x, :x)::Float64)::Float64)))
  end::foo{Float64}

The function g was inlined into f both for the f compiled for foo and for the f compiled for bar.
In the former, the argument x is not mutated. In the latter, it is.
You can come up with similar examples for const vs nonconst bindings.

3 Likes