Looks like a bug to me, either in the documentation or implementation. If that function is supposed to return true there, the documentation doesn’t say that.
The implementation is
t = unwrap_unionall(t)
# TODO: what to do for `Union`?
isa(t, DataType) || return false
hasfield = !isdefined(t, :types) || !isempty(t.types)
return hasfield || (t.size == 0 && !isabstracttype(t))
Note that even if they’d be implemented in pure julia, they’d still be structs - one hypothetical version could look like
(you wouldn’t use this of course, since the data is no longer stored inline and for other reasons).
Containers generally can’t be anything other than structs - having them as primitive types is usually not an option, due to their variable size & different meanings of parts of their bits (i.e. fields).
That sounds more like a technicality than useful documentation… E.g. if it were to say something like
Determine whether objects of type T behave no different from structs (i.e. objects declared using the struct or mutable struct keywords).
I’d bet people would be confused about what other kinds of objects aside from structs & primitive types there are, as that would seemingly imply a third option when there isn’t.
Arrays and vectors are, for all intents and purposes, structs and should be treated as such. How that is checked internally is not relevant and can’t be reduced to how it was written in code anyway - you can always create structs & objects manually using the C API of the runtime when e.g. embedding julia.
Arrays only fit the last definition, as they contain a length & its data (and some other bits) in their (unexposed) fields. How else would you check for whether something is a struct type except for checking whether it has fields? That’s the distinguishing feature after all.