This question has kind of been asked before here Why are structs and classes separate concepts in C#? but I'm interested in a specific aspect of this that wasn't really answered.
I'm trying to understand why the language designers for C# and Swift (and possibly others) opted for a language design that promotes implicitly definining a type as a value-type (struct) vs. a reference type (class).
If a language (like C++) only supports defining compound types in one manner, say with the class keyword, but allows instances of this type to be bound to a variable either by value or by reference, this lets the type's consumer decide whether to use value semantics vs. reference semantics.
An oft cited characteristic of good language design is its ability to succinctly convey the most meaning possible in a local manner. Yet, these modern languages seem to conceal the value vs. ref semantics when using them. Exploring this assertion with two examples:
1) Calling a method:
Consider a method that operates on an array in C#. This array is passed as an argument to the method. Since arrays in C# are reference types, the method could either choose to mutate the array directly or to build a new array while operating on copies of the original array's values. Imagine that in both cases, the method would return the reference to that array to the caller in order to support method chaining. Given this scenario, if the method name doesn't hint at mutation or not, the adopted strategy is not readily knowable from the API (i.e. without testing or reading documentation).
Of course in the above example, the method's implementer generally shouldn't mutate the original array without good reason. But the point is that he can and the caller can't know about it just by writing out the method call.
Conversely, if the language design assumes value semantics everywhere (even though it may pass references for performance reasons under the hood, this is an implementation issue), the above example would not be possible. If the method implementor would want to mutate the original array, it would require that the parameter be explicitly marked as a reference to the array, communicating a strong hint to the caller that the array will be mutated.
2) Local variables:
Consider a basic person class with typical members such as name, birth date, etc.
var p1 = new Person();
p1.name = "Tom";
var p2 = p1; //Note I would never do this but I'm trying to keep the example trivial for brevity.
p2.name = "Peter";
Since Person
is a class, only one Person is created and is referenced by both p1
and p2
. Therefore, in a more complex example, this indirect mutation of p1 may be unintended. However, if Person
was declared as a struct, a copy would be created and p1
would have retained the name of 'Tom'. Again, this requires the type's user to know whether the type was defined as a struct or class - that information is not readily available in the local context.
If the language only supports value semantics by default with explicit references being used, this kind of mistake wouldn't be possible. Things would be even clearer if the language required a different operator for accessing members for refs vs. vals (e.g. ->
operator / .
operator).
var p1 = new Person();
p1.name = "Tom";
var p2 = &p1;
p2->name = "Peter"; //It is pretty clear that we are using a reference here because the language forces a different operator.
So again, given the premise of good language == clear/unambiguous language, why do modern language designers feel that it is better to put reference vs. value type semantics in the hands of the type designer instead of the type consumer?