As @chepner said, bash doesn't have array values. When you pass an array to a function, what you're really doing is passing each element of the array as a separate argument to that function.
All shell functions can ever return is a one-byte exit code value, 0-255. The only way they can return anything else is to output it, with echo or printf or whatever; the caller then has to capture that output in any of the usual ways (command substitution, process substitution, redirection into a file to read, etc).
That said, your original code would work if you just added a bit of syntax to the call:
b=($(sort_array "${a[@]}"))
But that relies on the elements of the sorted array being strings that parse as individual words. A safer version would be to change the sort_array function to print out one element per line; the caller can then read those lines into an array using the mapfile builtin (alias readarray; requires Bash 4.x). That looks something like this:
function sort_array {
declare -a array=("$@")
local sorted=()
sorted+=("1")
sorted+=("2")
sorted+=("3")
printf '%s\n' "${sorted[@]}"
}
mapfile -t b < <(sort_array "${a[@]}")
That says to read the array b from the output of the command inside <(...); the -t tells it not to include the newlines in the array values.
Even safer would be to use null characters instead of newlines; easiest if you have bash 4.4 which added an option to mapfile to use a different character in lieu of newline:
function sort_array {
declare -a array=("$@")
local sorted=()
sorted+=("1")
sorted+=("2")
sorted+=("3")
printf '%s\0' "${sorted[@]}"
}
mapfile -t -d '\0' b < <(sort_array "${a[@]}")
b... it returns empty.b=($b)right after the function call gives the expected result. A string assignment to array variable would place that value in the first index only.b=($(sort_array a[@])). If you want to post that as an answer I'll accept it.