The manual and docstring describes the interface it should have, but remember most Julia functions are extensible. For example, I can do this:
julia> struct EmptyType end
julia> Base.UInt16(x::EmptyType) = "hello"
julia> Base.convert(::Type{UInt16}, x::EmptyType) = 1.0
julia> x = EmptyType()
EmptyType()
julia> UInt16(x)
"hello"
julia> convert(UInt16, x)
1.0
That’s terrible design, but nothing at the language level prevents it. (A frequently-requested feature sometimes called “interfaces” or “protocols” would make it possible to ban such methods.)
Let me also correct something I implied that’s a bit wrong. I implied that UInt16(x)
called convert(UInt16, x)
, but actually it now the other way around (it used to be the opposite, and I’m slow to change…). So the UInt16(val::Any)
calls one of these:
julia> methods(UInt16, (Any,))
# 13 methods for type constructor:
[1] UInt16(x::Union{Bool, Int32, Int64, UInt32, UInt64, UInt8, Int128, Int16, Int8, UInt128, UInt16}) in Core at boot.jl:711
[2] UInt16(x::Float32) in Base at float.jl:685
[3] UInt16(x::Float64) in Base at float.jl:685
[4] (::Type{T})(x::Float16) where T<:Integer in Base at float.jl:71
[5] (::Type{T})(z::Complex) where T<:Real in Base at complex.jl:37
[6] (::Type{T})(x::Rational) where T<:Integer in Base at rational.jl:109
[7] (::Type{T})(x::BigInt) where T<:Union{UInt128, UInt16, UInt32, UInt64, UInt8} in Base.GMP at gmp.jl:347
[8] (::Type{T})(x::BigFloat) where T<:Integer in Base.MPFR at mpfr.jl:324
[9] (::Type{T})(x::T) where T<:Number in Core at boot.jl:716
[10] (::Type{T})(x::Base.TwicePrecision) where T<:Number in Base at twiceprecision.jl:243
[11] (::Type{T})(x::AbstractChar) where T<:Union{AbstractChar, Number} in Base at char.jl:50
[12] (::Type{T})(x::Enum{T2}) where {T<:Integer, T2<:Integer} in Base.Enums at Enums.jl:19
[13] (dt::Type{var"#s827"} where var"#s827"<:Integer)(ip::Sockets.IPAddr) in Sockets at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Sockets/src/IPAddr.jl:11
First, you’ll note there is no specific method for x::Any
. Moreover, in inference Any
means “I don’t know” and not “it’s of type Any
” since no actual object has type Any
. Consequently inference has to deal with the possibility that any of a whole list of methods might be called. For reasons of performance, Julia does not run inference on a method/type combination until necessary, so Julia doesn’t know in advance that all 13 of those methods either return a UInt16
or throw an error. If the number of methods is 4 or fewer, Julia will run inference on all of them and might discover that they all return an UInt16
, but beyond 4 methods it instead sets the return type as Any
. The tradeoffs are discussed in new call site inference algorithm · Issue #34742 · JuliaLang/julia · GitHub.