The confusion stems from this: assignment and mutation are not the same thing.
Assignment. Assignment looks like `x = ...` – what's left of the `=` is a variable name.
Assignment changes which object the variable `x` refers to (this is called a variable binding). It doesn't mutate any objects at all.
Mutation. There are two typical ways to mutate something in Julia:
- `x.f = ...` – what's left of the `=` is a field access expression;
- `x[i] = ...` – what's left of the `=` is an indexing expression.
Currently, field mutation is fundamental – that syntax can *only* mean that you are mutating a structure by changing its field.
This may change. Array mutation syntax is not fundamental – `x[i] = y` means `setindex!(x, y, i)` and you can either add methods to setindex! or locally change which generic function `setindex!`. Actual array assignment is a builtin – a function implemented in C (and for which we know how to generate corresponding LLVM code).
Mutation changes the values of objects; it doesn't change any variable bindings. After doing either of the above, the variable `x` still refers to the same object it did before; that object may have different contents, however. In particular, if that object is accessible from some other scope – say the function that called one doing the mutation – then the changed value will be visible there. But no bindings have changed – all bindings in all scopes still refer to the same objects.
You'll note that in this explanation I never once talked about mutability or immutability. That's because it has nothing to do with any of this – mutable and immutable objects have exactly the same semantics when it comes to assignment, argument passing, etc. The only difference is that if you try to do `x.f = ...` when x is immutable, you will get a runtime error.
Maybe we should put this explanation in the manual somewhere. If someone wants to make a pull request doing that, you can use any of my text here.