Added: C# Language Design Notes for Aug 7, 2017 #796
Replies: 68 comments
-
@jcouv and I discussed ideas around a migration strategy if we take the "one big switch" approach (though the idea may also apply to any of the other opt in approaches). The idea was based on the observation that when you turn on the switch you don't really get warnings for:
You won't really get warnings here** because you're not using nullable values (and all unannotated references will be non-null), so there won't be a place** for nulls to enter into the system. The one place you would get warnings would be through:
In a way, this makes sense. Once you turn on this feature, the usage of 'null' is precisely the point at which null 'flows in' to your system and which you'll be warned about. The potential downside of this 'one big switch' approach is that then all your nulls end up warning you, and there may literally be thousands of these cases. To help with that, we think we could supply a pair of fixers to ease migration: Fixer#1 would go find all the warnings due to using null, and would change them to Fixer#2 would be a warning we would give you if you were using Foo(null);
...
Foo(null);
...
void Foo(string s) { ... } Initially, thanks to fixer#1 we would convert that into: Foo(null!);
...
Foo(null!);
...
void Foo(string s) { ... } Now, say the user goes and wants to actually address the usage of the -- This approach allows for the user to get themselves into a 'clean slate' state without a deluge of warnings. It also provides them a means to then go investigate and manually update cases one at a time, with fixers to help update affected reference locations quickly and easily. -- ** Ok, i lied a bit. It is true that using var s = b ? "" : null;
var len = s.Length; If you turn on the feature, then you will get a warning on "s.Length" (a direct dereference). This is due to the inference of the type "string?" due to the usage of 'null' in the ternary. For Fixer#1 to work, it will need to find all the cases where a nullable type was inferred due to
etc. etc. Fortunately, we should be able to enumerate these cases and detect them from a fixer. As before, the fix would be to use -- Finally, as we discussed in teh meeting with @jcouv and @DustinCampbell , we'll have a fixer that detects usages of nullable types (like As many users may be frightened away from opting in due to the number of warnings, having documentation to help explain more palatable ways to migrate may likely be necessary. |
Beta Was this translation helpful? Give feedback.
-
There seriously must be a better approach to this than to throw "damnits" literally all over the project. That completely masks the very problem that you're trying to solve. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour I don't see how that's the case. The intent would not be that you would be "done" at this point. The intent is that now you're at a good place to start actually tackling the issue of 'null' in your project. Consider a project the size of Roslyn. If we turn on this feature we will literally have 50 thousand warnings to have to deal with. Attempting to turn on the feature and fix all of these will be insurmountable. Just trying to keep up with the PR as our team continually changes things would be very difficult, and the review itself could take weeks. However, let's say we could turn on the feature and be in a good clean state. Now, the team can appropriately tackle 'nulls' in any particular area as appropiate when it fit into the owner's particular schedule. For example, i could decide one day to fixup all the code in the tagging subsystem of the IDE. I could simply go to those locations where 'null!' was used and i could process each one at a pace that made sense to me. I wouldn't even have to fix all of them. I'd just fix what i thought made sense for me in the time i had, could send out a small PR, and move on. In this way, organically, over a long period of time, Roslyn itself could totally adopt this feature. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour @Joe4evr please see the part where i mention:
The goal here is to make it feasible for people to actually effectively migrate code. And, to that end, people need a way to 'cut off' the feature to make it non viral. An analogy I used when discussing this idea with people is how we were able to introduce 'async/await' such that it did not have to be viral in a codebase. Specifically, someone could async/await'ify some pieces of their code, but then still nip it in the bud at points that made sense to them by using To that end, i was proposing actual IDE helpers that would both enable you to prevent both the deluge of warnings and the viral problem endemic here, while also giving helpers to then fix up all the fallout once you'd made the appropriate changes to your code. Through this, you would also get a clear indicator in the code that there was work to be done. |
Beta Was this translation helpful? Give feedback.
-
That might not be your intent, a savvy developer of a technologically advanced codebase. But that will be the end result in the vast majority of instances. The problem will have "gone away".
Thank you for both introducing and accidentally encouraging a massive anti-pattern. The number of people I deal with who think that |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Again, these things are necessary in order to actually make it possible to adopt the feature.
And the codebase will be no worse than today. The alternative is to make things painful. In which case people will turn it on, go 'noope', turn if off and never think about it again. For them, "the problem will have 'gone away'" as well. Except now they'll likely never turn it on again. |
Beta Was this translation helpful? Give feedback.
-
Again, without this possibility Roslyn itself could not have become async. What you see as a negative was actually necessary in order to adopt the feature that was actually quite valuable and beneficial. We have to be cognizant of the cost and difficulty people have migrating over existing code bases. This is something we hear over and over again, and it's something that has prevented even small introductions of warnings being added to the language. What i'm proposing allows for a way for people to say "yes, i really do want this, but i need to not have to rip the band aid off in one fell swoop. Needing to do that will make it so that i cannot use this feature at all". I'd rather make it possible for everyone to opt-in, even if it means their code may contain unpalatable constructs for a quite a while, versus making it impossible for many to opt-in ever for their existing codebases. |
Beta Was this translation helpful? Give feedback.
-
I wholeheartedly agree that enabling this feature (especially via "one big switch") will be a cumbersome event and that there should be ways to mitigate that. Placing 40,000 I'd rather something at the project level. Maybe something to suppress the warnings but still provide the flow analysis and "squigglies" at the file level. Or something like unchecked warnings in Java where you're informed that there are warnings and you have to enable another option to get the actual list of issues. You get the iterative approach to solving the problem without having to make massive changes across the codebase just to shut up single project-level feature that you're trying to enable. |
Beta Was this translation helpful? Give feedback.
-
Or I might've said this before, but one thing that maybe could help with this migration story is to only allow the expression Wouldn't make it completely fool-proof if some kid were to look around the internet, but it would be a step for those big companies you mention: A large project by a professional team (just like Roslyn) would be subject to code review, so once that project got properly migrated, they could set the flag to |
Beta Was this translation helpful? Give feedback.
-
I don't actually see an issue there. It would be akin to accepting a PR containing a commit that was only the rename of a commonly used type. It would hit a ton of files, but would be completely mechanical and trivial to review. |
Beta Was this translation helpful? Give feedback.
-
Again, if there is no way to mesh async with existing sync systems, async is dead in the water from day one for any existing codebase. Code-bases simply will not accept any sort of viral feature that cannot be effectively suppressed at a reasonable boundary (or a boundary of the developer's choosing). You view this as an anti-pattern. I view this as a necessity to get to a minimum viable product. -- Note: things would certainly be different if this was a V1 language. You could have no-block-async, and true-non-nullable from the start, if you had a V1 language. But we don't. We're going to be on version 8, and we have to deal with the problems inherent in migration and the needs of people who want to be able to adopt on a large scale. |
Beta Was this translation helpful? Give feedback.
-
As i said multiple times, i was just using
I would certainly be ok with any approach that suppressed the warnings effectively, but allowed users to go in and incrementally update things. As these would be warnings, it would of course be possible to even use existing suppression mechanism (like #pragma warning disable, potentially applied at a file level). Please do not focus on the "you're going to put 50k !'s everywhere". The crux of my post was that we could provide suitable mechanisms to allow one to opt in, while not being flooded with tens of thousands of warnings. We'd also want a way for them to remove those mechanisms once they became unnecessary as they started migrating hteir code step by step. It actually sounds like you're behind such an idea, so i'm not sure what is even worth arguing about. |
Beta Was this translation helpful? Give feedback.
-
The pattern remains long after the concerns of migration. I routinely catch developers trying to use
Ok, we're certainly in the same camp when it comes to the nature of the problem and that there needs to be some method to mitigate the "wall of warnings" to enable an iterative approach. We can take a step back here and focus on that instead. |
Beta Was this translation helpful? Give feedback.
-
Again, that's better than not being able to ship async. :)
Great. |
Beta Was this translation helpful? Give feedback.
-
Can we try to make the Fixer smarter so that it does flow analysis to infer nullables, Specifically, if flow analysis are confident a method parameter is safe to be null, fix its type to |
Beta Was this translation helpful? Give feedback.
-
So maybe rather than a switch this is a dial? Get the flow analysis in there, be as aggressively pessimistic as possible, expose the metadata, ship a reference "stock" analyser and let's hack at it to see what fits. Maybe it can't be one size fits all? Either way I think it would be massively beneficial for the flow analysis and metadata to always exist. That way people can sic analyzers on their codebase without having to flip something project-wide. |
Beta Was this translation helpful? Give feedback.
-
These are exactly the sort of discussions we're trying to have :) |
Beta Was this translation helpful? Give feedback.
-
BTW, Kotlin has the same issue as F# isn't? you can still have NRE there if say I wrote the library in Scala/Java and used Kotlin to consume it, I haven't used Kotlin yet so just wondering. |
Beta Was this translation helpful? Give feedback.
-
@eyalsk Yes, kotlin has this as well. They even spell these out as examples of null refs that can occur:
This is actually a great sort ot thing to look at as it shows the recognition and pragmatism of recognizing that shutting out all nulls in an existing, established ecosystem is very hard. Kotlin takes an approach that isn't completely safe, but which still provides a ton of value. You are both unsafe against the existing ecosystem not abiding by your annotations (same with our proposal) and you're also unsafe against certain coding constructs (same with our proposal). Being completely safe against both would likely be too onerous or too impactful to perf to be acceptable. They struck a compromise that they likely felt provided the maximum benefit with the least amount of pain (both for using the feature, and for runtime cost). |
Beta Was this translation helpful? Give feedback.
-
To me, the most useful thing is only the nullable annotation in member declaration, baked into the language. |
Beta Was this translation helpful? Give feedback.
-
@Thaina No, "n" is declared as "string?", so it's allowed to be null. However "s" is declared as "string" (so it's non-null). So if you assign 'null' into 's' you get a warning. |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi Thanks I misread it at first |
Beta Was this translation helpful? Give feedback.
-
Just to clarify (as I was clearly unclear with my comment here), by "analysis", I mean one or more people sitting down, working through the code to determine what it is doing and correcting it to work with this feature. I wasn't referring to the compiler's flow analysis, which I agree will be extremely limited. And that "extremely limited" is my issue with the whole idea of flow analysis. Arrays are an obvious case where the flow analysis cannot be trusted as you are preparing the dammit operator as a work around for those before the feature gets going. With your example, void Foo(string[] nonNullValues)
{
} I would argue that the best (though possible naive, see below) way to handle arrays is to not allow arrays of non-nullable reference types. So the compiler would complain about the above and insist it were changed to Of course generics screws this up. If we swap void Foo<T?>(T value) ...
int a = 1;
string b = "";
string? c = "";
Foo(a); // ok
Foo(b); // warning
Foo(c); // ok
I want the latter. With your proposed fix, string Foo() => null; would become: string Foo() => null!; The library author opts in, applies your "easy fix" and ships. How can I trust an opted-in library that may contain code like that? I'd far prefer conversion require manual effort and that it remain a niche feature for a couple of years, than it quickly gain the reputation as a feature that cannot be trusted. |
Beta Was this translation helpful? Give feedback.
-
That seems like it would make for a pretty terrible experience :( Say i actually create an array, and fill it completely. Now i have no way to express that i've done that. And that means from that point on, the compiler is telling me at every location in my code where i use that array that now i have to be adding null checks in. That's precisely the type of "bad warning" that i want to avoid and that will just end up frustrating most people. If you can't express things that are true about your own code then this feature ends up just being a PITA nuisance. |
Beta Was this translation helpful? Give feedback.
-
I can't imagine a scenario where that would actually happen. If anything, even if it mistakingly happened, it would be fixed immediately the moment someone actually tried to use such an API. As i mentioned before, i believe we exist in an ecosystem where people are not working to actually screw you over. Not only is that due to actual observation around how most libraries work, but it's also because we cannot effectively supply a feature that could possibly defend you against this. See the kotlin example above. Even they don't bother trying to make you safe from this sort of thing. F# doesn't bother trying to make you safe from this. |
Beta Was this translation helpful? Give feedback.
-
"trust" is invariably subjective. As mentioned already, other ecosystems out there have adopted these styles of null checking without trust problems. TypeScript has done this without having a trust problem. F# has done this. etc. etc. None of these systems are fully trustworthy. However, what they are is trustworthy enough, and that's precisely due to the ecosystem not being full of people who are actively trying to subvert these systems. The existence proofs are there. The concern you have isn't an issue in practice, and we find that enough of the developer ecosystem does a proper job to warrant these sorts of approaches. |
Beta Was this translation helpful? Give feedback.
-
You may well be right that dammit won't be the problem I fear it will. I've been wrong before, eg allowing variable scope leakage from if's hasn't proved the problem I feared it would. So I may well be being paranoid here too. Time will tell... |
Beta Was this translation helpful? Give feedback.
-
The way I see it is it really make sense for TypeScript because part of their design is to have an unsound type system whereas C# has a sound type system which I wouldn't say doesn't make sense but if we take the spectrum example you gave above, it's certainly a shift in thinking for me and for many others here because we're used to think about our code through the type system and be exact about it where with this feature you explicitly say what you want but you can no longer "count on it", it looks like a contract from usability point of view but it really isn't. :) |
Beta Was this translation helpful? Give feedback.
-
As someone who has been moving to option strict on for a couple of VB.net projects for the last year, I would like to encourage both a big flip (project level), and a small flip (file level), as well as fixers. Half of the changes I have to make is adding ToString to ints, which could easily be fixed by a tool. Depending upon the fixer, I might want it to be either on a file or project level (optionally both?). |
Beta Was this translation helpful? Give feedback.
-
Don't be affraid, it won't! be a problem :) |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
C# Language Design Notes for Aug 7, 2017
We continued refining the nullable reference types feature set with the aim of producing a public prototype for experimentation and learning.
Please discuss!
Beta Was this translation helpful? Give feedback.
All reactions