csharplang/meetings/2017/LDM-2017-02-22.md
2017-04-04 15:17:33 -07:00

90 lines
6.1 KiB
Markdown

# C# Language Design Notes for Feb 22, 2017
## Agenda
We went over the proposal for `ref readonly`: [Champion "Readonly ref"](https://github.com/dotnet/csharplang/issues/38).
# readonly refs
Passing and returning by ref addresses the copying and out parameter nightmare of performance critical code over structs. However, the use of it comes with risk: the recipient of a passed or returned ref can freely modify it.
This proposal introduces a `readonly` modifier on ref parameters and returns that incurs restrictions on the recipient of the ref similar to those incurred by a `readonly` modifier on a field. Thus, a ref can safely be passed or returned without risk of the recipient mutating it. This eliminates one common source of (deliberate, defensive) struct copying in C# code.
## Conclusion
We want to support this.
# readonly struct types
In and of itself, though, readonly refs contribute to another source of struct copying: when a method or property is invoked on a `readonly` struct (including now a readonly ref parameter or local), the C# compiler *implicitly* copies the struct, as a defense against the method or property invocation mutating the original struct. In the common case where the member is not actually mutating the struct, this is pure waste.
To counter this, the proposal also allows the `readonly` modifier on struct type declarations themselves. This signifies that no function member is mutating, and therefore calling them does not need to incur a copy. Inside struct members, `this` would be a readonly ref instead of a ref.
Once we have this feature, we could start warning on the compiler's defensive copying (with a warning wave for back compat).
More detailed versions are possible, where readonly-ness can be per struct member. This may not be necessary in practice, based on experience from the Midori project. (Or we can make it not strictly enforced, or allow exceptions that are somehow marked.)
## Conclusion
Support `readonly` just on whole struct types for now, until and unless evidence shows the need for per-member decisions.
# Syntax
We've been calling this feature "readonly ref", but it is really not the ref that is readonly; rather it is the thing that is referenced. For that reason is more correct to call it `ref readonly`, as in:
``` c#
public ref readonly int Choose(ref readonly int i1, ref readonly int i2) { ... }
```
Currently, refs *themselves* are always single-assignment in C#, but if we decide at some point to make them reassignable by default (which we could do without breaking), then you might want to be able to *explicitly* put a `readonly` modifier on the refs themselves. That would be `readonly ref`. And of course you could have `readonly ref readonly` where both the ref and the target are readonly.
## `in` parameters
`ref readonly` is a bit of a mouthful. For parameters, `ref readonly` is in fact the exact opposite of `out`: something that comes in but cannot be modified. It would make total sense to call them `in` parameters. However, it would probably be confusing to call a *return* value `in`, so `in` should be allowed as a shorthand for `ref readonly` exclusively on parameters.
``` c#
public ref readonly int Choose(in int i1, in int i2) { ... } // Equivalent to the above
```
## Call site annotations
With today's refs you need a call site `ref` annotation. This is to warn the caller of potential side effects and make sure they buy into them. Do we need it for `ref readonly` parameters, where there are no side effects by definition? We believe not.
``` c#
int x = 1, y = 2;
int z = Choose(x, y); // implicitly passed by readonly ref
```
We didn't question the `ref` annotation on return statements in `ref readonly` returning methods, but maybe we should.
## Values as `in` arguments?
The proposal allows literals to be passed as an `in` argument. A variable is created under the hood, assigned the value and passed along. Is that bad? On the one hand, it makes for less predictable performance, in that some arguments cause copying (into a fresh variable), and others do not. On the other hand, since no `ref` is required, the arguments look like value arguments, that are already copied.
At least initially, as we prototype this, we will allow any expression of the right type, and we will just copy into a shadow variable when necessary. Even when a variable of a different but convertible type is passed. If this turns out to be a problem, or a cause of confusion or overly defensive programming, then we'll reevaluate.
# Extension methods
We want extension methods on structs to be able to work by ref, so that they don't copy. VB already allows this. We should allow both ref and ref readonly extension methods on value types.
Ref extension methods on classes could be controversial - like an `EnsureNotNull` method that replaces the object reference in the variable. We are not necessarily opposed to this in principle, but we don't need it for this scenario, and we'd have to track down weird consequences of it. So for now, extension methods with `ref` and `in` this-parameters must extend a value type.
# Readonly parameters and locals
A separate feature, but seems to be part of the same package, and is a long standing request. Let's try to do these at the same time.
# Versioning
What happens when older code references newer code with ref readonly return types? The old compiler might not understand the `readonly` annotation, and would happily allow mutation. Upgrade the compiler, and you're broken. Can we encode it in metadata in such a way that it breaks downlevel? modreqs? Possibly, but then putting it on existing libraries is a library breaking change.
The CLR already does not protect readonly-ness, and there are therefore already ways you can circumvent it and mutate anyway. You can't avoid bad actors (reflection etc), but we would like to avoid accidentally breaking guarantees, and also breaking code.
## Conclusion
We'll build the first version using attributes, and therefore leaving us open to breaking on upgrade. We'll consider if there are mitigations, but poisoning downlevel code, e.g. with modreqs, seems like too big of a hammer.