dotnet / csharplang

The official repo for the design of the C# programming language
11.02k stars 1.01k forks source link

Champion "readonly for locals and parameters" #188

Open gafter opened 7 years ago

gafter commented 7 years ago

See also https://github.com/dotnet/roslyn/issues/115

Design review

orthoxerox commented 6 years ago

I've been hacking on a prototype of this feature and I want to dump all my open questions here.

Unresolved syntax and semantics

The one thing this is clear so far is the syntax of read-only locals. They can be declared via the new syntax:

readonly Type x = ..., y = ...; //initializers are mandatory for readonly variables
readonly var x = ...;
let x = ...; //same as readonly var

readonly let

Is this an error, a warning or a tautology that the compiler silently accepts?

ref types

Since readonly is a modifier on the whole statement and not on the type, a ref local can now have two readonly modifiers. What do they mean?

ref readonly var x = ref ...;
//Same as in 7.2, can modify the innards of x if it's a reference type, cannot assign to x

readonly ref var x = ref ...;
//What does this statement do?

readonly ref readonly x = ref ...;
//And this?

One possible option that comes to my mind is using the new readonly for ref local reassignment. Until it's implemented, I propose to make readonly ref a syntax error, along with ref let.

Other places were variables can be declared

Already read-only variables

Variables declared in fixed, using, foreach statements are already read-only. Should we allow statements like foreach (readonly string s in strings) ...? I don't see a reason not to.

for statement variables

At first it looks like it makes no sense to declare a read-only variable in a for statement header, but it can be used for object-based iterators similar to IEnumerator. I think read-only for variables should be supported.

out variables, pattern variables, method parameters

They all behave like regular locals and should support readonly modifiers or let instead of var for the first two.

if (int.TryParse(s, out let i) ...
if (int.TryParse(s, out readonly int i) ...
if (o is readonly int i) ...
if (o is Point(let x, let y)) ...

void M(readonly int i) ...

catch variables

Why would anyone want an explicitly read-only Exception variable? However, I think they should be supported for the sake of uniformity.

The big unresolved question: "are read-only locals worth it?"

Read-only references are useful, they let you share pointers to memory without worrying about data modification. Read-only fields let you create immutable objects. But what do read-only locals bring to the table?

By themselves, they only provide a tiny bit of discipline. But if your methods are big enough that you cannot track what's happening to your locals without the compiler's aid you probably have bigger problems than runaway method state.

They could also be promoted to read-only fields in closures, this is one of the tricky parts of writing lambdas where extra insurance would be welcome.

They also provide some interesting interplay with ref types:

let x = 1;
ref var y = ref x; //Error!
ref readonly var x = ref x; //OK

However, I am not sure if that's something that's so important that it justifies the implementation of read-only locals.

This leads us to...

Compiler optimizations!

The compiler could probably do all sorts of wonderful stuff with read-only locals. However, I have no idea what optimizations are enabled by them. Something related to aliasing by refs, I guess? But that's solved by ref readonly.

If you have any ideas, please let me know. Right now I'm starting to feel that this kind of shallow immutability is actually not that useful.

svick commented 6 years ago

At first it looks like it makes no sense to declare a read-only variable in a for statement header, but it can be used for object-based iterators similar to IEnumerator. I think read-only for variables should be supported.

How would that behave for struct enumerators? Consider this code:

for (readonly List<int>.Enumerator e = new List<int> { 42 }.GetEnumerator(); e.MoveNext(); )
{
    Console.WriteLine(e.Current);
}

Assuming a readonly local behaves the same as a readonly field, in that any potentially mutating methods are executed on a copy, this loop would infinitely print 0, since e will always be in the "before first value" state.

Though this isn't really specific to the for loop. Maybe it would be worth considering when should calling a method on a readonly value type local produce a warning?

alrz commented 6 years ago

when should calling a method on a readonly value type local produce a warning?

that would require the method to be annotated with readonly (somehow) to verify it's actually not mutating, then you can just forbid non-readonly method calls. since that would be breaking for readonly fields we might require another notation for that (#421).

orthoxerox commented 6 years ago

when should calling a method on a readonly value type local produce a warning?

Probably always, if it's a void returning method.

alrz commented 6 years ago

@orthoxerox

I think readonly var and readonly let should be frobbiden for the same reason we don't have static const.

readonly ref would guard from ref reassignment -- it's currently not possible due to the lack of the implementation, (edit: it won't be a breaking change to be added later).

it should be possible to define let in using, foreach and fixed, (edit: and for, doesn't make sense to limit the feature on the assumption that for variables would always mutate). Also don't forget about let (...) deconstructions.

catch should be supported too. the value of all this is that the code would be self documented regarding mutations. You will know that a local will have its original value at any given point in the program.

I actually hit by this issue, I got a null ex at the end of the catch block. turns out it was being reassingned in a loop. I could make it readonly and spot the assignment at compile-time. An analyzer could fix-all all locals that are already not being assigned to readonly, eventually you can be sure that none of initialized values would be changed later on (this is specifically valuable in a code you didnt write, not at least recently)

as @svick said, we should never mutate readonly local structs. worth to mention, this very feature would make it possible to define readonly tuples anywhere in a program.

all in all, I see great value here, though, deep immutability is attractive, but it's not something that we could add to readonly as it exists today without breaking things, so it has to be a separate feature.

HaloFour commented 6 years ago

@alrz

it should be possible to define let in using, foreach and fixed, but not for, imo.

Why not? I think it's valid to have readonly iterator variables. You couldn't mutate them directly, but you could mutate their contents or whatever.

Java permits iterator variables to be marked as final.

alrz commented 6 years ago

Probably always, if it's a void returning method.

My opinion is that any difference between readonly locals and readonly fields would be surprising and inconsistent. you may want to promote a readonly local to a readonly field. it shouldn't change the semantics of the program. I don't see this feature as an opportunity to "fix" readonly. it's just to relax the scope where it's usable, as-is.

theunrepentantgeek commented 6 years ago

Probably always, if it's a void returning method.

That's assuming that the only reason for a method to be declared as void is because it mutates the object. I don't think that's a valid assumption.

For example, I have (somewhere at home) an immutable type that's used to post messages for background processing elsewhere in the application. (Nothing to do with a Windows Message loop, btw).

DavidArno commented 6 years ago

@orthoxerox,

The one thing this is clear so far is the syntax of read-only locals

That's unfortunate, as I think you have the syntax all wrong 😊

My thinking on the matter is that let should prepend a local variable declaration to mark it as read-only. readonly shouldn't be used for locals:

Type w = ...; // Explicit mutable variable declaration
var x = ...; //Implicit mutable variable declaration
let Type y = ...; // Explicit read-only variable declaration
let var z = ...; //Implicit read-only variable declaration

And that var in the last case should be optional:

let z = ...; //Implicit read-only variable declaration

Then the same rules can be used to simplify readonly refs, so the follow three lines are semantically identical:

ref readonly var x = ...;
let ref var x = ..;.
let ref x = ...;

and the following two are semantically identical too:

ref readonly Type x = ...;
let ref Type x = ...;

Then, for already read-only variables, let and let var can be used in place of var with no semantic change. The same applies to let Type too:

using (let var x = ...) // just a long-winded way of saying var x = ..., 
                        // supported purely for consistency.

Where I'd then be tempted to drop let in favour of readonly is for parameters:

void Foo(readonly int x, int y) => ... // x is read-only; y is mutable

and for out variables and pattern variables:

if (int.TryParse(s, out readonly int i) ...
if (o is readonly int i) ...
if (o is Point(readonly x, readonly var y)) ...

Again, as shown in the last line, readonly var can be shortened to just readonly.

My reasons for choosing let over readonly are:

  1. It's less to type;
  2. It simplifies syntax around already supported readonly-refs.

My reason for choosing readonly over let in the last three cases is simply because it makes more sense, from a "natural language" perspective.

Richiban commented 6 years ago

@DavidArno

My reasons for choosing let over readonly are:

It's less to type;

I respectfully disagree; I think it will, in the long run, end up being more to type. Since the only time you would ever use it is for locals (since readonly definitely reads better for parameters) and most locals will not be explicitly typed you've essentially lengthened let to let var.

I also feel that the switching between using let for locals but readonly for parameters would be a source of confusion and a lack of symmetry/consistency in the language.

I feel that, should this feature be implemented, a readonly local definition needs to be absolutely the same length (or shorter) than declaring a mutable local otherwise only those that care deeply about the feature will use it.

DavidArno commented 6 years ago

@Richiban,

... and most locals will not be explicitly typed you've essentially lengthened let to let var.

Not sure I'm following you. let var would be expressible as let. In fact the var could be implied and not even supported by the syntax. But even if it were supported, I'd see no "normal usage" use for let var; folk would just use let.

orthoxerox commented 6 years ago

@DavidArno I won't disagree with the color of your bike shed, except I think using readonly instead of let for out and pattern variables makes no sense. :p

What I want more is the list of benefits readonly locals/parameters can enable.

Richiban commented 6 years ago

@DavidArno

Apologies, in your post I glossed over the

And that var in the last case should be optional:

let z = ...; //Implicit read-only variable declaration

In that case I would actually advocate that the addition of var be disallowed. This would be consistent at least with range variables in LINQ expressions:

Given:

var strings = new[] {"one", "two" };

This is legal:

var _ = from s in strings
        select s;

As is this:

var _ = from String s in strings
        select s;

But this is not:

var _ = from var s in strings
        select s;
GeirGrusom commented 6 years ago

Don't forget that both readonly and let already exists. It would be a circus if the syntax of these suddenly change for locals.

Richiban commented 6 years ago

So, in this hypothetical new feature, this would be allowed:

let s = "Hello world";

As would this:

let string s = "Hello world";

But this would not:

let var s = "Hello world";

...because the var is pretty redundant.

DavidArno commented 6 years ago

@orthoxerox,

What I want more is the list of benefits readonly locals/parameters can enable.

To my mind, that's only possible if we look at the bigger picture of readonly-ness. The collection classes highlight the current problem, in that I can do something like:

class C
{
    private readonly List<int> _list = new List<int>();

    public void MakeAMockeryOfReadOnly() => _list.Add(1);
}

So we really need a cascade of readonly classes, being able to mark parameters as readonly, disallowing those parameters to be used as non-readonly parameters in other methods etc. At that point, let becomes a logical addition as being able to make just about everything in the language as read-only, save for local variables just seems inconsistent, if not plain odd.

HaloFour commented 6 years ago

@DavidArno

That would be a separate proposal. This proposal only deals with the ability to restrict reassignment of the variable or parameter.

DavidArno commented 6 years ago

@HaloFour,

I prefer to look at the bigger picture when thinking though features like this one. Not doing so tends to lead to decisions being made that have negative knock-on effects upon other future language features.

aluanhaddad commented 6 years ago

let doesn't read so horrifically when it is restored to its proper grammatical function. πŸ‘ @DavidArno

Still would rather have val though, because let var is longer. But, it is at least readable and regular.

HaloFour commented 6 years ago

@DavidArno

I prefer to look at the bigger picture when thinking though features like this one. Not doing so tends to lead to decisions being made that have negative knock-on effects upon other future language features.

Which is nice, but it's still a completely orthogonal concern to this proposal. The behavior of readonly is already established with fields and it would make more sense for locals/parameters to follow that same convention. Not to mention, that's infinitely easier to actually implement.

aluanhaddad commented 6 years ago

@Richiban

var _ = from var s in strings select s;

That is a good point. It would be really irregular especially if patterns are introduced in from clauses as has been discussed.

@GeirGrusom

Don't forget that both readonly and let already exists. It would be a circus if the syntax of these suddenly change for locals.

Yeah... so why not val again? Scala and Kotlin use it and I've never heard anyone complain.

HaloFour commented 6 years ago

@aluanhaddad

Yeah... so why not val again? Scala and Kotlin use it and I've never heard anyone complain.

In my opinion, because let is already a (contextual) keyword with nearly identical semantics and that val looks very similar to var. But they are minor points. Swift has let and do they complain much? 😁

aluanhaddad commented 6 years ago

It looks really ugly in Swift. Swift is a handsome language for the most part but that really is a wart.

Now, while it is true that let in a query clause has a similar function, I see the immutability of query variables to stem more from the nature of queries themselves. I mean you can't mutate the identifiers introduced by the into clause either. But no one's proposing that (into) because that would be really ugly :)

While we're on the subject, why not readonly alone?

readonly s = "Hello world";
DavidArno commented 6 years ago

@HaloFour,

Which is nice, but it's still a completely orthogonal concern to this proposal.

Seriously, if you want to ignore the bigger picture, that's your choice. I'm going to take it into account though.

The following:

let t = (a:1, b:1);
t.a = 2; // Cannot assign to a member of t because it is a readonly variable

already makes sense as a feature, since that's the way readonly fields and in parameter structs already work.

Whereas:

let s = "1";
s = "2"; // error.

doesn't really add anything at the moment save for a "warm feeling of well written code". I therefore want to look at what else is needed around this feature space to make it a compelling feature. Calling this orthogonal is more than a little myopic.

aluanhaddad commented 6 years ago

@DavidArno I see what you're playing at! A subversive gambit to make tuples readonly :wink:

Richiban commented 6 years ago

I would really, really prefer if we didn't use a readonly keyword to make objects deeply immutable as well as making the reference to said object un-reassignable. Swift conflates these two concepts and I've always hated it.

I don't like the idea that a data structure might be mutable or might be immutable, depending on the exact instance. A mutable type and an immutable type should be written completely differently, not to mention the fact that we currently have no way of labelling methods on an object that mutate its state.

HaloFour commented 6 years ago

@aluanhaddad

It looks really ugly in Swift. Swift is a handsome language for the most part but that really is a wart.

Minor stylistic preferences. If programming languages could only adopt keywords with unanimous approval then there'd be no keywords. Here's hoping this doesn't get as ludicrous as the private protected debate.

While we're on the subject, why not readonly alone?

Also a possibility, although I'm not that big of a fan of it being used as both readonly x = foo and readonly type x = foo.

@DavidArno

doesn't really add anything at the moment save for a "warm feeling of well written code".

That is the point. You do get the added benefit that it makes any value type effectively immutable since the value cannot be reassigned.

I therefore want to look at what else is needed around this feature space to make it a compelling feature. Calling this orthogonal is more than a little myopic.

Anything beyond this requires significantly more from the language and would require a number of other proposals to be adopted ahead of it to actually be any useful, such as proper pure functions, and deep support for them throughout the BCL. It also wouldn't align to how the language already behaves. I think that these are all good ideas, but that they should be addressed on their own merits and effort.

GeirGrusom commented 6 years ago

If they simply allowed readonly and let on local variables I would imagine that the syntax would be like this:

let a = 123;
readonly int a = 123;

M(out readonly int a);
M(out let a);
drewnoakes commented 6 years ago

@orthoxerox thanks for your detailed comment in which you share your discoveries. I feel excited about making things more readonly in general, but too wonder whether there's really much value here without deeper guarantees. Maybe, as you say, it opens up some optimisations.

One situation is variables hoisted to generated types that support closures. As I understand it, they're implemented as fields. If the captured local was readonly, then could the field be too? Are there any perf benefits to readonly fields? I believe they can bee worse for value types, as they are copied before their members are invoked in order to prevent modifications.

Lots of discussion here about the syntax, which is definitely important. Far more important is whether there's enough value to overcome it's -100 points.

DavidArno commented 6 years ago

@drewnoakes,

Far more important is whether there's enough value to overcome it's -100 points.

That's too easy. The following:

let t = (a:1, b:1);
t.a = 2; // Cannot assign to a member of t because it is a readonly variable

is worth 100,000 points all by itself as it enables tuples to be treated as read-only.

jnm2 commented 6 years ago

I have to agree. Tuples and other structs.

HaloFour commented 6 years ago

@DavidArno

is worth 100,000 points all by itself as it enables tuples to be treated as read-only.

which

doesn't really add anything at the moment save for a "warm feeling of well written code".

Because it's entirely up to you as to whether or not you mutate that tuple within the confines of your own method, just as it is with any normal variable. πŸ˜‰

But inconsistencies aside, I do agree that allowing developers to guard themselves from unwanted mutations is a good thing, and it accomplished by implementing readonly locals as simply not allowing reassignment.

quinmars commented 6 years ago

@drewnoakes, I actually do not expect any performance improvements here. If a value is quasi-readonly, i.e., it's not permuted in the local scope of a function (and that's what we are talking about), it shouldn't be to complicated for the compiler to recognize that and perform those potential optimizations, even without a new keyword.

The major benefit of this proposal isn't the possibility to prevent values or references to be altered accidently. I think that this is a rather small source of error or bugs. It isn't even about documenting inmutable values or unchangeable references. Once/if let will be available it becomes the new var. Estimated more than 90 percent of my local vars could be replace by let. And in the view places were not, the var keyword would draw attention on the mutable nature of the declared variable. Hence it'd make it easier for the reader to follow the code flow.

aluanhaddad commented 6 years ago

@quinmars

Once/if let will be available it becomes the new var . Estimated more than 90 percent of my local var s could be replace by let . And in the view places were not, the var keyword would draw attention on the mutable nature of the declared variable. Hence it'd make it easier for the reader to follow the code flow.

Which is exactly why I would like a nice keyword. However, bikeshedding, I believe your statement exactly captures the purpose of this feature and the value that it offers.

@drewnoakes

One situation is variables hoisted to generated types that support closures. As I understand it, they're implemented as fields. If the captured local was readonly, then could the field be too? Are there any perf benefits to readonly fields? I believe they can bee worse for value types, as they are copied before their members are invoked in order to prevent modifications.

readonly ref decouples immutability of value types and the need to copy them so I don't see how performance would be worsened by this feature. On the other hand, closures can already capture readonly state.

At any rate closures and readonly locals are orthogonal and that's good. In an imperative language it's essential that closures be able to capture mutable state.

drewnoakes commented 6 years ago

@aluanhaddad let me explain the scenario I was talking about a little more explicitly.

Here's a simple example:

int Foo()
{
    var foo = 42;
    Func<int> load = () => foo;
    return load();
}

The compiler generates something like the following:

int Foo()
{
    var closure = new Closure();
    closure.foo = 42;
    return closure.load();
}

[CompilerGenerated]
private sealed class Closure
{
    public int foo;
    internal int load() => return this.foo;
}

My question was whether making the local foo in the original code readonly (i.e. let foo = 42;) could result in the closure's field being readonly and whether this would actually give any performance benefit. There's no extra safety as the closure field is invisible from the user's perspective.

Looking at the code here it seems that the compiler would also have to perform the assignment via a generated constructor in order for the readonly closure field to be assigned. Might be possible, but it's more trouble.

DavidArno commented 6 years ago

Going back to @orthoxerox's question:

Read-only references are useful, they let you share pointers to memory without worrying about data modification. Read-only fields let you create immutable objects. But what do read-only locals bring to the table?

The first thing to challenge is "Read-only fields let you create immutable objects". No they don't. Read-only fields let you create read-only values and objects that cannot be replaced, but that can still be written to.

So the question has to be, should read-only parameters and locals simply mirror the behaviours of read-only fields, or could they be stricter? The biggest argument against making them stricter is inconsistency. The biggest argument against mirroring field behaviour is that - as @orthoxerox says - it doesn't really add much to the language.

So let's assume we dismiss the inconsistency argument. Could the rules be made stricter without expanding the scope of this feature to eg include pure functions?

One idea would be the following set of rules for read-only parameters:

  1. They cannot be written to fields, indexers, properties or indexers or properties of fields,
  2. They can only be used in turn as parameters to other methods if that other method's parameter is read-only too.

This set of rules (possibly with some extras that I've missed) would then guarantee that a method with all read-only parameters would effectively be read-only itself, ie it would have no state-changing side effects. This in turn would lead to a need for let (or for yet more flow control in the language) as shown here:

T Foo(T p) => ...  // p is not read-only

T Bar(readonly T q)
{
    _ = Foo(q); // no allowed as q is read-only and p in Foo isn't
    var x = p;
    return Foo(x); // x is not read-only, this is a problem
}

thus var x = p; could be an error and would need to be expressed as let x = p;

But there's a problem with all this: readonly parameters already exist, as in parameters were added to 7.2. Having readonly ref parameters behave one way and readonly parameters behave in a completely different fashion would be hugely confusing. "Fixing" in parameters would be a breaking change, thus a non-starter.

So we are basically stuck with readonly parameters and locals having no extra "powers". But what do readonly fields really bring to the language? Not much, especially for reference types. Yet people use them. That's because that "not much" is protection against accidentally overwriting a reference at a distance. As @orthoxerox again points out, such protection with locals only benefits those that write very long methods. As such,using read-only locals in such a situation would be just plastering over the problem, rather than truly fixing it.

Read-only locals and parameters don't bring much to the table. They offer consistency across the language: they enable externally mutable structs to be treated as read-only and they prevent overwriting references. More could be added, but that would be via extra features (eg pure functions), not via extra functionality being assigned to these locals. As such, we could go ahead and add this feature now, without compromising future functionality. Or we could wait until that future functionality is added (if ever) then add this feature.

aluanhaddad commented 6 years ago

@DavidArno I think you make a compelling case for either

  1. Not adding this feature.

  2. Adding the feature with no conferred immutability (as is currently proposed).

We don't have purity. We don't have const correctness. We don't have deep readonlyness. If any such thing were added I see no reason why it would be desirable to couple it to this feature. For example, in addition the inherent complexity of coupling them, we would lose the ability to treat them like other immutable types of such as string, to which we can freely rebind references. I see no advantage to that, and think it would make the language awkward.

alrz commented 6 years ago

An initial prototype is available here. See work items for details.

I guess at best this will be a post-8.0 feature since it touches a lot of areas that are either already in development or planned/proposed, namely, declaration expressions, recursive patterns, ref-reassignment, mixing declarations and variables in deconstruction, possibly deconstruction in out var and lambda parameters, implicitly-typed ref/in parameters in lambda expressions, etc.

It was previously discussed in roslyn repo that we should analyze control flow to guarantee that every readonly local is assigned once,

label: readonly int x = M();
// ...
goto label;

I don't think that'd be useful, because then you would need to disallow readonly locals in any loop!

wanton7 commented 6 years ago

@alrz This is so awesome! But I like to know if there is code style support planned for Visual Studio 2017, something like "Local variable can be converted to readonly"?

Pzixel commented 6 years ago

readonly ref readonly... C# begins to become C++... Beware, the Force, dark side is taking you...

alrz commented 6 years ago

@wanton7 IDE work should be done after the feature is completed. I think we can have a fix-all quick action to make all variables readonly if they are not re-assigned anywhere in the code.

@Pzixel That's just a sideeffect of the existing ref type syntax, defining a ref readonly T variable as readonly will give you readonly ref readonly T. Until 7.3 is shipped, we're immune to such possibilities.

wanton7 commented 6 years ago

@alrz That fix-all action sounds extremely good. You should permit ref let for readonly ref readonly and allow readonly var for consistency and just add code style support to prefer let

Pzixel commented 6 years ago

@alrz of course, I understand that. I even undarstand const const const in C++ world, and it has reasoning too, but it's not the thing I'd like to see in the language. However, I have no power to stop it so I just express my opinion.

alrz commented 6 years ago

I agree we should take a step back and reconsider the use of readonly for readonly locals (and possibly parameters), would like to know how others feel about this.

It has already been discussed in the context of const var x feature, that var (besides being oxymoron), is redundant and can be dropped (maybe optionally) in favor of const x, that would give you:

const int x = 5;
const x = 5;

Applying the same syntax to let, (as @Richiban commented above) would look like this:

let int x = 5;
let x = 5;

which reads a lot better than readonly int x = 5;. This style is not new in the language, for example:

var q = from int item in list ...
var q = from item in list ...

Note that any codebase that adopts readonly locals, would probably want to use it everywhere, imagine if the same codebase would like to use explicit types (even if not always) instead of var.. I don't think the result would look as clean as before. the worst part is that regardless of the codestyle of your choice, you'd need to use explicit types in patterns, as I mentioned above, it would give you is readonly Point p which is just weird.

For parameters, readonly doesn't look as bad, but there's lambda parameters,

(readonly int i, readonly int j) => {}

It has the same problem. I guess it would eventually cause you to stop using it due to its verbosity.

Also, readonly ref would be "illegal" on method parameters. How confusing would that be? (we can use a specific error message for that, but still)

Anyways, back to the starting point, I think we should follow const/from syntax and continue to use let anywhere (implicitly-typed or otherwise), for both readonly locals and parameters.

const x = e;
const int x = e;

M(out let x);
M(out let int x);

// patterns
case let (x, y):
case (let x, let y):
case (let int x, let int y):

// deconstruction
let (x, y) = e;
(let x, let y) = e;
(let int x, let int y) = e;

// lambda
(x, y) => {};
(let x, let y) => {};
(let int x, let int y) => {}

// (proposed elsewhere):
(ref x, in y, out z) => {}
(ref int x, in int y, out int z) => {} // already allowed

I don't know how ref/ref-readonly locals should look like with this though.

wanton7 commented 6 years ago

@alrz you are right in that whole codebase should use it. This might be little out there but how about setting this method, class or even project level. Like setting method or class immutable with readonly or immutable keyword, this would default everything to readonly. Then another keyword like writable or mutable would be used to make things writable.

Edit: If this was done project level, then it could work similar to Null Reference Types. It would give you a warning when variable is assigned if variable is not set as mutable. Constructor should be able to set class variables that are not mutable without warning. Rust uses mut keyword for mutable if something shorter is needed.

knocte commented 6 years ago

Hey guys, readonly var is a contradiction, "var" is shorthand for "variable", and "variable" actually means that it can vary, therefore, not read only πŸ˜…

ufcpp commented 6 years ago

@alrz With your idea, should the let clause in query expressions be changed too?

var q =
    from x in new[] {1,2,3,4,5 }
    let int y = x * x // should be allowed?
    select y;
Thaina commented 6 years ago

@knocte variable could means it can vary when it initailize. It did not need to means that it could vary after that

Consider mathematic variable

x + y = 2
x - y = 2

At first it might look like it could be any integer in the world. But when we write 2 equation then both variable was lock down to only one solution. It can't be vary anymore if we want the solution. It vary by the means that we can put anything in it place. But it not vary by the value of it

gafter commented 6 years ago

β€œVariable” means that it may vary from one instance of the variable to another, not that it is mutable.

DavidArno commented 6 years ago

@gafter & @Thaina, really? I think there's an element of desperation to those arguments there πŸ˜†.

And anyway, who cares what var means: just use let instead and the whole "well readonly var is an oxymoron, actually!" pedantic argument is neatly sidestepped.