# Could introducing formal interfaces be nonbreaking?

**URL:** <https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567>\
**Category:** Internals & Design\
**Tags:** speculative\
**Created:** [June 2, 2025, 2:10pm UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567 "2025-06-02T14:10:58Z")\
**Posts on this page:** 17\
**Page:** 2

<div class="post-metadata">

**Author:** ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)\
**Post date:** [June 2, 2025, 6:59pm UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/21 "2025-06-02T18:59:54Z")

</div>

At any rate, I’m doubtful that runtime interface checking (e.g., via the JIT compiler) would be a good fit for Julia. Interface checking could make sense in a static analysis context, like JET.jl, and it definitely makes sense to have a unit test suite for interfaces.

For example, consider the original example of an interface specification provided by Chris:

```julia
f(a::T, b::T)::T

```

This form of interface specification is quite limiting in the current Julia world of single inheritance. In particular, I wouldn’t be able to declare a function signature like this:

```julia
f(a::Iterator, b::Iterator)::Iterator

```

(Though I don’t know, maybe the compiler folks have ideas for more complicated interface specifications?)

Julia has never had the ability or semantics to constrain the output type of generic functions. It seems counter to the dynamic nature of the language to add it now, and I suspect it would lead to unintended consequences.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [June 2, 2025, 10:47pm UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/22 "2025-06-02T22:47:10Z")

</div>

A breaking change is about the API and has never meant anything that made any code stop working. If that were the case, changing internals would be breaking changes because it disrupts packages that rely on them. For obvious reasons, internals don’t print warnings or throw errors every time they’re used. Docstrings may very well need correction sometimes, but that doesn’t mean a random user’s unilateral decision to defy the documented API becomes API.

It’s worth pointing out that even formal interfaces in general may not throw all the warnings and errors we need; static type analysis will run into a wall every time there’s a runtime dispatch, even if the only existing methods do conform to the interface. Sometimes it may just be like `public`; something we can look up interactively without having to search text in the documentation and otherwise completely silent.

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [June 2, 2025, 11:21pm UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/23 "2025-06-02T23:21:45Z")

</div>

> [@Benny](#):
>
> A breaking change is about the API and has never meant anything that made any code stop working.

nobody is saying the criterial is “any code”, people are specifically talking about things like `==()` which is a public function, and it so far does not required to always return Bool, or take the example of `AbstractArray`, there’s not a set of interface package that subtype this must satisfy. So if one day someone’s package which defines `T <: AbstractArray` suddenly starts to break, you’ve introduced a breaking change, which is not allowed in Julia 1.x

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [June 2, 2025, 11:35pm UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/24 "2025-06-02T23:35:57Z")

</div>

> [@jling](#):
>
> it so far does not required to always return Bool

Really, because I also see this there now:

> [@nsajko](#):
>
> To be specific, this is what the doc string says currently:
> 
> > The result is of type `Bool`, except when one of the operands is [`missing`](https://docs.julialang.org/en/v1.13-dev/manual/missing/#missing), in which case `missing` is returned ([three-valued logic](https://en.wikipedia.org/wiki/Three-valued_logic)).

> [@jling](#):
>
> take the example of `AbstractArray`, there’s not a set of interface package that subtype this must satisfy.

This seems pretty clear: [Interfaces: AbstractArray](https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-array)

> [@jling](#):
>
> So if one day someone’s package which defines `T <: AbstractArray` suddenly starts to break

Again, this is just the colloquial sense of code failing. Breaking changes are entirely about what the developer does to their API. A breaking change could by chance not affect anyone’s code, trivially if nobody is using it.

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [June 3, 2025, 12:18am UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/25 "2025-06-03T00:18:54Z")

</div>

> [@goerz](#):
>
> (Edit: actually, I think [one of the comments on the issue](https://github.com/JuliaLang/julia/pull/53024#issuecomment-1913070339) nails it: It’s fine to use `==` with a non-standard meaning in a DSL macro, but not in actual code)

Is `missing` a DSL? Because `missing == missing -> missing` which is not a Bool. Why is `missing` special? Should interfaces allow for specifically having a different rule on one concrete type, or should the rule have to apply to all types?

I would argue that if we have to single out specific types in the rule for which the rule doesn’t apply to, we’ve got the rule wrong and should go back to the drawing board. We’ve always said Base isn’t special, we could do things in a package, but here is a case where `MyMissings.jl` would not be possible by definition. Are we okay with losing that fundamental tenant of Julia for a simpler interface on this function?

> [@Benny](#):
>
> This seems pretty clear: [Interfaces: AbstractArray](https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-array)

It’s not entirely, it’s actually quite ambiguous.

> `size(A)`: Returns a tuple containing the dimensions of `A`

Are those dimensions supposed to be scalar? Or for example with a ragged array, should it be a tuple of vectors for the dimensions of each vector?

Base code generally assumes that `size(A)` is a tuple of scalars, but it’s not actually in the interface definition.

> getindex(A, i::Int): (if `IndexLinear`) Linear scalar indexing

Should `A[i]::T` where eltype(A)==T? That isn’t required in the definition of the interface, but much of Base code relies on the idea that scalar indexing always gives something of `eltype(A)`. A classic type which violated this was always Tracker.jl’s TrackedArray, which stated `eltype(A) == Float64` but `a[i] == TrackedReal{Float64}`.

And for that matter, is `eltype(::AbstractArray{T,N}) where {T,N} = T` supposed to be true? Because the interface does not specify how `eltype` works.

> `getindex(A, I::Vararg{Int, N})`: (if `IndexCartesian`, where `N = ndims(A)`) N-dimensional scalar indexing

Is the identity `getindex(A, Base.Cartesian.CartesianIndex(i)) == getindex(A, i)` expected to hold? Because that’s not specified.

> setindex!(A, v, i::Int): (if `IndexLinear` ) Scalar indexed assignment

```julia

julia> a = rand(2)
2-element Vector{Float64}:
 0.8135346986182869
 0.7300081330807777

julia> a[1]= 2
2

```

Is it expected that `setindex!(A, v::T, i::Int)::T`, i.e. that it also returns `v`? Again there is some Base code in the AbstractArray that relies on this, but it’s not necessarily stated as something that should be true for all AbstractArrays

> length(A): **Default definition** `prod(size(A))`

Is this expected to be true, or just a default? There are some codes (IIRC broadcast is one?) that assumes this is true.

> `similar(A)`: Return a mutable array with the same shape and element type

Is `CuArray` considered mutable if `allowscalar` is false? Because then `A[1] = v` always errors. How do we define “mutable” here: that `A[i] = v` is a valid non-erroring operation? Since if it’s the latter, `similar(A::CuArray)::CuArray` is not correct.

Other functions aren’t even mentioned. Is `zero(x::T)::T` an identity we expect for array types? The only case I know that violates that is Measurements.jl.

I highly encourage folks to think beyond their own immediate codes when thinking about interface definitions and think holistically about how it applies to other codes and the downstream effects. When doing so, you get some not necessarily an easy questions to answer, and its okay for us to document the current limitations of our knowledge and understand that we commonly get interface definitions wrong or ambiguous.

---

<div class="post-metadata">

**Author:** ![adienes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/adienes/32/37459_2.png) [@adienes](https://discourse.julialang.org/u/adienes)\
**Post date:** [June 3, 2025, 1:00am UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/26 "2025-06-03T01:00:44Z")

</div>

> [@ChrisRackauckas](#):
>
> Why is `missing` special?

I ask myself this question frequently… the fact that `==` with `missing` infects collections that could possibly potentially maybe contain a `missing` is so annoying. or the fact that it takes `any` and `all` hostage with mandatory three-valued logic.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [June 3, 2025, 1:55am UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/27 "2025-06-03T01:55:39Z")

</div>

I stand corrected. We wouldn’t even need to wait for much formality, it’d be good just to clarify which of those assumptions were actually intended to be part of the interface. And that’s a tougher collaborative job because it’s worth weighing whether the interface can include the challenging implementations.

> [@ChrisRackauckas](#):
>
> Is it expected that `setindex!(A, v::T, i::Int)::T`, i.e. that it also returns `v`? Again there is some Base code in the AbstractArray that relies on this

Seems like `setindex!` returns `A`, though that’s an assumption worth arguing over:

```julia
julia> setindex!(a, 3, 2)
2-element Vector{Float64}:
 2.0
 3.0

```

It’s only the [assignment syntax](https://docs.julialang.org/en/v1/manual/variables/#man-assignment-expressions) that evaluates to the right hand expression, and that guarantee isn’t part of the `AbstractArray` interface.

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [June 3, 2025, 8:49am UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/28 "2025-06-03T08:49:05Z")

</div>

> [@araujoms](#):
>
> Such as?

As said, there’s many potential options, but the most obvious option would be an interface where you can insert checks into your test suite to make sure you’ve satisfied it.

E.g. staying with the `==` example, if Base did

```julia
@interface ((==)(::T, ::U) where {T, U})::Bool

```

then say you wrote

```julia
struct Foo end
Base.:(==)(f::Foo, x) = f

```

then we could make it so that this doesn’t throw any errors, but then in your test suite you could have

```julia
@test satisfies_interface((==), Foo())

```

or whatever, and have _that_ be responsible for throwing errors. We could even potentially integrate something like this into Aqua.jl so you’re alerted and automatically tested if you touch any interface methods.

Adding interfaces in this way would be non-breaking.

* * *

There’s multiple reasons we might want to go this way too, not just because it’d be non-breaking. Another reason to do it this way is that _gasp_ not all interface contracts can be expressed or checked statically. Sometimes they depend on runtime values, so we’d presumably want to include actual value-based runtime checks in our interfaces, and we definitely wouldn’t want those checks to run each time you call a function which is part of an interface (unless you’re in some sort of special debug-mode)

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [June 3, 2025, 11:11am UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/29 "2025-06-03T11:11:12Z")

</div>

> [@araujoms](#):
>
> talk about formal interfaces coming to the base language.

Please add some links to make the discussion more concrete.

There have been many proposals and discussions.

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [June 3, 2025, 11:15am UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/30 "2025-06-03T11:15:27Z")

</div>

The context here is that this thread was deemed off topic and split off from [Efficiency of abstract types vs `Any`](https://discourse.julialang.org/t/efficiency-of-abstract-types-vs-any/129518), where Chris’s comment mentioned [“when interfaces are added to the language”](https://discourse.julialang.org/t/efficiency-of-abstract-types-vs-any/129518/2)

Chris didn’t really get into any particular interface design there, but the mention of it piqued some interest from people, including Mateus, and another thread which was separately split off.

---

<div class="post-metadata">

**Author:** ![araujoms](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/araujoms/32/217734_2.png) [@araujoms](https://discourse.julialang.org/u/araujoms)\
**Post date:** [June 3, 2025, 1:08pm UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/31 "2025-06-03T13:08:57Z")

</div>

I’d be happier with something like strict mode. That gives a path for the language to evolve. I’m afraid that the current focus on avoiding breaking changes at any cost will lead to stagnation.

A test-based approach is less impactful, because it requires either a test to be written for every function, or for something like Aqua to be run in CI (which is almost never done). The default is to be lazy and not do it, and the inevitable result is to have countless of silent interface violations.

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [June 3, 2025, 1:10pm UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/32 "2025-06-03T13:10:56Z")

</div>

And indeed the hole was filled with rabbits 🐰 🐰 🐰 🐰 🐰 🐰

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [June 3, 2025, 1:12pm UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/33 "2025-06-03T13:12:24Z")

</div>

> [@Mason](#):
>
> another thread which was separately split off

I understand now. This happened many times before, so from a practical perspective, I do not have to fear formal interfaces as being imminent 😉

In this particular case, all is required is though is inferring the return type. That could be accomplished with a much more restricted, opt-in addition to the language where that is defined.

---

<div class="post-metadata">

**Author:** ![araujoms](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/araujoms/32/217734_2.png) [@araujoms](https://discourse.julialang.org/u/araujoms)\
**Post date:** [June 4, 2025, 1:34pm UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/34 "2025-06-04T13:34:22Z")

</div>

From the performance tips:

> In the case that the type of `a[1]` is not known precisely, `x` can be declared via `x = convert(Int32, a[1])::Int32`. The use of the [`convert`](https://docs.julialang.org/en/v1.13-dev/base/base/#Base.convert) function allows `a[1]` to be any object convertible to an `Int32` (such as `UInt8`), thus increasing the genericity of the code by loosening the type requirement. Notice that `convert` itself needs a type annotation in this context in order to achieve type stability. This is because the compiler cannot deduce the type of the return value of a function, even `convert`, unless the types of all the function’s arguments are known.

This is just begging for having formal interfaces. And it is not achievable in a non-breaking way, even in strict mode. Unless all your dependencies are also in strict mode.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [June 4, 2025, 1:50pm UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/36 "2025-06-04T13:50:41Z")

</div>

> [@araujoms](#):
>
> From the performance tips:
> 
> > […] Notice that `convert` itself needs a type annotation in this context in order to achieve type stability. This is because the compiler cannot deduce the type of the return value of a function, even `convert`, unless the types of all the function’s arguments are known.

Relevant issue:

- [Require constructors and `convert` to return objects of stated type? · Issue #42372 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/42372)

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [June 4, 2025, 1:57pm UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/37 "2025-06-04T13:57:36Z")

</div>

> [@ChrisRackauckas](#):
>
> Other functions aren’t even mentioned. Is `zero(x::T)::T` an identity we expect for array types? The only case I know that violates that is [Measurements.jl](https://juliaregistries.github.io/General/packages/redirect_to_repo/Measurements).

I think you’re confusing with `one` (and Measurements.jl follows precisely the [`one`](https://docs.julialang.org/en/v1/base/numbers/#Base.one)/[`oneunit`](https://docs.julialang.org/en/v1/base/numbers/#Base.oneunit) semantic, doesn’t violate anything):

```julia-repl
julia> using Measurements

julia> x = measurement(1.0)
1.0 ± 0.0

julia> typeof(x)
Measurement{Float64}

julia> zero(x)
0.0 ± 0.0

julia> typeof(zero(x))
Measurement{Float64}

```

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [June 4, 2025, 2:10pm UTC](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567/38 "2025-06-04T14:10:18Z")

</div>

Right, and similarly `zero` isn’t guaranteed to be the same type; it’s just the (or an?) additive identity. E.g.,

```Julia
julia> using Dates

julia> typeof(now()), typeof(zero(now()))
(DateTime, Millisecond)

```

[Previous page](https://discourse.julialang.org/t/could-introducing-formal-interfaces-be-nonbreaking/129567.md?page=1)
