In Rust, bindings are immutable by default. So you might think that the following are equivalent:
readonly List<int> list = new List<int>(); // C#
let list = Vec::new(); // Rust
And these:
List<int> list = new List<int>(); // C#
let mut list = Vec::new(); // Rust
But, as you found out, this isn't quite the case.
Inside the Add method in the C# version, there is no information about the binding that you used to invoke it. The Add method isn't able to declare that it mutates its data so there is no way for the C# compiler to prevent you passing a reference to a readonly binding. The readonly keyword prevents you overwriting the list binding with a completely new List, but it doesn't prevent you changing data inside the one you have. C# prevents you from changing the value of a readonly binding, but the value in this case is a pointer to your data, not the data itself.
In Rust, if a method needs to mutate the underlying data, it must declare its first argument to be self or &mut self.
In the case of self, then the data is moved into the method, and you can no longer use the original binding. It doesn't matter if the method changes the data because the caller can't use that binding any more.
In the case of a mutable reference, &mut self, Rust will only let you create it if the original binding is also mutable. If the original binding is immutable then this will produce a compile error. It is impossible to call v.push if v is immutable, because push expects &mut self.
This can be restrictive, so Rust provides tools that let you fine-tune this behaviour to encode exactly the safety guarantees that you need. If you want to get something close to the C# behaviour, you can use a RefCell wrapper (or one of the several other wrapper types). A RefCell<Vec<T>> doesn't itself have to be mutable for functions to be able to unwrap it and modify the Vec inside.