Runtime: On the immutability of System.String

Created on 21 Dec 2016  路  14Comments  路  Source: dotnet/runtime

This is not exactly a bug report or a feature proposal, but I think that this is the most appropriate place to discuss the issue.

I was wondering about the reason behind making the primitive System.String type immutable. By searching in the Internet, I found this and this StackOverfow posts in which the same question is asked. But, unfortunately, all answers (including the highly-upvoted and accepted ones) given there actually answer a different question that is "What are the benefits of immutable strings?" rather than the posed question "Why is System.String immutable?" This motivated me to look in the CLI specification, hoping to find an answer. Much to my surprise, the spec does not even say that System.String must be immutable. It just says in I.8.2.2 that System.String represents a Unicode string. It also doesn't specify that a string has to be preceded with a size field and has to be zero-terminated. I also looked in the C# spec which also only says that System.String represents a Unicode string.

This leads to the following questions:

  • I think that the reason behind making System.String immutable is for the sake of semantic consistency with other primitive types. That is, making characters to a string like bits to an integer. If we want to change an instance of Int32, we have to assign to it a new value. Similarly, if we want to change an instance of String, we have to assign to it a new value (which is a reference to another string). This makes the set of primitive types elegant and simple. Am I right about this? (maybe someone from Microsoft can look in the original design document of String?).

  • Is String guaranteed to be immutable across CLI-conformant implementations or is it just an implementation detail? The same question for the size field and the terminating zero. Also the same questions for C#-conformant implementations.

question

All 14 comments

ut, unfortunately, all answers (including the highly-upvoted and accepted ones) given there actually answer a different question that is "What are the benefits of immutable strings?" rather than the posed question "Why is System.String immutable?"

I don't understand the difference. Strings are immutable because immutability has many benefits.

Why not making System.String mutable and introduce another type ImmutableString rather than making System.String immutable and providing System.Text.StringBuilder which represents a mutable string (both length and contents). The benefits of immutable strings may quality it to be a standard type but not a CLI primitive type i.e. System.String.

Why not making System.String mutable and introduce another type ImmutableString rather than making System.String immutable and providing System.Text.StringBuilder which represents a mutable string (both length and contents).

Because it's likely more common to need an immutable string rather than a mutable one. Would you want to pass a mutable string to File.Open? Or Console.WriteLine? Or to any other of the zillions of APIs that require strings as input? I doubt that.

The benefits of immutable strings may quality it to be a standard type but not a CLI primitive type.

It seems to me that it is exactly the opposite. An immutable string is simpler and thus more likely to be treated as a primitive type. Much like arrays, they have special runtime support and List<T> does not.

string is a reference type array where its elements are readonly.

As the elements are readonly you can return the same string reference for things (e.g. error messages, enum names, field names for serialization etc). If it was read/write then you need to copy the string before passing it to things as they may change it; so that would be extra overhead and allocations.

Is String guaranteed to be immutable across CLI-conformant implementations

Strictly speaking you can change its contents with a fixed statement in an unsafe block; but unless you are only changing your own string immediately after creation and before use; you are entering a world of pain. And members of the clr team would argue if you do change it with fixed you should change your code not to do that https://github.com/dotnet/coreclr/issues/7083#issuecomment-247385274

The string object block header contains flags, such as isAscii which gets set when you first use the string in some operations and change the algorithms used by the runtime. So if you had an ascii string and then changed it to have some non-ascii characters in it things like string equality would stop working as it would be assuming 7-bit data.

The same question for the size field and the terminating zero.

The size of a string however is immutable (strictly speaking you could make it smaller and waste memory). As it is an array you can't resize it/change its length without making a new one (as you need to allocate new memory; copy to it, and then the pointer/reference has changed anyway); so to change a string size you would be creating a new string anyway - so nothing would really change.

Because it's likely more common to need an immutable string rather than a mutable one.

Indeed. But this can justify a standard immutable string type, not a primitive one. Arguably, some integral primitive types are much less common than others, yet they are primitive.

An immutable string is simpler and thus more likely to be treated as a primitive type.

Yes. This is my proposed answer to the question.

As the elements are readonly you can return the same string reference for things (e.g. error messages, enum names, field names for serialization etc). If it was read/write then you need to copy the string before passing it to things as they may change it; so that would be extra overhead and allocations.

You are mentioning some of the benefits of immutable strings which do not answer the question as I explained earlier.

But this can justify a standard immutable string type, not a primitive one. Arguably, some integral primitive types are much less common than others, yet there are primitive.

What do you mean by primitive? I don't really understand your logic. There's no way for those less commonly used integral types not to be primitives. And as far as Type.IsPrimitive is concerned strings aren't primitives.

Yes. This is my proposed answer to the question.

I don't think this is anymore an answer than the others. It appears to me that you're asking a question, expect a very specific answer and reject any answers that don't match your expectations thus making the question pointless.

In v1.x of the .NET Framework this thing that you call a "primitive" wasn't actually immutable like today string is. StringBuilder used it internally as a mutable string.

You are mentioning some of the benefits of immutable strings which do not answer the question as I explained earlier.

A string is a pointer to a block of memory which likely has other objects allocated on either side. You cannot resize it in place and keep the same pointer because there is no room at that location. To get round this you'd need to use an extra level of indirection and a more complicated data structure - which would end up being a bit like StringBuilder.

Therefore use string for immutable strings and StringBuilder for mutable strings?

What do you mean by primitive?

ECMA-335 I.8.2.2

It appears to me that you're asking a question, expect a very specific answer and reject any answers that don't match your expectations thus making the question pointless.

I'm not rejecting any of the reasons you have given (sorry, if this is the impression I've given). You could be right. I'm just expressing my opinion that most of these reasons do not justify the decision.

ECMA-335 I.8.2.2

Well, built-in and primitive are not exactly the same thing. And while ECMA spec doesn't say what is a primitive type all the cases where it uses this term involve numeric types. At the end of the day this classification is rather pointless. Vector<T> is neither built-in nor primitive yet it receives special runtime support.

I'm just expressing my opinion that most of these reasons do not justify the decision.

I don't know. The fact that immutability makes strings thread safe is more than enough to justify this decision for me. And anyway, such an important decision is unlikely to be justified by a single reason.

I'm just expressing my opinion that most of these reasons do not justify the decision.

As outlined above, you can't increase a block of memory's size without moving it; you can decrease it with wastage. So with string being a simple block of memory; there aren't many length retaining operations you'd likely do on it. (Upper and lower casing would be two examples).

It could be made mutable as a set of chained blocks/linked list; but that's a much more complicated data structure and would involve extra levels of indirection and be slower. Also you couldn't use a fast reference equality as the first test of string equals.

So on the flip-side: What advantages would having the base/simple/primitive string be mutable bring that would outweigh the advantages that having it immutable brings?

I agree. A mutable string is a complicated type that is not suitable as a primitive/built-in type. This is aligned with my proposed answer as well. I think it boils down to the semantic consistency of built-in types I was talking about. I believe that this reason is the only genuine reason that should be mentioned to justify the immutability of System.String, rather than enumerating the benefits of immutable strings in general.

I hope people from Microsoft will comment on all my questions.

I believe that this reason is the only genuine reason that should be mentioned to justify the immutability of System.String, rather than enumerating the benefits of immutable strings in general.

You're insisting that there's such a thing as a genuine reason when in fact it's more likely that there are many reasons. And you're doing that despite having told you that string wasn't always immutable.聽And聽Java's string having a very different implementation but also being immutable.

A mutable string is a complicated type

Array's are mutable but I wouldn't call them complicated. The .NET 1.x string was actually more complicated than an array.

semantic consistency of built-in types

What?!

With immutability, it will be much easier to use string in generic collection types that need to check the equality among the elements (for example, HashSet(T) and Dictionary(TKey, TValue)) because immutability ensures that the values before and after the element is added are exactly same.

String is the immutable string type, and StringBuilder is the mutable string type. The design of these existing entrenched types is not going to change because it would be a major breaking change.

http://github.com/dotnet/corefxlab is where we are experimenting with new different string types: There is prototype of UTF8 strings, or faster parsing and formatting APIs. Take a look at corefxlab if you are interested in alternative experimental string designs.

Was this page helpful?
0 / 5 - 0 ratings