Structs can have mutable fields, but combining "value type" (copies on assignment) with "mutable" produces some of the most confusing bugs in the language, because a copy silently diverges from the original the moment either one is mutated:
struct Counter2
{
public int Value;
public void Increment() { Value++; }
}
// Mutating a struct returned from a property or indexer often doesn't do what it looks like
List<Counter2> counters = new List<Counter2> { new Counter2() };
counters[0].Increment(); // Compile error in modern C# — indexers return a temporary copy,
// and the compiler now refuses to let you mutate a temporary you can't observe
Counter2 c = counters[0];
c.Increment(); // mutates ONLY the local copy `c` — counters[0] is untouched
Console.WriteLine(counters[0].Value); // still 0
The counters[0].Increment() line is a real historical foot-gun that the compiler now catches for indexers on List<T> specifically (list indexers return a copy, not a reference, so mutating it would silently do nothing) — but the underlying issue, a struct copy diverging from its source the moment either is mutated, is exactly why the broadly accepted guidance is: make structs immutable. Give every field a value only through the constructor, expose no mutating methods, and use with-style "return a new struct with this field changed" instead — records structs get this for free via with.