Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: 'readonly' for Locals and Parameters #115

Closed
stephentoub opened this issue Jan 28, 2015 · 94 comments
Closed

Proposal: 'readonly' for Locals and Parameters #115

stephentoub opened this issue Jan 28, 2015 · 94 comments

Comments

@stephentoub
Copy link
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 Proposal: 'readonly' for locals and parameters Proposal: 'readonly' for Locals and Parameters Jan 28, 2015
@ryancerium
Copy link

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

@drewnoakes
Copy link
Member

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
Copy link

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
Copy link

@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
Copy link

@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
Copy link
Member

@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
Copy link
Member

@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
Copy link

@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
Copy link

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
Copy link
Member Author

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.

@RedwoodForest
Copy link

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
Copy link

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
Copy link

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.

@Richiban
Copy link

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
Copy link
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
Copy link

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
Copy link
Member

gafter commented Feb 3, 2015

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

@BrannonKing
Copy link

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
Copy link

louthy commented Feb 5, 2015

@gafter

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

How do you mean?

@HaloFour
Copy link

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.

@aleks-sidorenko
Copy link

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
Copy link

@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
Copy link

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" [email protected]
Sent: ‎6/‎28/‎2016 4:14 PM
To: "dotnet/roslyn" [email protected]
Cc: "Grauenwolf" [email protected]; "Manual" [email protected]
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
Copy link

aluanhaddad commented Jun 29, 2016

@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
Copy link

joeante commented Jul 12, 2016

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
Copy link

@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
Copy link

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
Copy link

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
Copy link

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
Copy link

@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)
 {
  ...
 }

@binki
Copy link

binki commented Jan 26, 2017

@joeante @Ziflin @soroshsabz shouldn’t pass by ref optimization just be something that the JITter does when it sees that the called method never tries to write to the struct and it’s readonly in the caller? I don’t think such an optimization should require any new syntax (other than this proposal so that the caller can create and store the struct in a local readonly instead of needing to instantiate a new class with a readonly field).

@Thaina
Copy link

Thaina commented Jan 27, 2017

@binki If JITter always does that work we would never request this feature. Because it never do its job as much as we want that why we need explicit syntax to force it to do

Not to mention making contract like virtual or interface

@binki
Copy link

binki commented Jan 30, 2017

@Thaina I’m not sure what the current calling convention is, but a struct passed by value could safely be treated as COW by the synchronous parts of the callee. Such an optimization would work fine with interfaces too.

@binki
Copy link

binki commented Jan 30, 2017

I’m just concerned that this issue is being hijacked by all these ideas about const correctness that are unrelated to it that will distract from the simple change of adding readonly locals which behave like JavaScript’s const. This simple feature will help the developer a lot with avoiding accidentally altering values that the developer intended to keep constant within the current scope.

Now, this does incidentally enable readonly ref parameters, unless that ends up being explicitly disallowed. But the behavior of a readonly struct field is very well defined and any existing C# programmer would expect a readonly ref parameter to behave the same, so I don’t see the issue there either nor how this requires us to suddenly define const correctness just to get JavaScript const-like readonly locals (including parameters).

So can we keep the scope of this narrow? ;-)

@gafter
Copy link
Member

gafter commented Mar 28, 2017

This is now tracked at dotnet/csharplang#188

@TahirAhmadov
Copy link

I think readonly is much better than let. let is meaningless in this context, and other than LINQ usage (I prefer methods-based LINQ anyway), it reminds of other, older languages. readonly on the other hand is very to the point, you can read the code and it's obvious what it's intended to mean. Also, readonly is already used for class fields with similar semantics. In fact, there is not a single reason to use let instead of readonly.
Instead of var a = 123;, we can have readonly a = 123; or readonly object o = "str";

@binki
Copy link

binki commented Sep 26, 2018

@TahirAhmadov I agree with you that defining let as equivalent to readonly var is confusing. For older languages, I myself only konw of BASIC which had a LET statement which actually mutated a variable (though it was not a declaration itself). For modern languages which those coming to C# would know, modern JavaScript uses let for declaring a mutable variable. If let is adopted, web developers with C# backends will have to keep straight that let is assignable in one context and readonly in the other.

I do have an issue with your suggestion of readonly a = 123;. In the existing precedent of readonly for class variables, it is always followed by a type. readonly object o = "str"; and readonly var s = "str"; make sense to me, but readonly o = "o"; both departs from the existing class variable syntax and would introduce ambiguity if explicitly specifying the variable’s type were allowed (which it should be for consistency with class variables).

However, a shorthand for readonly var is necessary to get developer buy-in. People use var because it is short—they would avoid using readonly var just because it requires more typing and clutters the screen/code. Also, F# and LINQ are precedents of using let to declare readonly variables. Unless you can come up with a different keyword that is both short, easy to use, and also conveys the meaning more clearly than let, I myself am going to continue hoping that let is introduced as readonly var.

This issue is dead though…

Please see dotnet/csharplang#188 and keep the discussion there. There has already been some discussion over let there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests