Proposal: 'readonly' for Locals and Parameters #115

Open
stephentoub opened this Issue Jan 28, 2015 · 89 comments
@stephentoub
Member

(Note: this proposal was briefly discussed in #98, the C# design notes for Jan 21, 2015. It has not been updated based on the discussion that's already occurred on that thread.)

Background

Today, the ‘readonly’ keyword can be applied to fields; this has the effect of ensuring that a field can only be written to during construction (static construction in the case of a static field, or instance construction in the case of an instance field), which helps developers avoid mistakes by accidentally overwriting state which should not be modified. Optimizations are also possible in a compiler based on the knowledge that the field is immutable after construction.

Here’s an example of a class defined with a readonly field. The ‘m_birthDay’ field is explicitly declared by the developer to be readonly, which means any attempt to set it after construction will cause a compile-time error. The ‘FirstName’ and ‘LastName’ properties, which are defined with getter-only auto-props (introduced in C# 6), also result in readonly fields generated implicitly by the compiler, since there’s no need for a field to be writable if there’s no way for code to set it.

public class Person
{
    readonly DateTimeOffset m_birthDay; // readonly field, assigned in constructor

    public Person(string firstName, string lastName, DateTimeOffset birthDay)
    {
        FirstName = firstName;
        LastName = lastName;
        m_birthDay = birthDay;
    }

    public string FirstName { get; }  // getter-only auto-prop, backed by readonly field
    public string LastName { get; }

    public TimeSpan Age => DateTime.UtcNow – BirthDay;
    public string FullName => "\{FirstName} \{LastName}";

    public DateTime BirthDay
    {
        get => m_birthDay;
        set => m_birthDay = value; // Error: can’t assign to readonly field outside of ctor
    }
}

Problem

Fields aren’t the only places developers want to ensure that values aren’t mutated. In particular, it’s common to create a local variable to store temporary state, and accidentally updating that temporary state can result in erroneous calculations and other such bugs, especially when such "locals" are captured in lambdas.

Solution: readonly locals

Locals should be annotatable as ‘readonly’ as well, with the compiler ensuring that they’re only set at the time of declaration (certain locals in C# are already implicitly readonly, such as the iteration variable in a ‘foreach’ loop or the used variable in a ‘using’ block, but currently a developer has no ability to mark other locals as ‘readonly’).

readonly long maxBytesToDelete = (stream.LimitBytes - stream.MaxBytes) / 10;
...
maxBytesToDelete = 0; // Error: can’t assign to readonly locals outside of declaration

This is particularly valuable when working with lambdas and closures. When an anonymous method or lambda accesses local state from the enclosing scope, that state is captured into a closure by the compiler, which is represented by a “display class.” Each “local” that’s captured is a field in this class, yet because the compiler is generating this field on your behalf, you have no opportunity to annotate it as ‘readonly’ in order to prevent the lambda from erroneously writing to the “local” (in quotes because it’s really not a local, at least not in the resulting MSIL). With 'readonly' locals, the compiler can prevent the lambda from writing to local, which is particularly valuable in scenarios involving multithreading where an erroneous write could result in a dangerous but rare and hard-to-find concurrency bug.

readonly long index = …;
Parallel.ForEach(data, item => {
    T element = item[index];
    index = 0; // Error: can’t assign to readonly locals outside of declaration
});

Solution: readonly parameters

As a special form of local, parameters should also be annotatable as 'readonly'. This would have no effect on what the caller of the method is able to pass to the parameter (just as there’s no constraint on what values may be stored into a ‘readonly’ field), but as with any ‘readonly’ local, the compiler would prohibit code from writing to the parameter after declaration, which means the body of the method is prohibited from writing to the parameter.

public void Update(readonly int index = 0) // Default values are ok though not required
{
    …
    index = 0; // Error: can’t assign to readonly parameters
    …    
}

This support for ‘readonly’ parameters would be particularly helpful for a very specific usage of parameters: passing structs by reference. When a reference type is passed to a method, the state that’s actually passed into the method is a copy of the reference to the object (the reference is passed by value), effectively a pointer-sized piece of data. In contrast, when a struct is passed into a method, a copy of the struct is passed in (the struct’s state is passed by value). If the struct is small, such as is an Int32, this is perfectly fine and there’s little better that could be done by the developer. However, when the struct is large (for example, the System.Windows.Media.Media3D.Matrix3D struct contains 16 doubles, making it 128 bytes in size), it can become quite expensive to continually pass around copies of the data. In these cases, developers often resort to passing structs by ref, passing a pointer to the original data rather than making a copy. This avoids the performance overhead, but it now inadvertenly enables the called method to update the original struct’s value:

public void Use(ref Matrix3D matrix)
{
    …
    matrix.M31 = 0; // Oops! We wanted performance, not more bugs.
    …
}

By enabling marking parameters ‘readonly’, the struct could be passed by reference but still prevent the callee from mutating the original value:

public void Use(readonly ref Matrix3D matrix)
{
    …
    matrix.M31 = 0; // Error: can’t assign to readonly parameters
    …
}

This would also enable readonly struct fields to be passed by ref (to a readonly ref parameter), whereas in C# today passing a readonly struct field by ref is never allowed.
As with ‘readonly’ on fields, ‘readonly’ as it applies to locals and parameters would be shallow rather than deep, meaning that it would prohibit writing to the readonly field, but it wouldn't prevent mutation of any state contained in objects referenced from the ‘readonly’ value.

Additional Considerations

In many situations, in particular when "var" is used to declare a local with type inference, the developer often would like 'readonly' behavior for that local. This is possible if 'readonly' is allowed on locals, e.g.

readonly int foo = ...;
readonly var bar = ...;

but it makes the desired immutability harder to express than mutability. To combat that, as shorthand for 'readonly var', we could also introduce a new 'let' or 'val' syntax, with the same meaning as 'readonly var':

val foo = ...;
val bar = ...;
@stephentoub stephentoub changed the title from Proposal: 'readonly' for locals and parameters to Proposal: 'readonly' for Locals and Parameters Jan 28, 2015
@ryancerium

I'd like to allow the readonly modifier on classes to allow immutability by default, and methods also to correlate with C++ const functions.

readonly class Point
{
  public int x; // Implicit readonly
  public int y; // Implicit readonly
  Point(int x, int y)
  {
    this.x = x;
    this.y = y;
  }
}

Point origin = new Point(0, 0);
origin.x = 1; // Error:  can't reassign a readonly class member
@theoy theoy added the Language-C# label Jan 28, 2015
@drewnoakes
Contributor

readonly always felt a little bit of a short sell for instances of types with mutable internals. The readonly-ness is much more valuable when transitive, as happens with (well behaved) C++ const values.

class C
{
  private int _i;
  public int Read() readonly { return _i; } // method has 'readonly' modifier
  public void Write() { _i++; }
}

readonly C c = new C();
c.Read();
c.Write(); // compile error: method is not readonly

The readonly modifier restricts the method's ability to modify its own members. Any members are also treated as readonly too, and only their readonly methods may be invoked. The entire graph of references from this point outwards becomes immutable.


As functional programming paradigms become more commonplace, function purity becomes an comforting property to have, and an appealing property to enforce. Today we have [Pure], but there is no guarantee made by the compiler that this attribute is used factually. A strict purity constraint would extend the above readonly guarantee to the method's arguments any any static values/references. Such a function could have no observable side effects and would be truly idempotent. A pure method modifier could exist that extends this immutability not only to this but to all parameters.

void PureFunction(C c) pure // method has the 'pure' modifier
{
  c.Read();
  c.Write(); // compile error: a pure function's parameters are implicitly readonly
}

Framework code could optimise for or even require pure functions. The compiler could automatically hoist loop invariant code.

@Porges
Porges commented Jan 28, 2015

Re parameters, I don't like the way that readonly on reference parameters pollutes the (in-source) signature with information that is irrelevant to the caller.

For structs, it would be nice to have those semantics - I've often wished for in as an addition to ref and out, as readonly ref is not very symmetrical 😁 It wasn't mentioned in the proposal, but the call site should not require annotation with in/ref.

@ryancerium

@drewnoakes I completely agree, but backwards compatibility means we're screwed.

readonly var list = new ArrayList<int>();
readonly var size = list.size(); // Error, calling non-readonly method on readonly reference.
@fubar-coder

@stephentoub The following example is ambigous:

public void Use(readonly ref Matrix3D matrix)
{
    …
    matrix.M31 = 0; // Error: can’t assign to readonly parameters
    …
}

This would also enable readonly struct fields to be passed by ref (to a readonly ref parameter), whereas in C# today passing a readonly struct field by ref is never allowed.
As with ‘readonly’ on fields, ‘readonly’ as it applies to locals and parameters would be shallow rather than deep, meaning that it would prohibit writing to the readonly field, but it wouldn't prevent mutation of any state contained in objects referenced from the ‘readonly’ value.

The first thing that I thought, when I saw readonly ref was, that ref was used to avoid a copy and readonly was given, to avoid that the Use method changes it. Then I realized, that readonly was used to declare the underling structure as const.

My proposal to resolve this ambiguity is:

public void Use(readonly ref const Matrix3D matrix)
{
    …
    matrix = new Matrix3D(); // Error: can't assign to readonly reference
    matrix.M31 = 0; // Error: can’t change immutable value
    …
}

The variable is readonly and the Matrix3D value is immutable. It should also be possible to use the following syntax:

public void Use(ref const Matrix3D matrix)
{
    …
    matrix = new Matrix3D(); // OK
    matrix.M31 = 0; // Error: can’t change immutable value
    …
}

Calling methods on const objects

Calling methods on const objects should only be allowed when those methods don't make changes to the underlying object.

Examples

The following shouldn't work:

class TestObject {
    public int i;
    public void SetValueOfI(int v) {
        i = v;
    }
}

public void Use(ref readonly TestObject o)
{
    …
    o.SetValueOfI(0); // Error: can’t call non-const method on const object
    …
}

Instead we have to be able to make methods and properties (setter/getter) readonly too:

class TestObject {
    public int i;
    public int AddWithI(int v) const {
        return i + v;
    }
}

public void Test()
{
    var o = new TestObject { i = 1 };
    Use(ref o);
}

public void Use(ref readonly TestObject o)
{
    …
    Assert.AreEqual(124, o.AddWithI(123)); // OK: Can call AddWithI, because it doesn't change the underlying object
    …
}

I personally prefer const over readonly, because IMHO readonly means, that the variable can only be read, but this doesn't necessarily apply to the object it points to (similar to C's T const * const) and - to me - immutable variables and immutable values are different things.

@drewnoakes
Contributor

@ryancerium, I suppose this feature would require new metadata in the type system, therefore a new CLR and at the same time a new BCL. ArrayList<T> and friends would be annotated accordingly.

@drewnoakes
Contributor

@ryancerium can you further explore why the readonly modifier should apply to the type rather than the instance? The latter seems more flexible. You could still have:

readonly Point origin = new Point(0, 0);
origin.x = 1; // compile error: can't modify a readonly instance's fields
@ryancerium

@drewnoakes readonly isn't transitive for fields, or for arrays for that matter. It was a mistake committed long ago to not have transitive readonly like C++ const, but it's where we are. Don't get me wrong, I much prefer it, but I think that ship has sailed.

I want to co-opt readonly to make it easier to make immutable data types. Right now I have to put readonly on all the data members, and that's annoying as hell :-)

Here's a different Point class that has mutable x and y values.

class Point
{
  public int x, y;
}

class C
{
  readonly Point origin = new Point(0, 0);
  public void M(readonly Point p)
  {
    origin.x = 100; // Totally fine in C# as-is today.

    p.x = 100; // Error per your suggestion, seems weird when I can modify origin

    readonly Point d = new Point(p.x - origin.x, p.y - origin.y);
    d.y = 100; // Error per your suggestion, seems weird when I can modify origin
    // d = new Point(-1, -1); // Illegal today, reassigning the reference
  }
}
@louthy
louthy commented Jan 29, 2015

Agreed on the desire for readonly classes, it would be very much appreciated.

I think the syntax for local readonly values should take a leaf out of the F# book and use let. The reason for that is I think val looks too close to var and would make it slightly less easy to scan code quickly. And I think trying to avoid a readonly prefix would be wise to reduce the amount of effort required to 'do the right thing' in choosing the immutable option when coding. As somebody who likes to make as much of my code as pure and as immutable as possible, I would much prefer not to have to litter my code with readonly.

    val foo = 123;
    var bar = 123;

Compared to:

    let foo = 123;
    var bar = 123;

I think the second version is much more 'scannable'.

@stephentoub
Member

The devil is always in the details on these kinds of proposals around immutability, const, etc. (i.e. when you start working through the intricacies, there are a variety of interesting gotchas and corner cases that need to be handled and increase complexity or at least acknowledged that there are holes.) I have several more proposals to post in this area, I just didn't get a chance to do so yesterday; I'll ensure they're linked to from here when I put them up, hopefully later today.

@lawrencejohnston

And I think trying to avoid a readonly prefix would be wise to reduce the amount of effort required to 'do the right' thing in choosing the immutable option when coding.

As somebody who also programs in Java which has final for readonly parameters and locals as well as fields, I always use it (when applicable) for fields but I often omit it from parameters and locals. I think let would be a good choice, and I would definitely use it.

@ryancerium

The devil is always in the details on these kinds of proposals around immutability

@stephentoub Ain't that the truth. Is there a (very large) matrix anywhere that combines the list of types and methods and object locations (parameter/field/local/static)? Or do the language designers just keep them in the back of their head at all times?

struct S{}
struct<T> where T : struct GVS{}
struct<T> where T : class GRS{}
class C{}
class<T> where T : struct GVC{}
class<T> where T: class GRC{}
dynamic D{}

static class
{
  int a;
  S

Ah forget it, I'm getting tired already and realizing that the list is longer than War and Peace.

@MgSam
MgSam commented Feb 1, 2015

I like this proposal, especially around using let to infer a readonly local. This makes the need to actually type the word readonly in the context of a method very rare, which is a good thing.

@gafter gafter added the 1 - Planning label Feb 2, 2015
@MadsTorgersen MadsTorgersen was assigned by gafter Feb 2, 2015
@Richiban
Richiban commented Feb 3, 2015

I fully agree that let is the preferred syntax here, at least for local readonly variables / values. I believe that programmers should be encouraged into programming with immutability in mind, and having a nice, short keyword will facilitate this :).

Of course, let would work with type inference; you could simply swap var for let in most programs and you'd be fine. If you want to explicitly type the variable then you can much the same as in linq query expressions. So you should be able to write
let john = "John" or let string john = "John"
the same way in Linq you can write
from name in names and from string name in names.

This also has the added benefit of matching both F# and Swift for syntax.

@axel-habermaier
Contributor

@Richiban: let john : string = "John" would be the correct F# syntax. let string john = "John" would declare a method called string, taking a parameter john of generic type, returning the constant "John".

However, I also agree that let should be preferred over val. The syntactic difference between val and var is just too small and easy to miss.

@Richiban
Richiban commented Feb 3, 2015

@axel-habermaier Thanks, I was aware of the correct F# syntax. Perhaps I wasn't clear - let string john = "John" is what I want the C# syntax to be.

@gafter
Member
gafter commented Feb 3, 2015

The "let" keyword would not read well for parameters.

@BrannonKing

Is it possible for the compiler to determine that a struct is not modified in a method? If so, the struct could then be passed by ref to that method as a compile-time optimization. Obviously a readonly keyword would be a good clue that a struct should be passed by reference into that method. I don't see that you need both the readonly and ref keywords together.

@louthy
louthy commented Feb 5, 2015

@gafter

The "let" keyword would not read well for parameters.

How do you mean?

@HaloFour
HaloFour commented Feb 6, 2015

My two cents?

Support readonly as a modifier keyword for both parameters and local variables. Parameters with this modifier cannot be modified by the method nor can they be passed to methods as ref or out parameters. Local variables with this modifier must have an initializer and also cannot be modified by the method nor passed to other methods as ref or out parameters.

The behavior of this modifier will be exactly like the behavior of the readonly modifier when applied to fields. If the value of the variable is a reference type then any state carried by the instance may be modified through whatever members are exposed by that type, including public fields. If the value of the variable is a struct or value type then members which may mutate the value cannot override the value stored in the variable.

public class Box<T> {
    public T Value;
}

public struct Incrementor {
    private int value;

    public Incrementor(int initialValue) {
        this.value = initialValue;
    }

    public int Increment() {
        this.value += 1;
        return this.value;
    }
}

public void Foo(Incrementor i1, readonly Incrementor i2, readonly Box<string> boxed) {
    Debug.Assert(i1.Increment() == 1);
    Debug.Assert(i1.Increment() == 2);
    Debug.Assert(i1.Increment() == 3);

    Debug.Assert(i2.Increment() == 1);
    Debug.Assert(i2.Increment() == 1);
    Debug.Assert(i2.Increment() == 1);

    readonly Incrementor i3 = new Incrementor(0);

    Debug.Assert(i3.Increment() == 1);
    Debug.Assert(i3.Increment() == 1);
    Debug.Assert(i3.Increment() == 1);

    i3 = new Incrementor(5); // compiler error, i3 is readonly

    boxed.Value = "foo";
    Debug.Assert(boxed.Value == "foo");
    boxed.Value = "bar";
    Debug.Assert(boxed.Value == "bar");
    boxed = new Box<string>(); // compiler error, boxed is readonly

    readonly int number = int.Parse("4");
    int.TryParse("5", out number); // compiler error, number is readonly
}

Foo(new Incrementor(0), new Incrementor(0), new Box<string>() { Value = "baz" });

Additionally, as a counterpart to var also support the use of the existing let keyword as a method to declare implicitly-typed readonly variables. No type is permitted to be specified. This is syntactically similar to how let is already used in LINQ queries.

var i = 2;
let j = 3;

i = 4;
j = 5; // compiler error, j is readonly

Apart from the existing similar usage of let in C# it is also used by Apple Swift to define readonly variables, although that language does optionally permit explicitly specifying the type.

@Richiban
Richiban commented Feb 6, 2015

I could get down with this syntax - if it's technically the addition of the readonly keyword but locals have the keyword let as shorthand for readonly var then you get the best of both worlds as far as I'm concerned.

The only thing I'm concerned about is the fact that requiring an extra keyword on method parameters will mean that nobody bothers. They should be writing:

public int Add(readonly int x, readonly int y) {...} but who's going to do that? Readonly parameters should be the default, but unfortunately that would be a breaking change and therefore has a close-to-zero chance of happening :(.

@HaloFour
HaloFour commented Feb 6, 2015

@Richiban

Readonly parameters should be the default, but unfortunately that would be a breaking change and therefore has a close-to-zero chance of happening :(.

Given that all parameters are by-val by default does that really matter? Let them screw with their version of x and y all they want, it won't affect the caller.

@Richiban
Richiban commented Feb 6, 2015

@HaloFour No, you're right, the caller will be fine. I'm thinking more of
reasoning the contents of a method. When variables are immutable by default
it's harder to write spaghetti code. I've often seen code like this in
existing projects:

public void DoSomething(Customer customer)
{
     ...
     if (someCondition)
    {
        customer = new Customer();
    }
    ...
}

This method is very hard to reason about (at least in the second half)
because you don't know whether customer is still the object that was
passed into the method or not. That's what I mean by the fact that I would
prefer all parameters to be readonly. Reusing the customer parameter
name as if it were a local variable is sheer laziness on the part of the
programmer in this case, but if the language allows it and makes it easy
then unfotunately that's what people will do.

@mikedn
mikedn commented Feb 6, 2015

I've seen a lot of poorly written code but I've yet to see parameters modified in the middle of a method. Besides, there are valid cases to modify parameters:

public void DoSomething(Customer customer)
{
    customer = customer ?? new Customer();
    ...

You really don't want to introduce a new variable in this case because that means that someone my end up accidentally using the customer parameter instead of using the new variable.

@MgSam
MgSam commented Feb 6, 2015

@Richiban If the method author wants to mess with the parameters and they were immutable by default, they would likely just add a mutate keyword (or whatever this hypothetical keyword would be) to allow themselves to do so. I don't think making it the default would accomplish anything.

I imagine in practice very few methods will have parameters actually designated as readonly. I think it only adds value on long methods and/or those with complex logic. And even then, its a weak guarantee as you can still write to properties or indexers of a readonly parameter.

Given they have such limited use, I'm not even sure readonly parameters are worth implementing at all... (Though I'm still a big fan of readonly locals).

@Richiban
Richiban commented Feb 6, 2015

@MgSam Perhaps you're right. I'm very much in the F# way of thinking, and in F# you can't declare mutable parameters at all. To me it doesn't really make any sense why you would want them any way.

However, while I'm all for the ability to add the readonly keyword to method parameters (because "why not?") I think that in practice it will be rarely used since it's a) extra typing, b) extra noise to read c) of dubious value and d) will probably just lead to code style arguments amongst developers. Look at the final keyword in Java. People should really use it (for reasons I think we all agree with) but the extra keyword makes a surprisingly large negative impact on readability and thus very few people use it. That's why I think it's so important that we have the keyword let in C# for locals at least, because it's nice and short (val would be my second choice).

@HaloFour
HaloFour commented Feb 6, 2015

Of course the major reason to use final is the language requiring
parameters/variables be final in order to reference them in an anonymous
class. No such concerns in C#. It would exclusively be for developers to
ensure that they don't accidentally overwrite the existing value.
On Feb 6, 2015 10:39 AM, "Richard Gibson" notifications@github.com wrote:

@MgSam https://github.com/MgSam Perhaps you're right. I'm very much in
the F# way of thinking, and in F# you can't declare mutable parameters at
all. To me it doesn't really make any sense why you would want them any way.

However, while I'm all for the ability to add the readonly keyword to
method parameters (because "why not?") I think that in practice it will be
rarely used since it's a) extra typing, b) extra noise to read c) of
dubious value and d) will probably just lead to code style arguments
amongst developers. Look at the final keyword in Java. People should
really use it (for reasons I think we all agree with) but the extra keyword
makes a surprisingly large negative impact on readability and thus very few
people use it. That's why I think it's so important that we have the
keyword let in C# for locals at least, because it's nice and short (val
would be my second choice).


Reply to this email directly or view it on GitHub
#115 (comment).

@MrJul
MrJul commented Feb 6, 2015

I agree with @Richiban: readonly will in practice be applicable to most parameters, but won't be used because it's cumbersome to annotate every single parameter with it. It should be a default, but it's too late for that. Maybe as a project-wide option? VB had language options for years, C# not so much (except for arithmetic overflow checks).

Look at the previously mentioned Java's final, or VB's ByVal keyword (which isn't inserted by default anymore since VS2010 SP1 because it was too verbose).

@MgSam

I think it only adds value on long methods and/or those with complex logic

If you're smart enough to understand that your method is too complex and might need a readonly parameter, you're also probably smart enough to refactor it :) Corollary: you won't find readonly in code where it's really needed.

That said, I'm all for readonly locals, with a simple short keyword (my vote goes to let).

@aluanhaddad

I am very much in favor of adding readonly local declarations. However, I would like to suggest that the keyword which is chosen to introduce a readonly, type inferred local be val and not let. The reason is that I feel it makes the method body read better and provides a nice symmetry with the existing var keyword.

To illustrate this, let us consider the let keyword (pun intended), which reads extremely well in the context of a query because it is used as a verb.
Consider:

evenSquares = 
    from n in numbers        // from is a preposition
    let nSquared = n * n     // let is a verb
    where n % 2 == 0         // where is a preposition
    select nSquared;          // select is a verb

This reads wonderfully and is very symmetric.

Now let us consider the use of the var keyword, which can be thought of as a noun or noun modifier.
Consider:

int ComputeSomething (IEnumerable<int> numbers) // C# 6.0
{ 
    var result = 0;          // var is a noun or noun modifier
    var offset = 25;         // this is not going to be modified 
    foreach (var n in numbers) // var is a noun or noun modifier
    {
        if (n % 2 == 0)
        {
            result += offset;
        }
        result += n;
    }
    return result; // return is a verb
}

This reads very nicely.
Consider:

int ComputeSomething (IEnumerable<int> numbers) // C# where let declares a readonly local.
{ 
    var result = 0;          // var is a noun or noun modifier
    let offset = 25;          // let is a verb
    foreach (let n in numbers) // let is what?
    {
        if (n % 2 == 0)
        {
            result += offset;
        }
        result += n;
    }
    return result; // return is a verb
}

This does not have the nice symmetry of the previous but the ability to express immutability is wonderful.

Now consider:

int ComputeSomething (IEnumerable<int> numbers) // C# where val declares a readonly local.
{ 
    var result = 0;           // var is a noun or noun modifier
    val offset = 25;          // val is a noun or noun modifier 
    foreach (val n in numbers) // val is a noun or noun modifier
    {
        if (n % 2 == 0)
        {
            result += offset;
        }
        result += n;
    }
    return result; // return is a verb
}

This the best by far.

@HaloFour

I don't think that the fact that let is a verb is that big of a deal. I think that you can read it more like binding the expression to a name in which case I think a verb is appropriate.

The two reasons that I don't care for val is that it is yet another keyword (C# 7.0 feels like a keyword explosion thus far) and that it looks so similar to var that when scanning through code you might not immediately notice the difference.

I like let because it's already a keyword in C# that behaves in a nearly identical way and because it's not without precedent. Apple Swift uses var and let in virtually the same manner as this proposal. EcmaScript 6.0 has both keywords as well although their use is different. F# has val and let where val is an unintialized declaration and let is a binding, regardless of mutability.

That said, I don't feel all that strongly either way,

@gafter
Member
gafter commented Feb 11, 2015

@HaloFour @louthy let works well for local declarations, but it doesn't work so well in other contexts such as parameters where the initializing expression isn't given at the declaration:

    public static void Main(let string[] args) // let does not read well here
    {
    }

@louthy I don't think there is a readability issue with var vs val. To the reader of the program they mean the same thing: declare a local variable. The only difference is to the person who modifies the code, as val will place restrictions on what you can do.

@HaloFour

I agree. let would only be a counterpoint to var for inferred
variables. This would also be true if yhry decide to use val.
Parameters would just reuse the existing keyword readonly, as could
explicitly declared variables.
On Feb 11, 2015 12:49 PM, "Neal Gafter" notifications@github.com wrote:

@HaloFour https://github.com/HaloFour @louthy
https://github.com/louthy let works well for local declarations, but it
doesn't work so well in other contexts such as parameters where the
initializing expression isn't given at the declaration:

public static void Main(let string[] args) // let does not read well here
{
}

@louthy https://github.com/louthy I don't think there is a readability
issue with var vs val. To the reader of the program they mean the same
thing: declare a local variable. The only difference is to the person who
modifies the code, as val will place restrictions on what you can do.


Reply to this email directly or view it on GitHub
#115 (comment).

@gafter
Member
gafter commented Feb 11, 2015

@HaloFour so you suggest three keywords for the same concept? :/

@Richiban

Not three - 'readonly' is the keyword proposed, and 'readonly var' can be
shortened to 'let'.

On Wed, Feb 11, 2015 at 6:11 PM, Neal Gafter notifications@github.com
wrote:

@HaloFour https://github.com/HaloFour so you suggest three keywords for
the same concept? :/


Reply to this email directly or view it on GitHub
#115 (comment).

  • Richard Gibson -
@HaloFour

@gafter Not at all. I would reuse the existing keywords readonly and let. The readonly keyword could be applied to parameters or to formally declared variables:

public void Foo(readonly int x) {
    x = 5; // compiler error
    readonly int y = 10; // initializer required otherwise compiler error
    y = 15; // compiler error
}

The let contextual keyword would be used for inferred variables:

readonly int x = 10;
// equivalent to
let y = 10;  // again, initializer required otherwise compiler error
y = 15; // compiler error

I vote for let because C# already has the keyword with nearly identical syntax and behavior within LINQ projections, because it looks nothing like var and would not be mistaken as such when scanning through code, and because other languages already offer it as a precedent. In my opinion if you consider let to be a binding to an expression rather than a variable declaration it doesn't really matter that it is a verb.

I'm neither here nor there regarding readonly var. It's the tiniest bit more verbose and seems unnecessary if let or val shorthand is provided.

So really, my "proposal" is about identical to the original proposal in this thread, other than putting in my vote for let (over val) and expanding a little on my preference that these variables behave very similarly to readonly fields.

@aluanhaddad

@HaloFour There is also a precedent for the val keyword in this context. Scala uses this syntax.
Most Scala IDEs use syntax colorization to help the reader distinguish between var and val.

In F# let is used to bind readonly names to values inside function bodies, and let mutable is used to bind non readonly names to values inside function bodies.

Anyway, I do read let as binding a value to a name, which I think reads poorly in a context where var also binds a value to a name.

@qrli
qrli commented Feb 22, 2015

For parameters, no matter it is readonly or val, it is nothing related to the signiture of the method. It is more about the method implementation. Think about interface and virtual methods.
So I think it may be better to be specified in method trait/contract.

@aluanhaddad

@qrli I think that readonly should be used in method signatures, property signatures, and in any place where the type must be stated explicitly. This is consistent with its existing use in field declarations. My suggestion is that val be chosen as opposed to let as the keyword for declaring readonly implicitly typed local variables. The reason is that it reads better and is more symmetric.

@qrli
qrli commented Feb 23, 2015

@aluanhaddad
If we are talking about "const" (as in C++) instead of "readonly", I will agree with you.
But for "readonly", it is nothing about the external visible behavior of a method, but only for the convenience of a specific implementation. Enforcing it on a type system provides only trouble.

@aluanhaddad

@qrli I think we agree.

@paulomorgado

(sorry, but I haven't read all the comments with the required and deserved attention)

I do agree that having unmodifiable locals is very useful. For closures and code correctness.

But I think, like with fields, there are to use cases that could be leveraged: readonly and const locals.

A readonly local would be pretty much how @stephentoub specified.

A const local would be like a field const. const locals would be replaced by its value like the field equivalent.

As to syntax, I wonder if only the modifier could be used:

readonly x = GetX();
const y = 1;
var z = GetZ();    

It's a bit of a stretch, I know.

As for function parameters, taking the @stephentoub's use case of large structs, if I mark a by referenece parameter as readonly, I can't pass it along by reference without the assurance that it won't be modified. So the readonlyness must be surfaced to the caller. The suggestion of an in modifier seems like a good idea here. It will also give space for the combined use of in and readonly if such a use case surfaces.

And, wouldn't it be great if I could mimick the behavior of foreach and using?

var a = 1;
{
    readonly a; // _a_ is readonly in this scope.

    // use _a_ but don't use it.
}
a = 2; // _a_ is writable again.
@whoisj
whoisj commented May 31, 2015

Let me start by espousing my love for this entire concept. The more we can place intent into the code, the less likely we are to create bugs, the easier it is to share/consume code, and the more verification we can push upstream to the compiler. All good things.

const'ness is always a delicate matter to design properly. The const keyword in C was great in its day, but the languages derived from C have evolved to the point where const isn't as flexible as it could be. C# has attempted to differentiate const'ness with a variety of options: const for compile time constants, readonly for immutable allocations, and the System.Collections.Generic.IReadOnly*<T> family. All good in their own right, but inconsistent as a whole.

This is a fantastic time to begin resolving the inconsistencies. For starters, in place of the IReadOnly*<T> famlily, why not enable readonly to be a modifier of the type definition of generics?
example:
List<readonly T> list = List<readonly T>();

So what are the complete set of use cases?

Immutable parameter values:

void Method(readonly object Obj)
{
    // ...
}

Immutable reference parameters:

void Method(readonly ref TimeSpan time)
{
    // ...
}

Immutable allocations:

private readonly object;
private readonly object[] m_objs;
private readonly List<object>

immutable arrays:

private readonly  char[] m_string = readonly { 'i', 'm', 'm', 'u', 't', 'i', 'b', 'l', 'e' };

Immutable generics:

public readonly List<readonly object> GetList { get { return ...; } }

Immutable local values:

for ( int i = 0; i < arr.Length; i++ )
{
    let x = arr[i]; // let means readonly
    ...
}

Finally, I think we should always allow references to be cast towards less mutability as a rule.

List<object> foo = new List<object>();
List<readonly object> bar = foo; // legal, less mutable
readonly List<object> baz = foo; // legal, less mutable
readonly List<object> biz = bar; // illegal, as T would be more mutable
@HaloFour

As mentioned on #3202, the one issue with readonly ref when it comes to value types is that I don't think that there is any mechanism that would allow for the compiler to prevent calls to mutating methods. To the compiler they look like any other method defined on the value type.

static void Foo(readonly ref Point pt) {
    pt.Offset(2, 2);
}

static void Main() {
    var pt = new Point(2, 2);
    Foo(ref pt);
    // oops, pt is now {4, 4}
}
@whoisj
whoisj commented May 31, 2015

Lastly, I'd bring up the idea of a writable keyword to allow partial undoing of readonly. There's plenty of times when I'd like to make a reference readonly but can't because current conventions are too strict.

Example, if for some reason (this is an actual use case for me currently) I need a fixed length dictionary where the keys of the dictionary are fixed (none should be removed or added) but users of the API are required to modify the values: today I need a custom type. With proper readonly and writable keyword usage I wouldn't.

public readonly Dictionary<readonly Control, writable Color> ControlColors { get { return ...; } }
@HaloFour

@whoisj I don't see how your property definition defines a "read-only" dictionary. That seems to imply that the C# compiler would know a great deal more about the intent of the generic type arguments than it does. At best, the C# compiler could try to enforce that any parameter or return value of TKey would be a readonly Control, preventing use of property setters, but not much else. And since no such concept is baked into the CLR it couldn't be enforced at all.

@whoisj
whoisj commented May 31, 2015

@HaloFour hmm... I suppose you have a point.

It would be great if there was a way to create an IReadOnlyDictionary<TKey, TValue> where the TValue was writable without the need to produce a new type completely. Thoughts?

@gafter gafter assigned gafter and unassigned MadsTorgersen Jul 25, 2015
@drewnoakes
Contributor

@ryancerium:

I want to co-opt readonly to make it easier to make immutable data types. Right now I have to put readonly on all the data members, and that's annoying as hell :-)

Have you changed your opinion on this after trying C# 6's truly readonly auto properties?

@jonathanmarston

I think readonly on locals and parameters is a good thing.

I also really like the idea of being able to express that a method will not (and cannot) modify member fields and properties by applying the readonly keyword to methods.

On top of that, add a new modifier, immutable, that can also be applied to fields, locals, and parameters, that is different from readonly in that readonly specifies that the value of the field cannot be reassigned, while immutable specifies that the value (or referenced object) cannot be mutated.

The immutable keyword would add compiler checks that don't allow writing to fields, calling setters, or calling getters or methods not marked readonly (auto-properties would have implicit readonly getters). With the immutable keyword, you could effectively enforce immutability at the point where a type is used with a simple modifier, much like const in C++, but instead of the confusing const * const syntax, you could instead combine the readonly and immutable keywords. So "public readonly immutable MyType MyField" would be completely valid.

Essentially, to enforce the rules of a readonly method, the method body of a readonly method would only have access to an "immutable this" reference rather a normal, mutable "this".

This also gives the added benefit of possible runtime optimizations since you could safely pass value types to parameters marked readonly immutable by reference.

For example:

public class Person
{
    public string FirstName { get; set; } // Implicit readonly get
    public string LastName { get; set; }
    public void ChangeMe() { }
    public readonly string GetFullName { return FirstName + " " + LastName; }
}

public void SetPerson(Person person)
{
    person.FirstName = "John";
    person.LastName = "Doe";
}

public void WritePerson(immutable Person person)
{
    Console.WriteLine(person.FirstName); OK - calling a readonly getter is fine
    Console.WriteLine(person.GetFullName()); // OK - GetFullName is marked readonly
    person.FirstName = "Bill"; // Error - Can't call a setter on an immutable reference
    person.ChangeMe(): // Error - ChangeMe is not marked readonly
    SetPerson(person); // Error - can't pass an immutable reference to a method that takes a mutable reference
}

It gets a little more complicated when you have an immutable reference to an object that has a property of a complex type. To ensure immutability, the compiler would have to assume that all values obtained from an immutable reference, whether returned by field access, getters, or method calls are also immutable. There may be a need to override this for some methods (think factory methods) so a way to reverse the immutability may be in order (mutable would be an obvious keyword choice):

public class PersonFactory
{
    public mutable Person CreatePerson() { ... } // The person returned from this method would always be mutable, even if the reference to the PersonFactory is an immutable reference
}

Also, the immutable keyword would be a valid modifier for return types:

public immutable Person GetPerson(int id) { ... }
@binki
binki commented Sep 8, 2015

It gets a little more complicated when you have an immutable reference to an object that has a property of a complex type. To ensure immutability, the compiler would have to assume that all values obtained from an immutable reference, whether returned by field access, getters, or method calls are also immutable. There may be a need to override this for some methods (think factory methods) so a way to reverse the immutability may be in order (mutable would be an obvious keyword choice):

Explicitly applying mutable seems to make things more complicated than they need to be. Wouldn’t it be less confusing if the absence of the immutable keyword always returns a mutable reference? Auto-implemented properties would, of course, implement a readonly immutable get, a normal get, and a normal set (or, if it’s get-only, it would only implement readonly immutable get and a normal get). The functions and getters/setters missing the readonly keyword would not be visible on an immutable object. An immutable object could return mutable references through methods by omitting immutable from their signature. This would be safe because the compiler would not, for example, allow you to cast an immutable to mutable by trying to return, say, this from a readonly method.

I think that making immutable work requires two basic simple steps.

  1. Allow methods and getters/setters to be marked readonly as described by @jonathanmarston. A method or getter/setter marked this way will have access to an immutable this reference. This is more complicated for getters, because in many cases you will want both readonly immutable get and get.
  2. Restrict dereferencing of immutable references to readonly methods/setters/getters and treat all fields as readonly immutable. This way, any fields holding object references will inherit the immutability. This is the key to imitating C++’s concept of marking a whole object graph as immutable. Also, this simple rule will prevent any method/setter/getter from being marked readonly if it tries to write to memory controlled by the object directly or attempt to access any non-readonly methods/getters/setters on any referenced object.

To make it so that existing code can take advantage of this, the following changes would be necessary/helpful:

  1. Method overload resolution (and, now, getter/setter resolution) would need to prioritize non-readonly methods/getters setters. This way, if an object only provides a readonly implementation (which is safe to call on both mutable and immutable references), it can be directly called. But because most getters will need both readonly immutable and non-readonly implementations (if the class intends to allow the gotten object to be mutated), it needs to be possible to call the non-readonly one instead of having the compiler complain about an ambiguous method signature match.
  2. Auto-properties now implement three operations: readonly immutable get, get, and set. This enable much existing code for simple POD classes to immediately take advantage of immutable.
public class Name
{
    public string Given { get; set; } // Implicit readonly immutable get, get, and set
    public string Sur { get; set; }
    // Just because, it should be permitted to write
    // a customer setter that is marked readonly—even
    // if we can’t think up a use case for it. It wouldn’t
    // be allowed to write to any fields, which is what matters.
    public string Unused
    {
        readonly get { return ""; }
        readonly set { }
    }
}

public class Person
{
    public Name Name { get; } // Implicit readonly immutable get and normal get
    public void ChangeMe() { }
    // No reason to mark the string immutable as it is, by
    // definition, immtuable.
    public readonly string GetFullName { return Name.Given + " " + Name.Sur; }
    // Compiler error: Attempt to cast immutable Name to Name.
    public readonly Name GetName() { return Name; }
    // Allowed:
    public readonly immutable Name GetName() { return Name; }

    double popularityFactor; // This field will appear readonly when “this” is immutable
    // Compiler error: Attempt to write to readonly field.
    public readonly void IncreasePopularity() { popularityFactor++; }
    // Allowed:
    public void IncreasePopularity() { popularityFactor++; }
}

public void SetPerson(Person person)
{
    // Possible because the non-readonly get wins overload resolution.
    var name = person.Name;
    name.Given = "John";
    name.Sur = "Doe";
    // There is only a readonly immutable version of GetName(),
    // so the following would fail:
    name.GetName().Given = "Jon";
}

public void WritePerson(immutable Person person)
{
    // We get the readonly get. Because auto-properties
    // default their readonly get to be `immutable`, we
    // are stuck with an immutable Name.
    var name = person.Name;
    Console.WriteLine(name.Given); // OK - calling a readonly getter is fine
    Console.WriteLine(name.GetFullName()); // OK - GetFullName is marked readonly
    name.Given = "Bill"; // Error - Can’t call set on an immutable type because it is not marked readonly
    name.Unused = "unlikely to ever be useful…"; // OK - calling a readonly setter is fine
    person.ChangeMe(); // Error - ChangeMe is not marked readonly
    SetPerson(person); // Error - can’t pass an immutable reference to a method that takes a mutable reference
}
@binki
binki commented Sep 8, 2015

Oh, and one more thing I forgot to address: when accessing a struct field via an immutable object reference, any object references within that struct must be kept immutable. Perhaps it should be illegal to copy readonly struct members because copying the readonly struct field to a function local would un-protect any object reference members in the struct. To keep code backwards compatibility (as there exists code which uses readonly struct members), this rule would only be enforced when accessing the struct field via an immutable reference. And, of course, it should be possible to assign an object reference member of a readonly struct to a local variable but only to an immutable one.

@HaloFour
HaloFour commented Sep 8, 2015

@binki This proposal only refers to the variables/parameters themselves, not to the references to which they point. If the parameter was of type readonly ref Point you'd still be able to mutate the struct via calling one of it's mutating methods like Offset.

You should probably look into #159, a proposal for immutable types.

@binki
binki commented Sep 8, 2015

Ah, sorry. I was just looking at @jonathanmarston’s post which seems to be a proposal to change the definition of readonly to support const * semantics.

You should probably look into #159, a proposal for immutable types.

Hmm. It is hard to tell where my thoughts belong. That proposal seems, at least initially, to be more about building classes which are immutable than implementing const in C#. Bother, I don’t know if I have time to figure out even the right venue to place my idea :-/.

I do agree with the main tenant of this issue: that locals and parameters should be able to be declared readonly. This would be useful both when accepting structs as ref parameters and to control the mutability of fields in lambda capture implicitly-created objects. Even without these features, it can enable the programmer to protect him/herself from accidental mutation of variables that should only be set once. However, this issue is then also not a place to discuss changes to what readonly means. C# already has a well-accepted (if broken) meaning for readonly, and the changes that @jonathanmarston and I suggest should be considered separately.

@HaloFour
HaloFour commented Sep 8, 2015

@binki Could always start a new proposal. :)

I think the problem is that from C# it can be difficult to tell what does mutate state and even if you could you wouldn't be able to prevent those methods from mutating state, only prevent calls to those methods. That's why I mentioned ref Point as to C#'s point of view there is nothing about calling Offset that provides a clue to the compiler that the underlying Point will be modified. There would need to be work beyond compiler features to enable guarding against such changes at compile-time.

@gafter
Member
gafter commented Nov 12, 2015

"readonly for locals" would be a "free feature" of #6400. But it doesn't help with parameters.

@quinmars

I also would really like to use readonly (and let) in local scope. But in my opinion readonly should have the same consequences on local variables and parameters as it has on fields.

readonly MyStruct x;
x.Func();

Here an implicit copy of x will be created to call Func(). That's how readonly fields work today [1]. Therefore I don't see how a readonly ref parameter will have a positive effect on the reduction of structure copies. Nearly any access to the passed structure reference will create an implicit local copy.

So I vote for readonly locals, but against readonly parameters.

[1] http://blogs.msdn.com/b/ericlippert/archive/2008/05/14/mutating-readonly-structs.aspx

@aluanhaddad

@gafter using let to declare local variables reads well in the pattern context of your example:

let (int x, int y) = ComputePoint();

but it reads very poorly as the standalone declaration of an implicitely typed readonly local variable:

let x = 5;

looks ok out of context, but in the broader syntactic context it looks very out of place:

var radius = 2;
let pi = 3.14159265359;
var area = pi * Pow(radius, 2);

is awkward but passable.
However, it starts to look really weird in some constructs:

foreach (let customer in customers)
{
...
}

And if it were adopted as a parameter modifier, then things really read awkwardly:

bool UserHasAccess(let UserRole userRole, let bool lockDownInEffect, let Request request) {...}

I know Swift does this, and it reads very poorly there as well, so I don't think that legitimizes it.
ES6 also uses let, with a different meaning, and it reads poorly their as well

for (let value of values) {...}

I know it's just syntax but I really think another keyword would be better. My preference would be for val but even the more verbose readonly would be better than let.
I think let does read well in LINQ expressions because of the alternating verb preposition verb flow of the LINQ keywords, but it looks out of place in a lot of imperative situations and downright silly as a parameter qualifier.

Edit: I realize I am reiterating previous remarks made by @gafter, @MadsTorgersen, and others, as well as repeating myself, but I think it's worth bringing the "does it read well" concern up again in light of the subsuming work on #6400.

Edit: accidentally submitted before I was finished.

@gafter
Member
gafter commented Nov 15, 2015

@aluanhaddad There is no proposal to embed the let statement inside the parens of a foreach loop. I don't know why you would want that, given that a foreach loop control variable is already readonly. Nor would we likely use it as a parameter modifier.
See also #115 (comment) #115 (comment) and #115 (comment) where I keep saying that to anyone who will listen. If we want to add support for readonly parameters, I expect we would use the existing keyword readonly.

However for local variables I believe let reads very nicely, especially aside the use of var for mutable locals as in your examples. I can imagine also supporting readonly as a modifier for locals if we allow it for parameters, though I do not imagine it would be used much given the more concise let.

@aluanhaddad

@gafter I know that foreach iteration variables are readonly, but if let is introduced with "read only local variable declaration" as one of its meanings then I think it would be a bit odd if let declarations were illegal in that context. Furthermore, if let is used for destructuring assignment, and if destructuring assignment is ever permitted in the declaration cause of the foreach statement, then the inconsistency will be increased.
As for readonly parameters, I see that my example was erroneous.

@gafter gafter removed the 1 - Planning label Nov 20, 2015
@gafter gafter assigned stephentoub and unassigned gafter Nov 20, 2015
@gafter gafter added the 2 - Ready label Nov 20, 2015
@mcgolledge

I am thinking a readonly modifier would be useful for allowing the compiler to inline methods. Methods that accept structs as parameters can not be inlined and I suspect that is because when you pass a struct to a method, you can not change the original, but if the method were inlined, you could, and that might have serious impacts on the behavior of the code.

If you could tell the compiler that the parameter is not going to modified by the method, the compiler would gain the option to inline it, and that would have performance benefits.

@HaloFour
HaloFour commented Dec 7, 2015

@mcgolledge I don't think that would matter as the readonly would only be a compiled-enforced directive and not part of the metadata of the method nor enforced by the CLR. Furthermore, the readonly modifier would only prevent reassignment of the variable, it would not prevent mutating the variable through assigning a value to a property or calling a mutating method.

public void Foo(readonly Point p) {
    p.X = 5; // legal
    p.Offset(2, 2); // legal
    p = new Point(0, 0); // compiler error
}
@mikedn
mikedn commented Dec 7, 2015

Methods that accept structs as parameters can not be inlined

Actually they can be inlined and current JIT compilers do so.

@mcgolledge

Thanks, I was a little out of date on JIT compilers. It looks like the ability to inline methods with value type parameters was introduced somewhere around 3.5.

@mcgolledge

@HaloFour , it's moot now, but I think your argument would depend on if Point and its members were classes or structs. It's just the old C-style const pointer const var problem, where you have to remember to make the pointer and what is stored at the pointer readonly or you haven't bought yourself much. And, there are always ways to circumvent that if you want to write malevolent code.

@HaloFour
HaloFour commented Dec 7, 2015

@mcgolledge I mentioned Point specifically because it is a struct. The CLR provides no metadata that a struct method might mutate the value of the struct in place and there isn't really anything that the compiler could do about it short of copying the struct to a temporary variable before attempting to call any methods on it.

But yes, it is moot for your point. It's just an issue with readonly and structs in general.

@GregRos
GregRos commented Dec 11, 2015

readonly just seems really wordy for my taste. I'd rather just use let for locals. There isn't any situation when the type cannot be inferred at the point of deceleration, so types need not be specified at all. Personally, I always define locals with var anyway.

I'm not against using readonly for parameters, but it's not very important for me.

Also, it would be nice if re-declaring locals would be allowed, such as:

let n = 5;
let n = 6;

With the latter declarations making the previous ones inaccessible. Note that after declaring a value with let, simply typing n = 5; would be illegal of course.

I'm strongly against const-correctness of any kind in C#. This is largely due to compatibility concerns. Const correctness is something that has to be part of the language since day 1 to work properly when using different APIs. For this reason, even if the feature were available, I would only ever use it in very narrow scopes or maybe even not at all.

@knocte
knocte commented Jan 12, 2016

We, as programmers, are so used to the term variable that nobody has realized/stated here that the term readonly var is actually an oxymoron: if something is "variable", then, it cannot be read-only.

So yeah, I also support those calling for let to be used in this case. Especially because it goes closer to the idea of immutability-by-default. It could also be allowed later at the class level, and everybody would stop using 'var' for everything, only in the rare case when mutability is needed. (For this reason I don't think taking the readonly to be allowed in more places is a good idea. We should find new ways to express immutability without the need of extra qualifiers.)

@alrz
Contributor
alrz commented Jan 12, 2016

@knocte It's been discussed for type inferred const as well (#4423). While let (#6400) has an specific form just for this purpose, according to the #115 (comment) there is a chance to support also readonly for read-only locals. As I said, I think an optional type would be more consistent instead of something like readonly var.

@Thaina
Thaina commented Jan 15, 2016

First. I would like to use const instead of readonly

Second. I think it better for const tobe shorthand for readonly ref. If it marked as const it should pass by ref immediately

Third. I go against let the most. let is keyword only for query expression. It should not be used anywhere outside. If the keyword should be reused it should let bigger scope reuse in deeper scope, not the other way around
Problem is like, value keyword was used only inside set { }. So we may have variable named value everywhere in other function. So as let

Finally. const should also be shorthand for const var. Because it always need to set immediately so if we write const i = 1; It must know i is int

@omariom
omariom commented Jan 19, 2016

What is the state of this proposal?

@whoisj
whoisj commented Jan 21, 2016

First. I would like to use const instead of readonly

The keyword const infers complete immutability and "const correctness". The readonly keywords means the references cannot be updated, but implies nothing about the values on the other end of the reference. For value types this makes them immutable (ignoring unsafe), for reference types all bets are off.

@alrz
Contributor
alrz commented Feb 2, 2016

I'd like to see const parameters as well, so

class VersionString { 
  public string Verison { get; }
  public VersionString(const string version) {
    if(!Regex.IsMatch(...)) throw new ...;
    this.Version = version;
  }

  public static implicit operator VersionString(const string str) {
    return new VersionString(str);
  }
}

VersionString version = "1.0.";

causes a compile-time error rather than runtime.

@HaloFour
HaloFour commented Feb 2, 2016

@alrz That's asking for a lot more than const parameters. That's asking for compile-time executed object literals. There's #263, #4971 and #5403 but this takes it a bit further by asking the compiler to attempt creation of the type at compile-time. The compiler would still have to emit the code to create the type at runtime as well, arbitrary types cannot be embedded as true constants in the CLR, IIRC.

@alrz
Contributor
alrz commented Feb 2, 2016

@HaloFour I suspect if #119 or #8181 can do this, but you woudn't get compile-time error for that right? I don't want it to be embedded as constant (though constexpr mentioned in #5474 can be another option), I want compile-time errors, for masochistic reasons.

@HaloFour
HaloFour commented Feb 2, 2016

@alrz

I assume that #119 would eventually lead to some kind of embedded metadata that would provide validation hints to the compiler for parameters but I doubt that would involve arbitrary code execution. I agree that compile-time enforcement of validation like that would be ideal but I envision that it would be based more on attributes decorating the parameter, e.g.

public static implicit operator VersionString([RegexMatches("...")] string str) {
    return new VersionString(str);
}
@alrz
Contributor
alrz commented Feb 2, 2016

@HaloFour Are you saying that we can only use conditions that have a built-in attribute for method contracts? And also, for that example, do I need to repeat all contracts for both operator and ctor? I think at least const methods (#7626) can be evaluated at compile-time to not get into that mess. I don't think method contracts without compile-time errors are any different than if.

@HaloFour
HaloFour commented Feb 3, 2016

@alrz

Considering that the notion of constexpr doesn't exist in C# or the CLR I think the attribute route is more practical today. Even if #7626 were to be considered (with constexpr) the rules would likely be quite strict (as they are with C++) and without support added throughout the BCL you probably couldn't make use of much in the way of supporting libraries, like regex. Even then I'd worry that it would be a pretty big potential attack vector. With C++ since it's compiling from source it can enforce pretty strict rules as to what the constexpr function is allowed to do.

Either way, not particularly related to readonly parameters.

@alrz
Contributor
alrz commented Feb 7, 2016

Still I think const parameters can be useful, to ensure that the parameter came from a compile-time constant.

@omariom
omariom commented Feb 9, 2016 edited

@stephentoub

The proposal doesn't cover readonly returns 😐
They make sense in the context of ref returns.
Like in this example:

readonly ref header = buffer.Read<Header>();

It would be very useful for ReadOnlySpan<T> whose indexer could return readonly ref instead of a copy of the value.

update: Found it is mentioned in Proposal: Ref Returns and Locals

@HaloFour

I don't see it mentioned in the proposal or comments, but would we allow for uninitialized readonly locals where the compiler can ensure through flow analysis that at any attempted assignment to the local that at that point the local is definitely unassigned? This would be akin to Java's "blank final".

readonly int x;
if (condition) {
    x = 1;
}
else {
    x = CalculateValueForX();
}
@aleks-sidorenko

We definitely need readonly locals/params if we want C# to be considered as practical language with functional programming concepts. Even Java gives you this possibility with final keyword.
I would use existing readonly keyword + shortcut options with let/val like in initial proposal description.

Is it planned for C# 8?
It would be better to stick this feature with pattern/matching.

@aluanhaddad

@aleks-sidorenko
This is obviously an important feature, but it doesn't stop you from writing functional code. Focus on referential transparency, avoid side effects that escape the function, be declarative, etc.

@Grauenwolf

What does this really give us?

Besides piece of mind of course. Don't get me wrong, I like the idea from a self-documenting code perspective. But is there any performance or API design advantages?

-----Original Message-----
From: "Aluan Haddad" notifications@github.com
Sent: ‎6/‎28/‎2016 4:14 PM
To: "dotnet/roslyn" roslyn@noreply.github.com
Cc: "Grauenwolf" jonathan@infoq.com; "Manual" manual@noreply.github.com
Subject: Re: [dotnet/roslyn] Proposal: 'readonly' for Locals and Parameters(#115)

@aleks-sidorenko
This is obviously an important feature, but it doesn't stop you from writing functional code. Focus on referential transparency, avoid side effects that escape the function, be declarative, etc.

You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or mute the thread.

@aluanhaddad
aluanhaddad commented Jun 29, 2016 edited

@Grauenwolf no it's not about API design, parameters, with the exception of ref and out, are already read only from the perspective of the caller. It's about being able to write code that's more easy to reason about, more expressive of intent within the body of the method. I actually don't like the idea of read only parameters because it's something the caller has to look at but it only affects the callee. Read only locals on the other hand are a very good idea.

@joeante
joeante commented Jul 12, 2016 edited

The proposal is not 100% clear on what it means for the calling code. I think it is quite important that ref should not be prefixed when calling the function. My understanding of having to prefix ref is to make it clear that the value might get mutated, which is an important thing to know. In the readonly ref case that is guaranteed not to happen, and it would actually make it unclear if the value is being mutated or not. I think it is also quite important to support operator overloads with readonly ref.

So I propose this:

Matrix3D myMatrix;
...
Use(myMatrix); // Note: ref is not necessary

public void Use(readonly ref Matrix3D matrix)
{
…
matrix.M31 = 0; // Error: can’t assign to readonly parameters
…
}

Some reasons for why I believe this is extremely important for C#'s future. At Unity we are very interested in seeing C# develop into a high performance language. We very much want to see this happen as soon as possible.

In Unity (using mono), ByValue takes 17.3ms while ByRef takes 3.5ms. In Unity and in game development in general the below is very real world common code that needs to be fast.

We are using a lot of structs because for games hitting 60 or 90FPS it is important that our customers can optimize their game to have zero GC allocations during a frame. This implies heavy usage of structs. And as you can see from the example below, having to prefix with ref looks quite ugly when doing math.

void Update()   
{
    Profiler.BeginSample("ByValue");
    Matrix4x4 matrix = new Matrix4x4();
    for (int i = 0;i<100000;i++)
    {
        var myValue = new Vector4();
        var res = matrix * myValue;
    }
    Profiler.EndSample();


    Profiler.BeginSample("ByRef");
    for (int i = 0;i<100000;i++)
    {
        var myValue = new Vector4();
        var res = Matrix4x4.mul(ref matrix, ref myValue);
    }
    Profiler.EndSample();
}

public struct Vector4
{
    public float x;
    public float y;
    public float z;
    public float w;
}

// A standard 4x4 transformation matrix.
public struct Matrix4x4
{
    ///*undocumented*
    public float m00;
    ///*undocumented*
    public float m10;
    ///*undocumented*
    public float m20;
    ///*undocumented*
    public float m30;

    ///*undocumented*
    public float m01;
    ///*undocumented*
    public float m11;
    ///*undocumented*
    public float m21;
    ///*undocumented*
    public float m31;

    ///*undocumented*
    public float m02;
    ///*undocumented*
    public float m12;
    ///*undocumented*
    public float m22;
    ///*undocumented*
    public float m32;

    ///*undocumented*
    public float m03;
    ///*undocumented*
    public float m13;
    ///*undocumented*
    public float m23;
    ///*undocumented*
    public float m33;

    // Transforms a [[Vector4]] by a matrix.
    static public Vector4 operator *(Matrix4x4 lhs, Vector4 v)
    {
        Vector4 res;
        res.x = lhs.m00 * v.x + lhs.m01 * v.y + lhs.m02 * v.z + lhs.m03 * v.w;
        res.y = lhs.m10 * v.x + lhs.m11 * v.y + lhs.m12 * v.z + lhs.m13 * v.w;
        res.z = lhs.m20 * v.x + lhs.m21 * v.y + lhs.m22 * v.z + lhs.m23 * v.w;
        res.w = lhs.m30 * v.x + lhs.m31 * v.y + lhs.m32 * v.z + lhs.m33 * v.w;
        return res;
    }

    // Transforms a [[Vector4]] by a matrix.
    static public Vector4 mul (ref Matrix4x4 lhs, ref Vector4 v)
    {
        Vector4 res;
        res.x = lhs.m00 * v.x + lhs.m01 * v.y + lhs.m02 * v.z + lhs.m03 * v.w;
        res.y = lhs.m10 * v.x + lhs.m11 * v.y + lhs.m12 * v.z + lhs.m13 * v.w;
        res.z = lhs.m20 * v.x + lhs.m21 * v.y + lhs.m22 * v.z + lhs.m23 * v.w;
        res.w = lhs.m30 * v.x + lhs.m31 * v.y + lhs.m32 * v.z + lhs.m33 * v.w;
        return res;
    }
}
@soroshsabz

@joeante ref keyword is not only for mark muted in method parameter, another usage of ref keyword is pass by reference instead of pass by value that can help me to increase performance with preventing extra copy data for large struct. So if I could not say readonly ref, I could not say to user that API dose not want to change your data. and if I could not say to user, my API have not informative enough. and if I could not declare informative and accurate API with language, language features is poor.

@Ziflin
Ziflin commented Sep 23, 2016

I agree with @joeante that specifying readonly ref at the call site should not be required. This does not seem to provide any useful information and makes what would likely be the default use case for most performance-minded code significantly more wordy as well as making converting existing pass-by-value code to pass-by-readonly-ref a much more difficult task.

@whoisj
whoisj commented Sep 27, 2016

specifying readonly ref at the call site should not be required. This does not seem to provide any useful information

Whoa there. How are readonly ref and ref functionally the same? One is basically void * the other is const void const *. If C# is going to promote passing addresses around, it needs a way to specify that the value on the end of that address won't get tampered with.

@Ziflin
Ziflin commented Sep 27, 2016

Whoa there. How are readonly ref and ref functionally the same?

@whoisj I never said it was. I said "at the call site". Having to specify readonly ref at the call site would prevent overloaded operators that want to use readonly ref for performance reasons from being possible, and it makes it considerably more 'wordy' to use on regular methods.

I would much rather see:

var vectorC = vectorA + vectorB;
var dot = Vector.Dot( vectorA, vectorB );

than:

var vectorC = Vector.Add( readonly ref vectorA, readonly ref vectorB );
var dot = Vector.Dot( readonly ref vectorA, readonly ref vectorB );

As was mentioned, most of the math/physics related code in a game engine would likely be written to use readonly ref and it would be great if the code using it was as clean as possible.

@soroshsabz

@Ziflin You right, so I think we have agreed with readonly ref at the call site does not necessary and should be avoid it, but readonly ref in method declaration is necessary too to ensure that it will not change ( could not change) input parameter. this is necessary for define comprehensive API.

If any person have question about why a method writer declare input parameter ref when does not need to change it? the answer is some of size of struct ( as you know struct in C# pass by value in method ) is bigger than to pass by value with negligible performance cost, so method writer want to prevents extra cost and it is not possible except declare readonly ref.

If asked again why big object declare as struct and not declare as class? the answer is some of struct is outside of scope of method writer (sometime it is in another library) or for some design consideration writer does not like to change struct to class.

According to above reason I want to can declare such method as below:

 public void Add(readonly ref Vector vectorA, readonly ref Vector vectorB)
 {
  ...
 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment