Skip to content

Require constructors and convert to return objects of stated type? #42372

Open
@timholy

Description

@timholy

(This is a Julia 2.0 proposal, as it is breaking. It is surely not novel, but I don't see another such issue open.) We currently treat outer constructors and convert method definitions as any other generic function, but there's an argument to be made that they should be special-cased by requiring that they always return an object of the stated type. Motivations:

  1. At present, it's essentially a bug to write fallback methods like this:
foo(x::Int) = 1
foo(x) = foo(Int(x))

Why? Because someone might define

struct Foo end
Int(f::Foo) = f

in which case you get

julia> foo(Foo())
ERROR: StackOverflowError:
Stacktrace:
 [1] foo(x::Foo) (repeats 79984 times)
   @ Main ./REPL[3]:1

and StackOverflowErrors are to be avoided: they can take forever to resolve in some cases, they can at least in principle trash your session, etc.

The only good way to write such a fallback method is

foo(x) = foo(Int(x)::Int)

but I will bet that the vast majority of developers do not know this or bother with it, and probably everyone finds it ugly and annoying.

Some might complain about idempotent operators and involutions, for example one might be tempted to define

struct Not
    x
end
Not(x::Not) = x.x   # just strip the `Not` wrapper rather than returning `Not(Not(5))`

But a solution is to separately introduce not from Not, and allow this behavior for not but not Not (Not must always wrap in another Not layer).

  1. In addition to surprising the developer, failing to enforce this is much harder on inference and the compiler, sometimes with dramatically increased risk of invalidation. Consider the case of Expr constructor sometimes returns a Symbol, Bool, etc julia-vscode/CSTParser.jl#308. For the implementation in https://github.com/julia-vscode/CSTParser.jl/blob/778212a77d2977b94a531240877eacff00700111/src/conversion.jl#L133-L203 it is impossible to predict the outcome. For certain package combinations with the SciML ecosystem, this constructor alone is responsible for thousands of invalidations. Such problems can only be detected by devoted and sophisticated sleuths, but designing the language to be bullet-proof against this solves it neatly with very little cost (when you want to return something different, just check before you call the constructor, not after).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions