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

Champion: Simplified parameter null validation code #2145

Open
jaredpar opened this issue Jan 15, 2019 · 304 comments
Open

Champion: Simplified parameter null validation code #2145

jaredpar opened this issue Jan 15, 2019 · 304 comments

Comments

@jaredpar
Copy link
Member

@jaredpar jaredpar commented Jan 15, 2019

  • Proposal added
  • Discussed in LDM
  • Decision in LDM
  • Finalized (done, rejected, inactive)
  • Spec'ed

Full proposal is here: #2144

In short though this allows for standard null validation on parameters to be simplified using a small annotation on parameters:

// Before
void Insert(string s) {
  if (s is null)
    throw new ArgumentNullException(nameof(s));

  ...
}

// After
void Insert(string s!) {
  ...
}

LDM history:

@Thaina

This comment has been minimized.

Copy link

@Thaina Thaina commented Jan 15, 2019

Shouldn't this be string! ?

And also doesn't C# will always warning on nullable reference type from now on?

@jaredpar

This comment has been minimized.

Copy link
Member Author

@jaredpar jaredpar commented Jan 15, 2019

@Thaina

Shouldn't this be string! ?

No. This feature is for runtime value checking only. It does not affect the type system in any way. Hence the syntax annotation is on the value identifier, not the type.

And also doesn't C# will always warning on nullable reference type from now on?

Nullable reference type checking is an opt-in feature. Also it is designed to warn on potential null references in the code base but it doesn't affect the execution semantics of it.

@qrli

This comment has been minimized.

Copy link

@qrli qrli commented Jan 15, 2019

Maybe over simple, though 😆
And it looks like a declaration rather than implementation

@DavidArno

This comment has been minimized.

Copy link

@DavidArno DavidArno commented Jan 15, 2019

Great to see this proposal has resurfaced, and has been championed. I think it an excellent idea but thought it had been lost amongst all the other work going on around NRTs. Thanks, @jaredpar. 👍

@Joe4evr

This comment has been minimized.

Copy link

@Joe4evr Joe4evr commented Jan 15, 2019

Shouldn't this be string! ?

No. This feature is for runtime value checking only. It does not affect the type system in any way. Hence the syntax annotation is on the value identifier, not the type.

You say that, but the original idea does put it on the type.

@GeirGrusom

This comment has been minimized.

Copy link

@GeirGrusom GeirGrusom commented Jan 15, 2019

It's the dammit operator placed on the argument. It makes sense that it is applied to the value rather than the type.

@alrz

This comment has been minimized.

Copy link
Contributor

@alrz alrz commented Jan 15, 2019

It makes sense that it is applied to the value rather than the type.

that logic works only for parameters not property setters (that's why we'd need to invent syntax like set!). to me, this is more of a very specific code generation problem (what if I just want to Assert?), IMO the functionality should be a part of method contracts, or alternatively, some special attribute (which could be extended to other validation logics as well).

@Thaina

This comment has been minimized.

Copy link

@Thaina Thaina commented Jan 15, 2019

@Joe4evr If I understand it right (from the jaredpar's comment after mine) this is actually newer feature unrelated to nullable reference type. It just a syntactic sugar to throw exception while nullable reference type is compile time warning

@Logerfo

This comment has been minimized.

Copy link
Contributor

@Logerfo Logerfo commented Jan 15, 2019

is this feature supposed to ship with 8.0 as well?

@DavidArno

This comment has been minimized.

Copy link

@DavidArno DavidArno commented Jan 15, 2019

@Logerfo, my money is on "unlikely, but not impossible". Seems a candidate for 8.1+ to me. But I could be wrong...

@bbarry

This comment has been minimized.

Copy link
Contributor

@bbarry bbarry commented Jan 15, 2019

In isolation I think this is great but I'd much rather see a more comprehensive contract annotation system with a potential way to expand it for pattern matching validations and potentially even arbitrary code validation as well as return value validations, even if those advancements don't come as part of a first version of this.

In short I worry that this:

ReadOnlySpan<char> Foo(string s!, int n) 
{
    ...
}

means something like this will never happen:

ReadOnlySpan<char> Foo(string s, int n)
    requires s != null // perhaps: requires s! 
    requires s.Length > 0
    requires n >= 0
    requires n < s.Length
    ensures value.Length > 0 
{
    ...
}
@Joe4evr

This comment has been minimized.

Copy link

@Joe4evr Joe4evr commented Jan 15, 2019

If I understand it right (from the jaredpar's comment after mine) this is actually newer feature unrelated to nullable reference type.

You're not wrong in that this feature could be achieved without NRTs, but the initial idea of having the compiler generate null checks for parameters (which is an extremely common scenario) came up during discussions of the NRT design. (Or rather, that was the moment the LDT would take it seriously. Theoretically, we could've had a syntax for generated null-checks ages ago.)

And while it's true that the check is about the value and not the type, applying the ! to the type 1) makes it more symmetrical, and 2) would probably make a lot of edge cases moot. (Granted, the reverse index syntax shows how much they care about symmetry. ¯\_(ツ)_/¯)

@jaredpar jaredpar self-assigned this Jan 15, 2019
@jaredpar jaredpar added this to the 8.0 candidate milestone Jan 15, 2019
@GrabYourPitchforks

This comment has been minimized.

Copy link
Member

@GrabYourPitchforks GrabYourPitchforks commented Jan 15, 2019

I spoke with @tannergooding a little regarding this. He suggested that it might be more sensible for the compiler to emit a call to a helper method to perform the throw instead. I agree with this, but for somewhat different reasons.

In my idealized scenario, the null check would actually be emitted by the JIT / codegen, not the C# compiler. The reason for this is that the JIT can generally handle this more optimally since it's already responsible for setting up things like exception dispatch. Consider the following method, whose prologue is quite common throughout the framework.

public void DoSomethingWithArrays(byte[] buffer, int offset, int count)
{
  if (buffer is null) { /* throw */ }
  if ((uint)offset > (uint)buffer.Length || (uint)count > (uint)(buffer.Length - offset)) { /* throw */ }

  /* actual method logic goes here */
}

With the proposed syntax the method declaration DoSomethingWithArrays(byte[] buffer!, int offset, int count) would allow the developer to elide the first line, and the C# compiler would emit essentially the same thing.

What I would love to see is the JIT elide the check entirely. Instead, optimistically and immediately attempt to dereference buffer.Length without performing a null check:

mov eax, dword ptr [rcx + 8h] ; rcx := buffer, move buffer.Length into eax

If rcx (buffer) is null, this will immediately trigger an interrupt just like any other null ref would have. The runtime would need to perform some sleight of hand to deduce that this should become an ArgumentNullException instead of a standard NullReferenceException. The end result is that the original method is faster in the happy path of no exception being thrown - less code gen to emit, smaller method sizes, fewer branches - at the expense of making exception dispatch a little more complicated. Generally this seems like a good tradeoff to me.

Coming back to the start of this comment, the reason this matters from a compiler perspective is that we should plan for a future JIT which might have these optimizations. There are a handful of ways we could do this: put a special attribute on the arguments, call a helper method to perform the null check, or smuggle some other metadata that allows the JIT to recognize and remove the compiler-emitted checks. I don't really have a vested interest in which particular mechanism is used, but I wanted to make the compiler team aware of the implications of the feature design.

@HaloFour

This comment has been minimized.

Copy link
Contributor

@HaloFour HaloFour commented Jan 15, 2019

@GrabYourPitchforks

And what if this method doesn't immediately attempt to use the non-nullable argument and instead does some body of business logic which definitely should not be happening if the argument is null? That seems like a lot of tricky optimization that would only apply to very specific codepaths.

@GrabYourPitchforks

This comment has been minimized.

Copy link
Member

@GrabYourPitchforks GrabYourPitchforks commented Jan 15, 2019

@HaloFour In most cases I've seen the null check is only meant to avoid a null ref exception on the line immediately following, which generally is a candidate for this optimization. This should serve the majority of developers well by improving the efficiency of their code. Any other more complex code would still go down the standard "perform an explicit null check" path.

@HaloFour

This comment has been minimized.

Copy link
Contributor

@HaloFour HaloFour commented Jan 15, 2019

@GrabYourPitchforks

So the specific optimization would only apply to a method accepting a single reference argument that shouldn't be null where the code immediately attempts a deference? Is that really so common? And if it is, wouldn't it be simple enough for the JIT to recognize and optimize without requiring a new special handshake from the compiler?

I'm not making the argument against making things faster for a common case, especially if it's common in the BCL itself, this just seems oddly specific. Even in your use case above, what happens if the C# code validates that offset is >= 0 before accessing buffer.Length, does that defeat this potential optimization?

@HaloFour

This comment has been minimized.

Copy link
Contributor

@HaloFour HaloFour commented Jan 15, 2019

@GrabYourPitchforks

Rereading your original comment it does sound like you're suggesting that this is something that the JIT could do, aided by the compiler emitting a call to a helper method to throw the exception instead of throwing it directly. If that's the case, sounds good to me. :)

@jaredpar

This comment has been minimized.

Copy link
Member Author

@jaredpar jaredpar commented Jan 15, 2019

@GrabYourPitchforks

Coming back to the start of this comment, the reason this matters from a compiler perspective is that we should plan for a future JIT which might have these optimizations

In this case I think we are fairly safe to change the implementation of the throw at a later date. Consider if the runtime introduced a helper Runtime.ParameterNullCheck<T>(T arg). A newer version of the compiler could bind to that if it existed and fall back to a generated helper if it didn't. There isn't much of a compat concern here as there is no user code involved.

@Korporal

This comment has been minimized.

Copy link

@Korporal Korporal commented Jan 16, 2019

I think C# is getting unwieldy, newer features are leading to more and more idiosyncratic syntax and non intuitive behavior, consider the assymetric Index concept and the odd way ref and var behave. The language is becoming more and more inelegant each year. Its time for a new language with a richer grammar, these C based grammars are crippled.

@HaloFour

This comment has been minimized.

Copy link
Contributor

@HaloFour HaloFour commented Jan 16, 2019

@Korporal

What does any of that rant have do with this proposal?

@Korporal

This comment has been minimized.

Copy link

@Korporal Korporal commented Jan 16, 2019

@Korporal

What does any of that rant have do with this proposal?

@HaloFour - I'm voicing an opinion on the contrived use of "!" which usually means "not" but will now have another meaning. This is being done because the options are limited due to the inflexible grammar.

I then go on to suggest that perhaps a new language with a better more flexible grammar is the way to go rather than performing syntactic acrobatics, for example a grammar that has no reserved words would perhaps offer much greater options for adding new features over time.

@HaloFour

This comment has been minimized.

Copy link
Contributor

@HaloFour HaloFour commented Jan 16, 2019

@Korporal

Funny, your rant doesn't mention that at all.

Syntax often has multiple meanings in many different languages. Many languages overload the definition of !, such as Rust and Swift.

I then go on to suggest that perhaps a new language

You're free to start a new language if you want, or use any of the other many, many languages in the ecosystem, .NET or otherwise. But I'd suggest that design discussions for languages that are not C# aren't appropriate for a repo specific for the design of the C# language, nor are you likely to recruit many like-minded individuals.

@CyrusNajmabadi

This comment has been minimized.

Copy link

@CyrusNajmabadi CyrusNajmabadi commented Jan 16, 2019

I then go on to suggest that perhaps a new language with a better more flexible grammar is the way to go rather than performing syntactic acrobatics,

Can you please take that discussion elsewhere? It is specifically out of scope and unnecessary in this specific issue. You are free to do any of the following as it suits you:

  1. take it to gitter.im. That's a good place to wax philosophical about what you want the language to be.
  2. blog about it on any platform of your choice.
  3. create a honey-pot issue for that discussion. while i would prefer it not be in csharplang at all, this would at least localize the dicsussion to a single space instead of leaking out and getting mixed up into real issues that are being worked on.
@MrJul

This comment has been minimized.

Copy link

@MrJul MrJul commented Jan 21, 2019

I find it weird that this feature is being championed at a time where another feature makes it almost unnecessary.

I believe that a few years from now, when nullable reference types and C# 8 are completely adopted by the BCL and most popular libraries, this type of check will be redundant: why would you want to check for a null argument, when you already know you're being passed a non-null one (unless you're an existing public library method already documented to throw ArgumentNullException)?

I know about the implementation details of null reference types, and that everyone is still "free" to pass null to non-null arguments. In practice, once everything is correctly annotated, that won't really happen.

I don't have any crystal ball, but I already saw a similar effect in a large codebase. Everything in this code base (and I mean every field, property, parameter and return type of every method) is annotated with ReSharper's [NotNull] and [CanBeNull] attributes. (I can't wait to convert them to C# 8 nullable/non-nullable references!) At first, any public method had the classic if (arg == null) throw new ArgumentNullException() for every argument, and private/internal methods had debug assertions.

We never encountered those. With everything annotated and compile-time checked we didn't see any NullReferenceException, ArgumentNullException or null assertions fired in years in first party code. Believe in the tools! :) We started to gradually remove all those checks, since they sometimes take more lines that the method itself, plus they have a (very small) runtime cost for what is basically dead code.

Please, at least consider using an attribute like [NullCheck] instead, keeping the simple suffix syntax available in the future for other purposes. This also seems the kind of feature which won't be needed with proper code generators / AOP.

@qrli

This comment has been minimized.

Copy link

@qrli qrli commented Jan 22, 2019

Reference #2153
Especially, to make the two features to work as one: #2153 (comment)

@yaakov-h

This comment has been minimized.

Copy link
Contributor

@yaakov-h yaakov-h commented Jan 22, 2019

With everything annotated and compile-time checked we didn't see any NullReferenceException, ArgumentNullException or null assertions fired in years in first party code.

My team has seen the same effect from hundreds of thousands of unit tests, and from Code Contracts.

However, we often have developers try and use code in new and exciting ways, like jumping into an assembly from PowerShell or C# Interactive or similar. Plus, a lot of code such as DI ends up going into Reflection and similar.

As such, the approach my team is taking is:

  • Add/keep null checks at the surface of the assembly
  • Remove them for all internal and private calls
  • (Pedantic mode:) Keep for constructors, particularly with DI.

Given the above, I personally do still see a use for this, even with a history of correctness proofs.

@fschmied

This comment has been minimized.

Copy link

@fschmied fschmied commented Jan 26, 2019

On my team, we are currently using Fody NullGuard to automatically generate argument null checks in conjunction with the Implicit Nullability ReSharper add-in, which implements static checking for non-nullable reference types. The nice thing is that with this combination (and a little bit of configuration), NullGuard already knows which method parameters need argument null checks and which ones don't. Switching to the feature of this proposal would require us to explicitly annotate the parameters again with no gain, which feels redundant and cumbersome to me.

You might argue that being explicit is better in this case, as you enable a side effect, but if you're embracing non-nullable reference types, the side effect is only the second line of defense (for Reflection calls, type checker insufficiency, etc) and, in my opinion, doesn't deserve syntax of its own.

About the proposed syntax, I feel it's too special-purpose and closed for future extension, as it can't be extended to more general contact checking.

And finally about the position of the exclamation mark: the proposal argues that it can't be put on the type because it's not part of the type in the type system. But it really could be part of the type system quite easily by making T! denote a runtime-checked, i.e., guaranteed, hard non-nullable type (as opposed to an ordinary non-nullable type, which isn't guaranteed to be non-null). For hard non-nullable types, the compiler and JIT could implement stricter rules, e.g., emit errors instead of warning or elide null-checks as an optimization when passing a guaranteed non-null value to a T! parameter.

So, T? would be a nullable reference type, T a soft non-nullable reference type, T! a hard non-nullable reference type. Very symmetric and, IMO, more useful than a non-extensible shorthand for a very specific element of contract annotations. Even if you don't plan to implement different behaviors in the compiler at first, the syntax would allow for evolution into this direction in the future.

@Grauenwolf

This comment has been minimized.

Copy link

@Grauenwolf Grauenwolf commented Jan 16, 2020

The proposed behavour: You have 1500 warnings and every method in the assembly is now going to throw an ArgumentNullException even if it expects and correctly handles null values. You have no choice but to work through all 1500 warnings in one go.

That doesn't make any sense at all.

  • Step 1: Turn on Nullable <-this doesn't change
  • Step 2: Fix your warnings <-this doesn't change
  • Step 3: Turn on WarningsAsErrors <-this doesn't change
  • Step 4: Turn on EnforceNullable <-this is new
@lcsondes

This comment has been minimized.

Copy link

@lcsondes lcsondes commented Jan 16, 2020

@HaloFour Is a #if considered a dialect? What about ConditionalAttribute? Those also require knowing the compiler flags that go into compilation.

EDIT: to be clear I'm after where the line is drawn between what is and what isn't considered a dialect

@Grauenwolf

This comment has been minimized.

Copy link

@Grauenwolf Grauenwolf commented Jan 16, 2020

The team has no interest in making dialects of the language.

That is not an argument. that's a position.

What I'm taking offense at is the idea that you keep tying it to NRT when this won't affect the that feature at all. NRT must be compile-time only for backwards compatibility. No one should dispute that.

But nothing in the EnforceNullable proposal changes the NRT design decision. We're arguing their position should change for future features.

@Grauenwolf

This comment has been minimized.

Copy link

@Grauenwolf Grauenwolf commented Jan 16, 2020

@HaloFour Is a #if considered a dialect? What about ConditionalAttribute? Those also require knowing the compiler flags that go into compilation.

Because you can read it in the code. If you see #if or ConditionalAttribute, then you know to check the compiler flag.

The whole point of EnforceNullable is that you don't see it in the code. That's what makes it a separate dialect.

@HaloFour

This comment has been minimized.

Copy link
Contributor

@HaloFour HaloFour commented Jan 16, 2020

@Grauenwolf

We're arguing their position should change for future features.

The team has already been pretty outspoken about how they feel about that. It ain't gonna happen.

#2145 (comment)

@Grauenwolf

This comment has been minimized.

Copy link

@Grauenwolf Grauenwolf commented Jan 16, 2020

I readily acknowledge that it won't be an easy case to make. But given the surprisingly high percentage of my code that is parameter null checks, it's worth fighting for.

@Grauenwolf

This comment has been minimized.

Copy link

@Grauenwolf Grauenwolf commented Jan 16, 2020

@lcsondes Don't get me wrong, I do not like the ! syntax at all. It's problematic because it already means "trust me, this isn't null" elsewhere.

But I do want to make sure you understand their position, if only so that you can argue against it better.

@lcsondes

This comment has been minimized.

Copy link

@lcsondes lcsondes commented Jan 16, 2020

@Grauenwolf there have been a few lines of discussion in this loooong thread, a compiler flag just happened to be among the last and it has a pretty strong opposition. Which is completely fair, I just hope that the team takes into account the ~300 comments made here by everyone, and make the right call, whatever that might end up being. There have been multiple issues raised with this proposal and multiple counter-proposals for what to possibly do instead, ranging from an IDE quick action to just write the if or the ?? throw for you, to the universally-beloved compiler switch. :)

@jzabroski

This comment has been minimized.

Copy link

@jzabroski jzabroski commented Jan 16, 2020

The more I think about this feature, the more I think it's like the dude with muttonchops in the high school year book 20 years on down the line. Why were muttonchops popular back then? I don't know, but nulls were cool at one point.

@markusschaber

This comment has been minimized.

Copy link

@markusschaber markusschaber commented Jan 17, 2020

@jzabroski About nulls having been cool at one point:
They're still cool (for some definitions of cool, at least). The possibility to express "no valid value there" can be very important and useful (see things like the maybe monad in other languages, or Nullable<T> for value types).
The problem with nulls in traditional C# (and Java, C/C++, Python, …) is that it's the default instead of opt in and one cannot opt out. Thus the compiler can't provide a safety net.
I'm quite a fan of how they brought that feature into C# as an afterthought - given all the circumstances. They clearly had no time machine to bring it into the language from the beginning. :-)

@MithrilMan

This comment has been minimized.

Copy link

@MithrilMan MithrilMan commented Jan 17, 2020

@lcsondes

@Grauenwolf this proposal, as is, would transform most of your null checks to one character each. A character that you can forget to put on your arguments, but still way shorter.

If you forget it, you'd have CA1062 remind you

@Grauenwolf

This comment has been minimized.

Copy link

@Grauenwolf Grauenwolf commented Jan 17, 2020

But why should I have to be reminded? Other than backwards compatibility, there is no reason to not have this check on every parameter.

@gilmishal

This comment has been minimized.

Copy link

@gilmishal gilmishal commented Jan 17, 2020

@HaloFour I would argue that making NRT not enforceable through runtime creates code that is working unexpectedly. If my code base is using NRT, I would expect to never be able to get nulls.

Think about all the comments about it, most people who see NRT think the runtime, or compiler or whatever already enforces these null checks.

My issue with the teams stance is that it seems theoretical to me, and doesn't seem like a good argument in practice. I am sure that you can give me real life example of how this will really affect developers or code base, but I wish you guys would have gone through a greater length to explain your decision, I know how hard this is, but I feel that NRT is a big change that the community should at the very least be aware of why were those decisions taken - It's very frustrating to see a language you really love using, making all this progress and improving to become arguably the best development language and platform out there, but still takes a very conservative decision on such an important matter, one that you don't fully understand.

I honestly believe I have a good idea in my head on how runtime checks should be implemented, in a way that won't hurt backwards compatibility or even forward compatibility to a degree. I even expect that the team have already thought about it, and you have a good argument as to why this is not really that good of an idea as I think, and you have good reasons to not go with it. The thing is that I don't know that, and there is always a small chance any one of the people commenting here have thought of something you haven't -I don't see a blog post, or a GitHub discussion (I read all 289 comments in this one) explaining the different runtime implementation options you thought of, I don't see the different issues with these implementations - all I see is a bunch of comments that explain that this decision is final, and there is no room from discussion.

I must also emphasize that I don't mean any offence, and I am not criticizing you, because I can understand how you probably had to explain to hundreds of different people why you make that decision and how it's final, and how annoying this, and customers in general are - I am just expressing the frustration about the implementation choice of NRT and the way you guys are handling our comments.

Even saying, runtime checks would take too much effort and are unimportant because the compile time checks already in place for people using NRT is good enough for the 90% of cases - is a decent argument

@MithrilMan

This comment has been minimized.

Copy link

@MithrilMan MithrilMan commented Jan 17, 2020

@Grauenwolf

But why should I have to be reminded? Other than backwards compatibility, there is no reason to not have this check on every parameter.

Maybe micro performance optimization , anyway I'm an advocate of code readibility and I like to have the code that self explains itself when possible, not relying on some hidden attribute, directive, flags, etc...

So to me it's important to be able to define the behavior in place, limiting the boilerplate code (and just placing ! would be the perfect fit to me)

I don't want to go into discussion about having a compiler flag or not, this is out of my interest, if they create one or not doesn't change anything to me (I'd just ignore it), as long as I have an option to define the behavior with !

@MiddleTommy

This comment has been minimized.

Copy link

@MiddleTommy MiddleTommy commented Jan 17, 2020

This feature should follow the same syntax as default parameters except with the != symbol
I am all for shortening syntax but just a ! at the end of a variable name seems too foreign
Please use this syntax
void Insert(string s != null) { ... }

@lcsondes

This comment has been minimized.

Copy link

@lcsondes lcsondes commented Jan 17, 2020

@MiddleTommy that would get weird if you also wanted to add a default value to it (= "MyDefaultString")

@mikernet

This comment has been minimized.

Copy link

@mikernet mikernet commented Jan 17, 2020

@gilmishal Here, knock yourself out with all the language design meeting notes you could possibly want :) The language design process for C# is quite transparent and public for those that want to follow along and see what goes into making these decisions.

@CyrusNajmabadi

This comment has been minimized.

Copy link

@CyrusNajmabadi CyrusNajmabadi commented Jan 17, 2020

I am just expressing the frustration about the implementation choice of NRT

NRT, and the impact of it on the language ad community are in no way "done". We're at step one of what will likely be a multi-year effort to be able to tackle a problem affecting trillions of lines of code over thousands of companies and millions of devs. No approach will ever be sufficient or appropriate for everyone for a problem space that large. The best that can be done is to thoughtfully introduce features, collect feedback and measure the subsequent positives and negatives, and then feed that into future designs in this space.

@mikernet

This comment has been minimized.

Copy link

@mikernet mikernet commented Jan 17, 2020

@gilmishal Also very few people are saying that runtime checks are no longer needed in 90% of cases. On the contrary, you should be doing runtime checks like you normally would. NRTs are not as simple as it seems on the face of it, and bugs involving nulls can still easily slip by, especially in generic library code. You want to be able to catch these bugs as close to where they happen as possible as an easy to diagnose ArgumentNullException that tells you exactly which parameter caused it, not as a vague NullReferenceException in an unrelated method belonging to some far away class 3 degrees of separation away inside another library because a lack of runtime null checks allowed it to get that far before the null was dereferenced.

@CyrusNajmabadi

This comment has been minimized.

Copy link

@CyrusNajmabadi CyrusNajmabadi commented Jan 17, 2020

I honestly believe I have a good idea in my head on how runtime checks should be implemented, in a way that won't hurt backwards compatibility or even forward compatibility to a degree

Please file an issue with your proposal. I would be happy to take a look at it.

@gilmishal

This comment has been minimized.

Copy link

@gilmishal gilmishal commented Jan 19, 2020

@mikernet
I actually went through some LDM stuff looking for the decision process, maybe I haven't searched well enough, but I couldn't find any mention of why NRT shouldn't be supported by the CLR.

As for your comment about adding runtime checks, the issue is the requirement for manually adding those checks when the CLR could probably add them automatically where needed, unless you opt-out (or opt-in). It makes sense that NRT is just a first step and there is more work to come, and until this work is complete (which could take many years) you should add those checks - but simplified, idiomatic, and exclusively parameter null validation code is basically cementing the opposite IMO.

@gilmishal

This comment has been minimized.

Copy link

@gilmishal gilmishal commented Jan 19, 2020

@CyrusNajmabadi
This is the validation I was looking for, a lot of these comments made it seem as though NRT is done, or at least, that you will never be willing to add runtime checks through the CLR, and make NRT more reliable, because that was your decision, just knowing that you are willing to entertain proposals about this matter is enough.

Now that I know you are willing to entertain my proposal, I will put some effort into learning more on the subject and how I would imagine it implemented before I file it.

@AartBluestoke

This comment has been minimized.

Copy link

@AartBluestoke AartBluestoke commented Jan 20, 2020

in summary of the above...

  1. in a perfect would the compiler would enforce at runtime that "the non null things are never null"
  2. there exists thousands of millions of lines of existing code for which 1) isn't true.
  3. it would be a (horribly) breaking change to turn on 1) for 2).
  4. any way to have 1) only for some projects would introduce dialects where you could not look at a function signature / c# file and tell if it was guaranteed to throw on a null, if it was a precondition, or if you had to handle it yourself.

Cyrus says "suggestions welcome" on a way forward for helping the compiler put the checks in that you would like without having to put ! on everything.

There appears to be a General consensus of "I want my (runtime) code to be enforcing that (at least) some of the (compile time only) non-null fields, of which saying thisOne! is important is the currently championed feature.

3 objections to the enforcing of this at runtime in general:

  1. i want to handle nulls in a special way, doing it via ! would change behavior, and be a breaking change
  2. checking a register for null on function call is just too expensive for me.. (no benchmarks mentioned anywhere in the thread)
  3. auto-enforcing for constructors breaks many lazy-creation scenarios (similar problem to readonly)
@MiddleTommy

This comment has been minimized.

Copy link

@MiddleTommy MiddleTommy commented Jan 20, 2020

@lcsondes It would not be weird as having a parameter that can not be null will never use a default value.

@mikernet

This comment has been minimized.

Copy link

@mikernet mikernet commented Jan 20, 2020

@MiddleTommy Why not?

public void Encode(string value, string encodingName = "UTF8" != null) 

or

public void Log(string message, [CallerMemberName] string callerName = "" != null) 

(as per your proposed syntax)

@lcsondes

This comment has been minimized.

Copy link

@lcsondes lcsondes commented Jan 20, 2020

@MiddleTommy Why not?

public void Encode(string value, string encodingName = "UTF8" != null) 

Or

public void Log(string message, [CallerMemberName] string callerName = "" != null) 

(as per your proposed syntax)

This syntax, other than being weird to look at IMO, is already taken. You're assigning the bool value true (the result of the expressions "UTF8" != null and "" != null) to be your default value. The only reason your examples don't compile is because there's no standard conversion from bool to string.

@mikernet

This comment has been minimized.

Copy link

@mikernet mikernet commented Jan 20, 2020

@lcsondes I'm not sure if you're talking to me or @MiddleTommy. I posted those examples because I agree with you that the syntax is weird and taken.

@MiddleTommy

This comment has been minimized.

Copy link

@MiddleTommy MiddleTommy commented Jan 20, 2020

@lcsondes I see your point that you could have not null and default parameters in certain scenarios. I back down my position of using !=.
Putting the ! after the variable name makes no sense to me. string! makes more sense.

@lcsondes

This comment has been minimized.

Copy link

@lcsondes lcsondes commented Jan 20, 2020

@lcsondes I see your point that you could have not null and default parameters in certain scenarios. I back down my position of using !=.
Putting the ! after the variable name makes no sense to me. string! makes more sense.

That's fair enough, however in that case others have pointed out that string! is not preferred because the ! doesn't interact with the type system, it's still a string-that's-not-nullable, just like regular string is. It is/was being considered at some level though, it's one of the syntaxes discussed in the LDM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.