Why are self-referential struct parameters impossible?

I think the wider reason for global functions being recursive is that they don’t need to know anything about their global variables until they are compiled. The backedges help in the case that the global variable wasn’t defined by the time it’s compiled, which needs to be undone and retried after defining the variable. Locally scoped functions are implemented as callable instances instantiated at definition, so while they can be recursive, they can’t know the type of the function in the recursive call, which really hurts performance. There too are ideas about how to rectify that.

Also I think it’s worth reiterating that this is about parameters constraints specifically. The earlier linked mutually recursive structs issue doesn’t address that directly AFAIK, but the forward declarations idea there could also help the undefined variable error here. Maybe this could be resolved without resolving that issue, as self-recursive structs are already possible:

julia> mutable struct Node1 next::Node1; Node1() = new() end

julia> let x = Node1();  x.next = x  end
Node1(Node1(#= circular reference @-1 =#))

julia> mutable struct Node2{T} next::T; Node2{T}() where T = new() end

julia> let x = Node2{Node2}();  x.next = x  end
Node2{Node2}(Node2{Node2}(#= circular reference @-1 =#))

Looking into Meta.@lower mutable struct Node{T<:Node} next::T end, it seems as if the undefined variable error is thrown by Core.TypeVar(:T, Node) before Node exists. I think Node starts existing at a Core._structtype call that takes the TypeVar as an input, and the result seems mutable based on subsequent calls, including Core._typebody! annotating fields. Unfortunately, I couldn’t begin to tinker because trying that call by itself in the REPL just crashed Julia.

4 Likes