It's not very the same, yes.
When you have "" as string, it is a true string object. It has no chars in it, but you can do everything you want with it, like you can with any other strings. It can call functionality.
When you try to work with null, the string functionality can't be used. It's no real object, so concatenating (which is just a function in some way for the string object) isn't working.
But also some test cases:
case 1:
set s = null + "text"
-> compiler error. (expected)
Next, this is more or less, too, as expected, case 2:
JASS:
function nullAsString takes nothing returns string
local string s // just for no inline
return null
endfunction
local string s = nullAsString() + "test"
-> sums up to (null) , no valid string.
case 3 ..
local string s = "test" + nullAsString()
-> prints "test"
(at least from my theory above this is a bit unexpected)
And also, case 4:
JASS:
local string s = null
set s = s + "test"
-> printes "test"
(might be unexpected like case 3, because null can not have functionality)
===
Both last cases would not work in normal c++, for example.
One might argue case3 works over case2 because the first string is actually always the string which actually calls the functionality, so the concatenate function, and so it is valid. First string is the calling object. And then there are internal checks for the second parameter, if the string to add is null, which will result in like adding an empty string/just nothing.
Case 4, there is maybe some implicit string conversion directly done here, when assining null to a string variable,
local string s = null
. So from then it's simply seen as empty string "".
===
So when having a conrete string variable, set to null, it may act as "", while working directly with null doesn't work with string concatenation.
Working with functions that return null (without being stored into a string variable) does not work for calling string concatenation as first parameter, but it will not cause an invalid string, when given as second parameter.
Basically as long as the string is only input for some function that jass vm controls, and is not the calling object itself like in string concatenation, null seems always be reacted same like empty string "". It's most likely some jass comfort that it is like this, checking for null at many places to keep nicer usage.