I think this leads to arranging function arguments according to their type instead of semantics. Otherwise you have to keep track of which of the arguments are mutating and which are not.

x, A, y, B = f(...)
# Mutating:
f!(x, A, y, B, ...)
# Non-mutating:
x, y = f!(A, B, ...)

Incremental computation is also affected:

x_t, A_t, y_t, B_t = f(....)
x += x_t
A += A_t
y += y_t
B += B_t

Instead of

f!(x, A, y, B, ...)

for a function

function f!(x, A, y, B, ...)
x += ...
y += ...
A += ...
B += ...
end

xref, yref = Ref(x), Ref(y)
f!(xref, X, yref, Y, ...)

There is no way to mutate a value. You can only mutate things stored inside the value. Consequently you can mutate [5] but not 5. There’s nothing Julia-specific about that statement.

I defend always the following: ! is a licence to modify, not an obligation.
Then

ynew = f!(y)

can be used as idiom for generic code which does not stop working when replacing a MVector by an SVector. Unfortunately that is not strictly followed in Base.