csharplang/meetings/2017/LDM-2017-09-27.md
2017-10-05 20:15:39 -07:00

111 lines
4.2 KiB
Markdown

# C# Language Design Notes for Sep 27, 2017
***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!***
QOTD: `in int i = in init(in it);`
We changed our minds to allow and require explicit `ref` for `ref readonly` arguments.
``` c#
const int x = 42;
foo(ref x);
```
In the compiler, the order of things change, which is the most churning fix.
Technically not hard to do.
## Concern 1
Is 'ref' the right keyword? Putting `ref` signals to the caller that mutation may be happening. You need to understand what you are passing it *to* in order to know whether it's safe from mutation.
In other cases, like `return ref x` or `ref readonly r = ref x` the `readonly` is nearby, and it doesn't cause the same concern.
## Concern 2
Also, the `ref` in front of rvalues feels wrong.
``` c#
Foo(ref 42);
Foo(ref await FooAsync());
```
There's an argument that `ref` is about *how* the value is passed, not about the guarantees of the callee. On the other hand we use both `out` and `ref`, where the only difference is the guarantee.
Compromise position:
Use keyword to pass by ref, no keyword to pass "by value" (really by creating a new local and putting the value in).
Warn (or error) when an lvalue is being passed without the keyword, that could have been passed by ref.
* Is it important to be able to see in the code if something is passed by ref?
* Is it important to be able to see in the code if something is copied?
* Is it important that the parameter passing mode reflects the contract?
* Is it important that the same keyword is used for passing and returning?
Other compromise:
- No modifier: Do you best
- Modifier: Require ref, don't copy
The keyword would be `in`.
Should it then also be `in` in parameter declarations? A danger is that people misunderstand it for "being explicit" about value parameters, whereas `ref readonly` leaves no such room for interpretation. On the other hand, this may be a case where we'd overoptimize for newcomers to the feature, leaving too much syntax for too little benefit. A bit of education, maybe some warnings, maybe analyzers...
## Conclusion
- At the parameter declaration site, use `in`. `ref readonly` is no longer allowed.
- At the call site, `in` is optional. If it is used, the argument has to an lvalue that can be passed directly by ref. If not, then the compiler does its best: copy only if necessary.
No change to `ref readonly` return signature, or to `return ref`.
No change to `ref readonly int r = ref ...`
We would consider allowing `ref readonly int r = 42;` in the future, but it is only useful once we have ref reassignment.
We could consider a warning for `in` parameters of small types (reference types and built-in value types, maybe), but sometimes you do need that. Better left to an analyzer.
What about ambiguity:
``` c#
M(in int x){}
M(int x){}
M(42); // how do you resolve in direction of value
```
We could deal with this through a tie breaker in overload resolution, but it's probably not worth it.
If you have an API like that, there's going to be a method overload you cannot call. We could always add it later.
This scenario isn't entirely esoteric: if I want to update my API from value-passing to in-passing, then either:
- I add an overload. Then all existing call sites are broken on recompilation.
- I replace the current overload. The all existing assemblies are broken until recompilation.
Instead, you can add an optional parameter, change parameter names etc. to give another means of distinguishing.
Delegate conversion: No contravariance in the type of parameters, unlike value parameters. THis is because the CLR doesn't know about it. It's similar to the restriction on out parameters.
Conditional: If one of the branches is readonly, the whole thing is readonly. That means that calling a mutating member on it would mutate a copy, *regardless* of whether the actual branch chosen was readonly or not:
``` c#
void M(bool b, in x, ref y)
{
(b ? ref x : ref y).M(); // M is always called on a copy, even if b is false
}
```
# ref structs
## Syntax
Partial has to keep being the last modifier; ref can for now only be right before `struct` or `partial struct`.
Long term we want `ref` to float freely as a modifier. Just may not get to do the work now.