to match the performance of the un-annotated version. Your original code converts an array of concrete type String to an array of abstract type AbstractString, which causes a performance penalty. So return type annotation is sometimes not just an “annotation” but can trigger implicit conversion; a type error is only produced if this conversion is impossible.
@greatpet Yes, I understand. However I generally used return type annotations when I wanted to clarify my “interfaces”. In such context, I didn’t want to prematurely restrict the function to return a vector of String.
I was responding to a comment about using return-type declarations to “clarify”, i.e. document.
And return-type annotations are “enforced” by calling convert in Julia, which means that they aren’t necessarily a validation mechanism — they can hide a type-instability.
Given that AbstractString is an abstract type, what does it mean for a String to be converted to AbstractString? Presumably in memory these have the same representation. Surely it is more likely that the type specification of AbstractString prevents some compiler optimizations - perhaps function call inlining? (On the basis that AbstractString could have multiple implementations and therefore there is not a single function (memory address) which can be guaranteed to exist.)
Note that this is specifically about vectors of AbstractString — and specifically Vector{AbstractString}. This is a very different object than a Vector{String}, even if the contents are the same. In particular, you can change the contents of a Vector{AbstractString} to include non-Strings, but you cannot do that with a Vector{String}. The latter can only contain Strings.
The vector itself needs to be able to know about and handle that that possibility. That’s why the convert isn’t a no-op. The container itself must be different. It’s also why downstream uses aren’t as optimized — any code that retrieves elements from it must be prepared to handle any AbstractString.
This is different than annotating f()::AbstractString = "hello" — that will be a no-op and won’t pessimize any code. In fact, there’s no object that is just an AbstractString — everything in Julia is a concrete type. You can, however, have a container like a vector that is allowed to hold any subtype.
I’ve realized what I wrote was poorly worded. This is what I meant by
… meaning Vector{String} not Vector{AbstractString}.
Basically - it’s to do with dispatching functions. If the whole vector contains the same type there is no lookup at runtime required to dispatch the correct method.
I see - so no actual conversion is being done?
Or at least, ["hello"] which is a Vector{String} is converted to a Vector{AbstractString} but the elements are just memcopy’d, and they (the elements) remain at runtime, still of type String.
There will never exist some object x in Julia for which typeof(x) === AbstractString. Full stop. Doesn’t exist. Can’t exist.
When you convert an Vector{String} to a Vector{AbstractString} you’re creating a new container. It has exactly the same elements as the old container. Those elements have exactly the same types — they’re still all String. But that new container has a new behavior: you can store things like SubStrings in it now, too.