Conversion while constructing object

Hi, does anyone know in the code below, why #2 has convert called and how to avoid it?

struct S{T}
    i::T
end

mutable struct Box{T}
    i::T
end

function Base.convert(::Type{S{T1}}, d::S{T2}) where {T1, T2}
    println("Convert: ", T2, " --> ", T1)
    return S{T1}(zero(T1))
end

function Base.copy(box::Box{T}) where {T}
    println("Different Types? --> ", T, ", ", typeof(box.i))
    Box{T}(box.i)
end

# 1.
b = Box{Any}(S{Float64}(1.0)) # no conversoion
c = copy(b) # no conversion

# 2.
b = Box(S{Float64}(1.0)) # Conversion 
c = copy(b) # Conversion 

Thanks!

It’s the default constructor that implements the conversion. If you don’t want any default constructors, define your own inner constructor.

https://docs.julialang.org/en/v1/manual/constructors/

2 Likes

I defined my own constructors, and they didn’t change the behavior…

mutable struct Box{T}
    i::T
    Box(i::T) where T = new{T}(i)
    Box{T}(i) where T = new{T}(i)
end
1 Like

and b.i = S{Float64}(1.0) also calls convert while b.i and S{Float64}(1.0) have the same type S{Float64}.

Does converting a::T to type T make any sense?

Yes, there’s a default convert(::Type{T}, x::T) where {T} = x method builtin, but your convert method is more specific. You could similarly add a convert(::Type{S{T}}, x::S{T}) where {T} = x to avoid this case.

I guess I typically don’t see that convert call because of the no-op default.

1 Like

Ahhh, that explains the situation, thank you.

But in my case struct S and its convert are from another package[1], and Box is my own type, so I should:

  • either file an issue (or PR) to wait for the fixing
  • or do a type-pirate to add a convert method myself

Is that right?

[1] : https://github.com/JuliaStats/Distributions.jl/blob/master/src/univariate/continuous/inversegamma.jl#L48