However, bugs in general suck and we've been thinking a fair bit about what additional tooling the language could provide to help people avoid the classes of bugs that Yuri encountered in the post.
The biggest class of problems in the blog post, is that it's pretty clear that `@inbounds` (and I will extend this to `@assume_effects`, even though that wasn't around when Yuri wrote his post) is problematic, because it's too hard (arguably impossible) to write correctly. My proposal for what to do instead is at https://github.com/JuliaLang/julia/pull/50641.
Another common theme is that while Julia is great at composition, it's not clear what's expected to work and what isn't, because the interfaces are informal and not checked. This is a hard design problem, because it's quite close to the reasons why Julia works well. My current thoughts on that are here: https://github.com/Keno/InterfaceSpecs.jl but there's other proposals also.
This is THE huge issue when combined with the other variables:
- you often have hundreds of depedencies that interlock
- you often have dependencies that work with multiple packages, and they will often pull them all into your tree (there are sometimes "bridge packages" written, but that is O(n^2) in the number of packages)
- many maintainers do not care about good versioning practices, because they are not enforced, and it is not their problem when they break a bajillion other packages
- many people writing Julia code are not software developers. this is great! but they usually don't look at what their testing coverage looks like. often the CI always fails on a package and it's ignored.
Would Julia only allow multiple dispatch as a link between those packages, the situation would look a little better. But often the packages also talk through simple accesses of values like `X.someproperty`. I've seen situations where maintainers would add and remove these properties and break their own other libraries. Better "enforcement" of these types of things - however that would look like - would be a huge improvement for the time sink that is maintaining a Julia package.
I think this is a cultural issue to a large degree. I think even if there were better enforcement, it's just a question of coding of discipline to then actually not break something. If it were easier to anticipate what sort of breakage a change could entail (say, by making clear & documented _by the core language_ what is considered a breaking change, and then actually not even doing "technically breaking" changes and using deprecations instead), this COULD change.
That being said, that requires a very different development workflow than what is currently practiced in the main repos of the language, so there's quite a bit of an uphill battle to be had (though such issues happen there all the time, so solving that would actually help here the most, ironically).
i.e. what I am talking about is effectively informally encoding protocols/interfaces and suchlike in a test-suite, rather than seeking to make it a part of the compiler/language.
Not as sexy, no doubt, but flexible and with a short time to get feedback.
Over the last year we've been growing the domain of these as well, and have seen a decrease in bug reports come from it.
My own efforts (shameless plug, https://github.com/Seelengrab/PropCheck.jl for property based testing inspired by Hedgehog and https://github.com/Seelengrab/RequiredInterfaces.jl for somewhat formalizing "what methods are needed to subtype an abstract type") are unused in the wider community as far as I can tell, in spite of people speaking highly of them when coming across them. I also don't think Kenos InterfaceSpecs.jl is the way forward either - I think there's quite a lot of design space left in the typesystem the language could do without reaching for z3 and other SAT/SMT solvers. I personally attribute the lack of progress on that front to the lack of coherent direction of the project at large (and specifically not to the failings of individuals - folks are always very busy with their lives outside of Julia development/other priorities). In spite of the fact that making this single area better could be a big boon with more traditional software engineers, which are very underrepresented in the community.
* New trait systems define the allowed inputs into given solvers in order to enforce guarantees https://sciml.ai/news/2022/10/08/error_messages/
* The SciMLOperators.jl interface clarified operators (https://docs.sciml.ai/SciMLOperators/stable/interface/) * The highest level SciMLBase interface got codified (https://docs.sciml.ai/SciMLBase/stable/) along with the trait systems it requires.
* ArrayInterface.jl (https://docs.sciml.ai/ArrayInterface/stable/) and StaticArrayInterface.jl (https://docs.sciml.ai/StaticArrayInterface/stable/) have dropped fallback definitions and require definition of traits for downstream packages in order for any array types to be allowed into package functions
* SciMLStyle (https://github.com/SciML/SciMLStyle) came into existence and defines a style which avoids any of the behaviors seen in the blog post.
* Julia came out with package extensions in v1.9 (https://www.youtube.com/watch?v=TiIZlQhFzyk) and with the interface checking, implicit interface support was turned mostly into explicit interface support where packages and types opt-in via defining traits (this is still continuing to evolve but is mostly there).
Given all of these changes, most of the things in the blog post would now error in many standard packages without someone explicitly defining interface trait functions to allow an object in that doesn't satisfy the interface which it's claiming to. Of course, not every person or every package has changed, but what I described here are major interface, style, and cultural changes to some of the most widely used packages since 2020.
What interface checking? Base doesn't provide any such facilities. Package extensions are still ad-hoc constructions on a package by package basis; there is little to no consistency.
> Of course, not every person or every package has changed, but what I described here are major interface, style, and cultural changes to some of the most widely used packages since 2020.
And none of that has landed in Base, none of that is easy to find out about/discoverable for non-SciML users. SciML is not all of Julia, and certainly not Base or packages outside of SciML. Please don't frame this as if SciML was the only thing that mattered here.
Yes, SciML is doing good. I'm not denying that, and never have. Still, the rest of the community/package ecosystem is not good at catching up - which is what I'm criticizing.
I think it's because evaluating a language is horribly complicated, and people crave certainty and the article sounds very definive. Maybe people see it as a sort of the article is right/Julia bas vs the article is wrong/Julia good.
Yea, that would be me. Julia seemed like a good fit but I don't want to spend months bug hunting someone else's language
Oh, that explains it. I thought it enabled a bounds check that somehow allowed code to run rather than fault. So it is a promise to the compiler that the code is correct and doesn't need checking. Composition with arbitrary bounds silently breaks that promise. Go it.
What happened here is that julia has a package called OffsetArrays.jl which can be used to create arrays with arbitrary index offsets. Julia arrays start at index 1 (1-based indexing), but with OffsetArrays you could make 0-based arrays, or 10-based arrays or whatever you want.
There happened to be a bunch of code laying around which was older than OffsetArrays.jl and did things like
function f(v::AbstractVector)
for i in 1:length(v)
@inbounds do_something(v[i])
end
end
which implictly assumed that all abstract vectors could be indexed according to normal 1-based indexing rules. This created a lot of silent problems when people then went and fed offset arrays into functions that were using such incorrect indexing schemes.Instead, modern julia code that works on AbstractArray is supposed to be written using functions like eachindex, or axes, i.e.
function f(v::AbstractVector)
for i in eachindex(v)
@inbounds do_something(v[i])
end
end
though Julia's compiler is now smart enough that using @inbounds like this is typically unnecessary, since it can see that eachindex(v) can't produce an out-of-bounds index.The problematic code was incorrectly saying it worked on any subtype of AbstractArray, but was actually written assuming that the arrays were 1-based which is an invalid assumption.
Imagine trying the build something around gcc's internals, just because the gcc project exposed those as a public API and marketed it as such.
I mean, fundamentally, keeping a C++ abi backwards compatible is a horror story. It like, just doesn't work, for non-trivial projects. So LLVM breaks all the time.
https://docs.julialang.org/en/v1/devdocs/llvm/
Julia got like 12 custom LLVM passes. They are deep into the LLVM. And they seems to include C++ headers from the LLVM project, not using the C api.
In practice you'd want insider maintainers in the LLVM project to guard your dependency like it would have been an in tree front-end.
I say this as one of the devs that usually do the work of keeping up the latest LLVM.