# How not to commit type piracy when handling automatic conversion for field with Union type

**URL:** <https://discourse.julialang.org/t/how-not-to-commit-type-piracy-when-handling-automatic-conversion-for-field-with-union-type/109877>\
**Category:** General Usage\
**Tags:** question, struct, type-piracy\
**Created:** [February 7, 2024, 2:40pm UTC](https://discourse.julialang.org/t/how-not-to-commit-type-piracy-when-handling-automatic-conversion-for-field-with-union-type/109877 "2024-02-07T14:40:29Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)\
**Post date:** [February 7, 2024, 2:40pm UTC](https://discourse.julialang.org/t/how-not-to-commit-type-piracy-when-handling-automatic-conversion-for-field-with-union-type/109877/1 "2024-02-07T14:40:29Z")

</div>

I’d like to handle the conversion in structs just like when you have a concrete type for a field, but when a field as a `Union` type like below:

```julia
struct A end

struct B
    x::Union{A, Int}
end

```

I’d like this to work:

```julia
B(true) # should return B(1)

```

I was able to make everything work with:

```julia
# note: these definitions are not entirely correct in any case, see the issue for more details
Base.convert(::Type{Union{A,T}}, x) where T = convert(T, x)
Base.convert(::Type{Union{A,T}}, x::T) where T = convert(T, x)
Base.convert(::Type{Union{A,T}} where T, x::A) = x

```

everything seems to work very well

```julia
julia> b1 = B(A())
B(A())

julia> b2 = B(1)
B(1)

julia> b3 = B(true) # automatic conversion
B(1)

```

but then I was informed by @jameson in this issue: [Ambiguity in Base.convert on 1.8/1.9 but not on 1.10 and master · Issue #53224 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/53224) that I’m committing type-piracy, and indeed this now fails!

```julia
julia> Union{Nothing, Union{A, Int}}[1][1]
ERROR: MethodError: convert(::Type{Union{Nothing, A, Int64}}, ::Int64) is ambiguous.

Candidates:
  convert(::Type{Union{A, T}}, x::T) where T
    @ Main REPL[4]:1
  convert(::Type{T}, x) where T>:Nothing
    @ Base some.jl:37
  convert(::Type{Union{A, T}}, x) where T
    @ Main REPL[3]:1
  convert(::Type{T}, x::T) where T>:Nothing
    @ Base some.jl:36
  convert(::Type{T}, x::T) where T
    @ Base Base.jl:84
To resolve the ambiguity, try making one of the methods more specific, or adding a new method more specific than any of the existing applicable methods.

```

but without the above extensions of `Base.convert` it works fine:

```julia
julia> Union{Nothing, Union{A, Int}}[1][1]
1

```

Is there a way to achieve this without producing bugs?

---

<div class="post-metadata">

**Author:** ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)\
**Post date:** [February 7, 2024, 2:55pm UTC](https://discourse.julialang.org/t/how-not-to-commit-type-piracy-when-handling-automatic-conversion-for-field-with-union-type/109877/2 "2024-02-07T14:55:48Z")

</div>

Can’t you achieve the same thing with constructors rather than introducing a general conversion rule?

```julia-repl
julia> struct B3
           x::Union{A, Int}
           B3(a::A) = new(a)
           B3(a) = new(convert(Int, a))
       end

julia> B3(1)
B3(1)

julia> B3(true)
B3(1)

```

---

<div class="post-metadata">

**Author:** ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)\
**Post date:** [February 7, 2024, 3:05pm UTC](https://discourse.julialang.org/t/how-not-to-commit-type-piracy-when-handling-automatic-conversion-for-field-with-union-type/109877/3 "2024-02-07T15:05:12Z")

</div>

Seems like a very good idea, but I fail to see the generalization for my use case, for example

```julia
struct A end

struct B
    x::Union{A, Int64}
    y::Union{A, Int32}
    z::Union{A, Int16}
    k::Union{A, Int8}
end

```

doesn’t this need a lot of constructors with your idea?

---

<div class="post-metadata">

**Author:** ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)\
**Post date:** [February 7, 2024, 3:14pm UTC](https://discourse.julialang.org/t/how-not-to-commit-type-piracy-when-handling-automatic-conversion-for-field-with-union-type/109877/4 "2024-02-07T15:14:58Z")

</div>

ok I have it maybe:

```julia

julia> struct A end

julia> struct B
           x::Union{A, Int64}
           y::Union{A, Int32}
           z::Union{A, Int16}
           k::Union{A, Int8}
           B(x, y, z, k) = new(maybe_convert(x), maybe_convert(y), maybe_convert(z), maybe_convert(k))
           end

julia> maybe_convert(x::A) = x
maybe_convert (generic function with 1 method)

julia> maybe_convert(x::T) where {T} = convert(T, x)
maybe_convert (generic function with 2 methods)

```

---

<div class="post-metadata">

**Author:** ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)\
**Post date:** [February 7, 2024, 3:17pm UTC](https://discourse.julialang.org/t/how-not-to-commit-type-piracy-when-handling-automatic-conversion-for-field-with-union-type/109877/5 "2024-02-07T15:17:34Z")

</div>

I think you need to pass the type to `maybe_convert` or else it doesn’t do anything, no?

```julia-repl
julia> struct A end
julia> struct B
           x::Union{A, Int64}
           y::Union{A, Int32}
           z::Union{A, Int16}
           k::Union{A, Int8}
           B(x, y, z, k) = new(maybe_convert(Int64, x), maybe_convert(Int32, y), maybe_convert(Int16, z), maybe_convert(Int8, k))
           end

julia> maybe_convert(_, x::A) = x
maybe_convert (generic function with 1 method)

julia> maybe_convert(T, x) = convert(T, x)
maybe_convert (generic function with 2 methods)

```

---

<div class="post-metadata">

**Author:** ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)\
**Post date:** [February 7, 2024, 3:19pm UTC](https://discourse.julialang.org/t/how-not-to-commit-type-piracy-when-handling-automatic-conversion-for-field-with-union-type/109877/6 "2024-02-07T15:19:57Z")

</div>

Lovely, thank you a lot!

---

<div class="post-metadata">

**Author:** ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)\
**Post date:** [February 7, 2024, 3:30pm UTC](https://discourse.julialang.org/t/how-not-to-commit-type-piracy-when-handling-automatic-conversion-for-field-with-union-type/109877/7 "2024-02-07T15:30:44Z")

</div>

I think you could even automate that… But not sure whether this code is a good idea in practice 🙂

```julia
function maybe_convert_recursively(T::Union, x)
    try
        return convert(T.a, x)
    catch err
        err isa MethodError && return maybe_convert_recursively(T.b, x)
        rethrow(err)
    end
end
maybe_convert_recursively(T, x) = convert(T, x)

struct B
    x::Union{A, Int64}
    y::Union{A, Int32}
    z::Union{A, Int16}
    k::Union{A, Int8}
    function B(args...)
        length(args) == fieldcount(B) || error("You need to pass $(fieldcount(B)) arguments to construct a B. Got $(length(args)).")
        converted_args = map(maybe_convert_recursively, fieldtypes(B), args)
        new(converted_args...)
    end
end

```

---

<div class="post-metadata">

**Author:** ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)\
**Post date:** [February 7, 2024, 3:33pm UTC](https://discourse.julialang.org/t/how-not-to-commit-type-piracy-when-handling-automatic-conversion-for-field-with-union-type/109877/8 "2024-02-07T15:33:07Z")

</div>

mmmh, interesting, but this will be a bit slower I guess in some cases 😃

I will use this inside a macro so I can actually create the syntax we discussed previously with a bit of effort

---

<div class="post-metadata">

**Author:** ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)\
**Post date:** [February 7, 2024, 3:36pm UTC](https://discourse.julialang.org/t/how-not-to-commit-type-piracy-when-handling-automatic-conversion-for-field-with-union-type/109877/9 "2024-02-07T15:36:47Z")

</div>

yeah, this is nice because it is recursive though, but I wouldn’t go this far, because I just want to mimick how normal structs work, I don’t think they have this kind of recursive behaviour

---

<div class="post-metadata">

**Author:** ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)\
**Post date:** [February 7, 2024, 3:38pm UTC](https://discourse.julialang.org/t/how-not-to-commit-type-piracy-when-handling-automatic-conversion-for-field-with-union-type/109877/10 "2024-02-07T15:38:59Z")

</div>

I actually don’t know whether this is slow or not… In principle it could compile away I think. ~~And it actually does 😃 See here (my function is called `f` not `maybe_convert_recursively`)~~

```julia-repl
julia> @code_native debuginfo=:none dump_module=:false (()->f(Union{A,B2,Int32,String}, "asdf"))()
	.text
	push	rbp
	mov	rbp, rsp
	mov	rax, qword ptr [r13 + 16]
	movabs	rdi, 140250117903312
	movabs	rsi, 140250096741664
	mov	rax, qword ptr [rax + 16]
	mov	rax, qword ptr [rax]
	movabs	rax, offset f
	call	rax
	pop	rbp
	ret
	nop	word ptr cs:[rax + rax]

```

~~No error handling to be seen 🙂~~  
Edit: I actually take that back. I don’t know why I wrapped the function in an anonymous function but if you don’t do that then you clearly the error survive. More over if you simple profile the function it get clear, that it is in fact **quite slow**

```julia-repl
julia> @btime f(Union{A,B2,Int32,String}, "asdf")
  97.152 μs (40 allocations: 1.34 KiB)
"asdf"

```

**So don’t use this approach!**

---

<div class="post-metadata">

**Author:** ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)\
**Post date:** [February 7, 2024, 4:07pm UTC](https://discourse.julialang.org/t/how-not-to-commit-type-piracy-when-handling-automatic-conversion-for-field-with-union-type/109877/11 "2024-02-07T16:07:32Z")

</div>

When you hit the catch block it is usually slow in my experience, the try usually has small overhead instead
