-
Notifications
You must be signed in to change notification settings - Fork 66
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
Nullable References #355
Comments
Sub Foo(a As Bar, b As Bar?) That would make the most sense, and aligns with the syntax for |
I don't think that we need nearly the complicated implementation that the C# team chose. I would instead just trust the type annotation. If it says it's nullable, then just assume it might be null. sub M(ns as string?) ' ns is nullable
WriteLine(ns.Length) ' Warning A: null reference
if ns isnot nothing then
WriteLine(ns.Length) ' Warning A: null reference
dim s as string = ns ' Warning B: nullable to non-nullable
WriteLine(s.Length) ' ok, not null here
dim s2 = if(ns, "") ' If operator smart enough to infer non-nullable?
WriteLine(s2.Length) ' ok, not null here
if ns is nothing then
return ' Unlike c#, this didn't buy us anything. ns is still nullable
end if
WriteLine(ns.Length) ' Warning A: null reference
end if
end sub
sub N(s as string)
if s is nothing then ' Warning C: 's' not nullable
end if
s?.Foo() ' Warning C: 's' not nullable
s.Foo()
s = nothing ' Warning B: nullable to non-nullable
end sub
sub Test()
dim ns as string? = "Test"
dim s as string = "Test2"
M(ns) ' nullable to nullable is no prob
M(s) ' Warning D: non-nullable to nullable.
N(ns) ' Warning B: nullable to non-nullable
N(s) ' Fine
N(if(ns, "")) ' If operator smart enough to infer non-nullable?
end sub
class Person
public FirstName as string
public MiddleName as string?
public LastName as string
end class
structure PersonHandle
public person as Person' Warning E
end structure
sub Test2(p as Person)
p.FirstName = nothing ' Warning B
p.LastName = p.MiddleName ' Warning B
dim s as string = nothing ' Warning B
dim a(9) as string ' Warning F
end sub I think it would be nice to make the warnings above separate so that the project can choose to ignore some of them. I think this gives us the easiest possible path to this feature, while retaining 99% of the benefit of preventing most null reference exceptions. |
Here are my current thoughts on this... C# nullable reference types is a wild experiment. It's an attempt to make people rethink the way they work with and think about every single usage of reference types. Yes, there is significant compiler support. But that compiler support is to create a bazillion warnings. It is not just the declared type. If you look at the fourth line of @jdmichel code, a warning is clearly not needed. Humans and the flow control graph (CFG) both know this cannot be null. You would not want flow control ignored, because then the code you write today to correctly manage nulls would not work. With flow control, an null guard (assert) will prove that the value cannot be null, regardless of what was known before the null guard. In the method As far as VB, we need to first see this grand experiment get traction in C#. Then we need to look at the question more deeply as to whether this makes sense in VB.NET. A feature whose purpose it to break code and force you to make significant changes to use it does not feel very VB-like - and that is thinking only of the Option Strict/Option Explicit approach. It really seems to make no sense outside that. Someone I respect was quite surprised that I am hesitant to think this feature belongs in VB. Then they sat through a single design meeting (there have been many, many) and looked at me and said "OK, I get it" I can't say I'm at a final decision on this. But at the current point I think the major special thing about VB is that the code does what it says it is doing - you can see and quickly comprehend that. Part of that is syntax, and maybe we could find good syntax. But part is also decades of experience that reference types are nullable and that |
To add to @KathleenDollard 's comment (It's not just the declared type...), if we don't allow flow control to affect the understood type, the only way to use this without a warning would be to declare a new variable ( Dim o As Object
' fill o
If TypeOf o Is Random Then
' we cannot treat o as an instance of Random
Dim rnd As Random = o
' we have to go through rnd
' alternatively, we could use CType
End If Not having to do this is the "killer app" of pattern matching for VB.NET -- even newcomers to pattern matching immediately "get" this benefit. There is also a long-standing suggestion to rely on flow control within type-check blocks (#172), in order to avoid this. |
First, thank you for responding to my suggestion. My perspective comes from ~15 years spent writing C++ in a style that prefers use of references over pointers in 99% of all code. So I'm used to thinking of "reference" as something that cannot be null, and it always felt like Java (then C# and VB.NET) re-approriated the term reference for a concept that feels more like a pointer. In any case, one of the great benefits of what we called "modern C++" back in the 90's was that this use of references made it easier to reason about code, because you were free to assume that such code could never contain null pointers. It decreased the cognitive load of all of our code, and meant that in practice the only time we had to deal with null was when interfacing with older C-style code from third party libraries. We wrote simplified wrappers around such code, and this is the only place you would see "if (foo_opt != null) { Foo& foo = *foo; ...}" guards to translate between the two styles. I think the C# team is going to already handle annotating most dotnet libraries with nullable type information, so I would expect these types of checks to be mostly unnecessary in VB/C# for teams that choose to embrace defaulting to non-null. Therefore, I expect it to be very rare to ever need flow control analysis for nullability. Which is why I thought VB could take a chance on embracing the non-nullable option (Option Explicit Nulls?). Although it might be nice to support flow control analysis to infer non-nullable (shadow variables?), my point is that you get 90% of the benefit without it, because in practice your code is not going to be asserting or checking for null at all, because within the projects that embrace this style then all the variables/parameters are already going to be non-nullable, and it will be extremely rare for any code to make use of nullable reference types at all. For existing code, or for people who don't want to embrace this new style, then everything continues to work as it does now. Incidentally, I also don't understand #172, because I don't understand why I would ever have a variable/parameter of type Object that I then check for the type. In my style of VB coding (Option Explict, Option Strict) you would just rarely/never find yourself in this situation in the first place. Almost all my code would have strong types, and it isn't much trouble to wrap any code that is not type safe. For the places that do pass nullable, I think it could be nice to make it easy to cast away nullability.
The same goes for dynamic types:
That being said, thinking back, I don't think either of the above code was used much at all, because we would just never write code that took a Foo? or Object in the first place. That sort of thing might only exist at the edges where we called other peoples code, and those also usually required extreme testing for things like throwing unexpected exceptions or otherwise behaving in undocumented or incorrect ways. Or they would have complex interfaces to handle flexibliity that we didn't want or need, so we would wrap them with our own simpler type-safe non-nullable types. Anyway, the point of all of this is to hopefully eliminate what is in my experience the most common class of bugs in Java/dotnet code, and even more importantly to make code easier to reason about by eliminating the need to consider null in most of our code. I spent 8 years working on a large Java program, and NullReferenceExceptions were extremely common, and I think embracing non-nullable defaults style would have prevented most of those bugs. It seems like the c# team was finding the same thing as they refactored existing dotnet libraries in this style. I just think they wasted alot of effort by supporting flow analysis. |
Today, when I define a variable of As you've noted, "decades of experience that reference types are nullable and that |
String is annoying. It's a reference type that likes to pretend it's a value type (e.g. a reference type shouldn't need to be immutable). And then there's dealing with whether Empty needs to be treated the same as Nothing. And then in VB, we allow people to pretend they're not even strings and do things like math(s) on them. I'm in favour of e.g. removing + as a concatenation operator. Back on topic: I do, however, kind of like the idea of decorating method parameters so that code analysis could know whether Nothing is a reasonable value to pass in, and post warnings about possible causes for concern. In the discussion on the linked article, a comment is made by Mads that guard checks for null are probably still a good idea for public APIs, since external code still pass a null in and so you'd still need to be able to deal with them. |
@jdmichel I think if we were designing the behavior for new language we would not have null as a default. It is an interesting opinion that we rely only on the declared nullability and not flow in Visual Basic. I still have concern about changing. the meaning of code - the foundational change that "Option Null Strict" or similar would bring to code. However, you've added a layer to my thinking which is to not change the default, but allow declaration of a non-null, including in parameters. Programmers would have to do more work to track values through, including the examples you gave above. Thus there would have to be "cast-like" thing as well as a declaration site gesture. One (knee-jerk, not well thought out) idea is something that looked like pattern matching (and may or may not actually be). As an example: this code from here bothers me. I don't yet see how you accomplish this without full safety and no warnings without expanding your suggestion. Here's a copy of the part that troubles me: if ns isnot nothing then
WriteLine(ns.Length) ' Warning A: null reference
dim s as string = ns ' Warning B: nullable to non-nullable
WriteLine(s.Length) ' ok, not null here As a possible solution, I'll rewrite temporarily using ! as the not-null specifier and a version of pattern matching syntax (which we are certainly not decided on). Dim ns As String
WriteLine(ns.Length) ' No warning, this is oblivious as today
If ns Is String! Into s
WriteLine(s.Length) ' No warning, it's for real non-null
End If
' For intentional nullable
Dim nn As String?
WriteLine(nn.Length) ' Warning, it can definitely contain null In that direction, Option Null Specified (or similar) would simply outlaw the first line of my sample. Code would always do the same thing. C# did not go the route of specifying everything, because they felt it would be too much explicitness - but VB thrives on explicitness. I actually think if we did this I'd like to fall even deeper into the explicitness to: Dim ns As String
WriteLine(ns.Length) ' No warning, this is oblivious as today
If ns Is NonNull String Into s
WriteLine(s.Length) ' No warning, it's for real non-null
End If
' For intentional nullable
Dim nn As Nullable String Anyway, these are just my top of the head thoughts on this. |
Slightly random thought While I get where they're coming from, the Nullable Reference type being introduced in C# is not quite the same as a Nullable Value type, because Nullable Value types are built on To be consistent, Then Public Sub GetCask(value as Wine)
If Wine Is Nothing Then Throw New ArgumentNullException
Console.WriteLine($"{value.Name} {value.Vintage}")
End Sub
could become Public Sub GetCask(value as Wine?)
If Not Wine.HasValue Then Throw New ArgumentNullException
Console.WriteLine($"{value.Name} {value.Vintage}")
End Sub
And at the same time decorating the method with the intention to allow nulls. |
Would such a syntax (
Or would there be two separate syntaxes for value types vs reference types:
Either possibility would add cognitive load to code authors. And if we're stuck with using symbols for explicitly-nullable- and non-nullable-reference-types, then the VB.NET explicitness argument falls down. VB.NET is easy to read and understand because the only symbols used in the language are almost universally understood even from a non-programming perspective -- e.g. mathematical operators (
Having used Typescript both before and after I would suggest that it's not such a foundational change. Even today, |
I think this is a great discussion, and I hope we're closing in on a solution that everyone will like.
(Btw, a few years ago I started using Anthony Green's April Fools font to let me try VB with all lowercase keywords, and I've grown to greatly prefer that look. I always typed VB keywords as lowercase then relied on the IDE to fix the case, but I find it's enough feedback for the IDE to colorize keywords so that I know it understood me.) Maybe we could even support a default for the 'as' clause to cast away the nullability so that the following would be a more concise way to do the same thing:
Or maybe it's more consistent if you rearrange the first syntax to:
That way it's clear that the 'as string' part can be inferred if Option Infer On is set.
This seems more readable for both value and reference types, but with Option NonNull Off you could still support the following without any new keyword (just an implicit new name for the new kind of non-nullable reference):
Turning on Option NonNull(able) would then simply bring the defaults for references in line with the defaults for value types and let you simplify the above syntax to:
This feels very much in the spirit of VB to me, but maybe I'm just too close to it, and it's been a pet peeve of mine since I first used VB (and others like Java, C#, Python, JavaScript), because I was already used to C++ non-nullable references. |
My mental model of pattern matching looks something like this: Dim o As Object
Select Case o
Case String: Console.WriteLine("It's a string")
Case Integer: Console.WriteLine("It's an integer")
End Select or something else: Dim o As Object
Select Case o
Case 5 To 10: Console.WriteLine("It's a number or numeric string between 5 and 10")
Case Like "A*B": Console.WriteLine("It's a string that starts with A and ends with B")
End Select An additional goal of pattern matching is to extract all or part of the object into new variables: Dim o As Object
Select Case o
Case String Into s: Console.WriteLine($"Length of string: {s.Length}")
Case Integer Into i: Console.WriteLine($"i ^ 2 = {i^2}")
Case With {.LastName = "Smith", .DOB Matches > #1/1/1985# Into Since1985}
Console.WriteLine("Much more concise than the following alternative, with only a single new variable")
Console.WriteLine("Also works if the object is not of the known type Person")
' If TypeOf o Is Person Then
' Dim p As Person = o
' If p.LastName = "Smith" AndAlso p.DOB > #1/1/1985# Then
' Dim Since1985 = p.DOB
' End If
' End If
Case (String Into s, Integer Into i)
Console.WriteLine($"Tuple of string '{s}' and integer '{i}'")
End Select It's rather more than just a replacement for the Because pattern matching is a generalized syntax for matching patterns, and not just typechecking+variable assignment, I don't think it appropriate to modify the pattern matching syntax just for this use case. |
Although the discussion is nominally about nullable reference types; Is that not really a misnomer, since reference types are by definition nullable? It looks to me more that we're talking about the potential of 'Not Nullable' reference types as additional tool in our toolbelt. What we're really talking about is a new semantic option (compiler flag) that allows the compiler and/or code analysis to make better predictions about potential problems (primarily) with passing reference-type arguments to So. The way I see it, what I'd like from this concept is:
From the C# blog:
I don't think that logic applies to VB. I think it would be preferable to give people the option of using a new way of doing things, if it adds value to what they're doing, using exactly the constructs they're describing. So I think adding both ! and ? decorations to reference type method parameters has merit to permit per-method, explicit declaration of intent. When introduced along with an appropriate The name of the option should describe clearly the handling of null references:
So how about something like When combined with Option NullRef Implicit
Class Wine
Public Property Name As String
Public Property Vintage As Integer
End Class
' 1) NullRef Implicit; this enforces NotNullable semantics on `value`
Public Sub GetCaskExplicitNotNull(value as Wine!)
' 1a) Compiler inserts null reference check here,
' throws ArgumentNullException(NameOf(value)) if value is Nothing
' 1b) value is defined, all good
Console.WriteLine($"{value.Name} {value.Vintage}")
End Sub
' 2) NullRef Implicit; this works the same way it always has.
Public Pub GetCaskImplicitNullA(value as Wine)
' 2a) Compiler does *not* insert null reference check
' 2b) Seeing no user-defined reference check, compiler issues warning.
' 2c) Potential RunTime NullReferenceException:
Console.WriteLine($"{value.Name} {value.Vintage}")
End Sub
' 3) NullRef Implicit; this works the same way it always has.
Public Pub GetCaskImplicitNullB(value as Wine)
' 3a) Compiler does *not* insert null reference check
' 3b) Seeing user-defined reference check, compiler issues no warning.
If value Is Nothing Then Throw New ArgumentNullException(NameOf(value))
' 3c) value is defined, all good:
Console.WriteLine($"{value.Name} {value.Vintage}")
End Sub
' 4) NullRef Implicit; this is redundant, but still legal.
Public Sub GetCaskExplicitNullA(value as Wine?)
' 4a) Compiler does *not* insert null reference check,
' 4b) Seeing no user-defined reference check, compiler issues warning.
' 4c) RunTime NullReferenceException if value is null:
Console.WriteLine($"{value.Name} {value.Vintage}")
End Sub
' 5) NullRef Implicit; this is redundant, but still legal.
Public Sub GetCaskExplicitNullB(value as Wine?)
' 5a) Compiler does *not* insert null reference check.
' 5b) Seeing user-defined reference check, compiler issues no warning.
If value Is Nothing Then Throw New ArgumentNullException(NameOf(value))
' 5c) value is defined, all good:
Console.WriteLine($"{value.Name} {value.Vintage}")
End Sub
' 6) If Nullable(Of T) extended to allow T to be a reference type, then:
' NullRef Implicit; this is redundant, but still legal.
Public Sub GetCaskExplicitNullC(value as Wine?)
' 6a) Compiler does *not* insert null reference check
' 6b) Nullable(Of T) lets you do this; and seeing a user-defined reference check, compiler issues no warning.
If Not value.HasValue Then Throw New ArgumentNullException(NameOf(value))
' 6c) value is defined, all good:
Console.WriteLine($"{value.Name} {value.Vintage}")
End Sub
Public Sub CallGetCasks
Dim newWine As Wine = Nothing
' Compiler warning, RunTime ArgumentNullException from compiler-added guard code
GetCaskExplicitNotNull(newWine)
' No compiler warning, RunTime NullReferenceException (from called method)
GetCaskImplicitNullA(newWine)
' No compiler warning, RunTime ArgumentNullException (from called method)
GetCaskImplicitNullB(newWine)
' No compiler warning, RunTime NullReferenceException (from called method)
GetCaskExplicitNullA(newWine)
' No compiler warning, RunTime ArgumentNullException (from called method)
GetCaskExplicitNullB(newWine)
' No compiler warning, RunTime ArgumentNullException (from called method)
GetCaskExplicitNullC(newWine)
End Sub or Option NullRef Explicit
Class Wine
Public Property Name As String
Public Property Vintage As Integer
End Class
' 1) NullRef Explicit; this is redundant, but still legal
Public Sub GetCaskExplicitNotNull(value as Wine!)
' 1a) Compiler inserts null reference check here,
' throws ArgumentNullException(NameOf(value)) if value is Nothing
' 1b) value is defined, all good
Console.WriteLine($"{value.Name} {value.Vintage}")
End Sub
' 2) NullRef Explicit; value becomes 'NotNullable'
Public Pub GetCaskImplicitNotNullA(value as Wine)
' 2a) Compiler inserts null reference check here,
' throws ArgumentNullException(NameOf(value)) if value is Nothing
' 2b) value is defined, all good
Console.WriteLine($"{value.Name} {value.Vintage}")
End Sub
' 3) NullRef Explicit; value becomes 'NotNullable'
Public Pub GetCaskImplicitNotNullB(value as Wine)
' 3a) Compiler inserts null reference check here,
' throws ArgumentNullException(NameOf(value)) if value is Nothing
' 3b) Compiler sees you have too, issues warning.
If value Is Nothing Then Throw New ArgumentNullException(NameOf(value))
' 3c) value is defined, all good
Console.WriteLine($"{value.Name} {value.Vintage}")
End Sub
' 4) NullRef Explicit; value is Nullable
Public Sub GetCaskExplicitNullA(value as Wine?)
' 4a) Compiler does *not* insert null reference check,
' 4b) Seeing no user-defined reference check, compiler issues warning.
' 4c) RunTime NullRefException if value is null:
Console.WriteLine($"{value.Name} {value.Vintage}")
End Sub
' 5) NullRef Explicit; value is Nullable.
Public Sub GetCaskExplicitNullB(value as Wine?)
' 5a) Compiler does *not* insert null reference check.
' 5b) Seeing user-defined reference check, compiler issues no warning.
If value Is Nothing Then Throw New ArgumentNullException(NameOf(value))
' 5c) value is defined, all good:
Console.WriteLine($"{value.Name} {value.Vintage}")
End Sub
' 6) If Nullable(Of T) extended to allow T to be a reference type, then:
' NullRef Explicit; value is Nullable
Public Sub GetCaskExplicitNullC(value as Wine?)
' 6a) Compiler does *not* insert null reference check
' 6b) Nullable(Of T) lets you do this; and seeing a user-defined reference check, compiler issues no warning.
If Not value.HasValue Then Throw New ArgumentNullException(NameOf(value))
' 6c) value is defined, all good:
Console.WriteLine($"{value.Name} {value.Vintage}")
End Sub
Public Sub CallGetCasks
Dim newWine As Wine = Nothing
' Compiler warning, RunTime ArgumentNullException from compiler-added guard code
GetCaskExplicitNotNull(newWine)
' Compiler warning, RunTime ArgumentNullException from compiler-added guard code
GetCaskImplicitNotNullA(newWine)
' Compiler warning, RunTime ArgumentNullException from compiler-added guard code
GetCaskImplicitNotNullB(newWine)
' No compiler warning, RunTime NullRefException (from called method)
GetCaskExplicitNullA(newWine)
' No compiler warning, RunTime ArgumentNullException (from called method)
GetCaskExplicitNullB(newWine)
' No compiler warning, RunTime ArgumentNullException (from called method)
GetCaskExplicitNullC(newWine)
End Sub Assignments between nullable and not-nullable references should have the same semantics as assignments between |
Not quite, although it is a little confusing. The C# proposal actually consists of two basic things:
I would suggest that we first have to consider what the right design should have been. In VB.NET, the shape of an object as recognized by the language comes from its type, wherein are defined the methods, properties, events, constructors etc. that are relevant for objects of a given type (inheriting or implementing). But It's true that the CLR type system does treat If we can agree that ideally The only difference here between C# and VB.NET, is that C# has a large community of enthusiasts, who would be happy to accept additional warnings as the price of more correct code, and are thus willing to drive these kinds of changes; my perception is that the VB.NET community is smaller, and less users who would be willing to pay this price. |
I honestly don't get this. Every time I think of the edge cases I keep coming up with a cat in a box .... At what point during instantiation is an objects fields null or not null. If they cannot be null, then all instantiation has to be to a default which is just another form null with another a value. VB already went down that road with strings and equality testing with Nothing. |
My point about pattern matching being confusing is that VB already has several different concepts that could be called pattern matching.
I googled and found Features of a pattern matching syntax, so I now understand how it relates.
So within a Select Case the variable being tested is implied, but outside of a Select you would specify it.
|
I'm really confused by the whole "cat in a box" thing. I think maybe I see what you mean if you're pointing out that it's not clear what assigning Nothing to a non-nullable reference type should do. I think many people are probably confused that Nothing does not mean Null, and instead means Default, because in practice with nullable references the default could be Null. But what is the default for a NonNullable reference?
The first seems easiest to me. |
A reference type at one stage or another is always null; if not it would be a value type. |
"tomato, tomato". I read the blog. And I call it as I see it. They can spin it however they like, they're still talking about introducing a new concept of non-nullable reference types. That they're wanting to gently shove C# people in that direction is fine. If C# enthusiasts are still struggling with the concept of null, then they probably need help :D.
That's fine. But doing what C# is doing, which is introducing (even if opt-in) a breaking change to 20 years of legacy code, is simply not acceptable in the VB world. Which is why I would rather 'add' the feature as something that VB developers can choose to use. Or not.
Which is why I suggested that Nullable(Of T) should be extended to allow T to be a reference type. Which I think would then make all types, value or reference, have a common way of dealing with nullability.
Agreed. And being in VB-land, we'll make it an
I suspect VB developers are a little bit like many non-mainstream-thinkers in the world today - scared to raise their heads for fear of being called out for innappropriate language use, or that they'll be DOX'ed for being VB fanciers. |
To TL;DR my earlier textbook (apologies for that):
By having this combination, you can code with or without using the options, and whichever convention suits your purposes. This doesn't cover everything the original concept is going for, but I think it covers most of it, relatively simply. Notes:
|
Maybe this will help. Let's try to understand what the dotnet clr looks like behind the scenes (simplified).
Currently lets pretend that #3 looks something like this psuedocode:
What I'm trying to suggest is that we change the compiler to instead implement #3 as:
And I guess now that I write it out, there is an additional missing concept we haven't discussed which is whether non-nullable references should also be immutable. |
The opposite. A reference type is mutable. It always starts as null. We can write special cases where we make it mutable only at instantiation, but that is a special case not a Type in the broad sense (as in ValueType, ReferenceType, Nullable ValueType etc).
So it remains a cat in the box. (dead or not dead ?) What we are interested in is null checking and safe code flow. This is what we practice today. We move away from the unknown state to a known state: we open the box and examine the cat. So rather than pretend it be something it isn't I think we should just focus on those aspects. You can never guarantee it cannot be null, hence there is no such thing as a non nullable reference, and all reference types are nullable . C# seems to be taking a shorthand approach, and annotating the type declaration to indicate if the compiler will raise warnings or not. The code can be compiled, warnings ignored, and a string?, string!, or string are still System.String, still all capable of being null. It is a "leaky abstraction" If VB wanted to improve code flow checking at compile time, I think using attributes that say "warn on null", or similar would be a better fit. And being a compiler directive attribute it could be applied at field, parameter, variable, method, class, code file, maybe even project. |
@Bill-McC Are you suggesting more of an Assert strategy? With nice gestures to make it easy? |
@KathleenDollard not sure. I think more thorough compiler warnings/assertions, something it is easy for existing code to opt into, and something that makes it easy to opt out of at a granular level. |
Moving into code block like an AssertNotNull(param, param, Action) or similar might help speed writing code. |
This is all true if the type system in VB.NET must precisely reflect the CLR type system. At the level of the CLR, a reference type always starts as null, and as long as it it mutable, can be set to null. But what if we define a virtual type layer that exists only at the language level? Those types could have additional rules enforced by the compiler. It could be argued that VB.NET already does this -- extension methods can be called as if they were instance methods, even though they don't actually exist on the extended type. It's true that because the CLR rules would be more lax, there is a greater potential for leaks in this abstraction; but that goes back to the point Mads made in the original post -- this kind of abstraction will never be perfect. The example of Typescript is instructive here. If you want to be technical, Javascript does have a very primitive type system -- there is a single type enforced by the compiler/interpreter, to which language-defined keywords and operators can be applied. Trying to use arbitrary keywords/operators not defined by the language will be rejected by the interpreter. All of Typescript's types are simply a virtual layer on top of the "real" type system, one based more on runtime behavior than compilation. |
@zspitz if only at language level it would be even more prone to failure as it would fall down at every framework call. |
We won't be changing the type system for null-reference types (which, yes, is a bit of a misnomer). It's hard to imagine that with C# picking the flow analysis route, and being quite happy with what they've achieved with it, that VB would take a route of a virtual type system. |
@ Bill-McC Sub SomeMethod(value as MyClass!)
' do something
End Sub is translated by the compiler into Sub SomeMethod(value as MyClass)
If value Is Nothing Then Throw New ArgumentNullException(NameOf(value))
End Sub Then would the runtime error not then be in the 'right place' - in the method that doesn't want nulls? |
@pricerc yes it would be in the right place as a null check. |
It isn't (tested on .NET Core 3.1, Option Strict On), because it's interpreted as:
and I'm guessing there's some sort of implicit conversion from the
This is further supported by wrapping the expression in an
which generates the following VB.NET string representation:
or in the DebugView of the expression:
|
@paul1956 I don't follow how this:
is relevant. If the variable If What does this have to do with VB.NET's evaluation of Unless you're saying that |
Some would argue that C# looks like it's swearing at you anyway, but I digress... I wasn't arguing the merits (or lack thereof) of anything, just observing that "?" and "!" are existing programming shortcuts in VB, so their re-use in slightly different context would be just as 'VB-like' as their existing use. Whether they should be overloaded for re-use in a different context is its own (related) discussion. |
Can you please expand on what you mean by "Forced Dereferencing"? I fear we may be talking at cross-purposes. While there may be some esoteric academic discussion to be had, my interest in this conversation is the idea of having some declarative syntactic sugar to simplify existing code patterns; a compiler hint, if you will, that a variable (or parameter) intended to hold a reference type should not be null, and that I'd like the compiler to warn me and/or generate appropriate guard code as appropriate. I can't help the feeling that you're talking about something slightly different. Then, on "!": what you describe as a 'dictionary lookup', I think of as a 'dereference', because my only use of ! for this purpose has been limited to VBA in the context of MS Access, where I'm "dereferencing" the fields in an ADODB.RecordSet to get their values as if they were properties of the recordset. I don't believe I've ever used it in VB.Net. I do think there should be a punctuation shortcut for explicitly declaring non-nullable reference types, but I don't have a strong opinion on whether "!" is a good one. Although, since I don't use it for anything at the moment, it would work well for me. As long as the intent is unambiguous from the context, I don't think it matters greatly. But way more important than any discussion around punctuation shortcuts, is actually getting traction on the idea that differentiating between nullable and non-nullable references has a place in VB. |
Just to point out it isn't the type that is nullable but the identifier used to object of that type. I am in favor of using an attribute on the point where the variable is declared or introduced into scope. eg |
I think (hope) we all know that.
two thoughts:
|
Given that 16.5.0 Preview 2 has a CodeFix to put all the guards in explicitly I am not sure this is that big an issue. Having the guards there explicitly make it clear what is happening. VB also already has the attribute which if it worked correctly would tell the compiler that an argument probably will be Null and it is definitely the methods responsibility to test before use (which could be hidden by compiler) and to set it. I think there would be a lot of internal changes to have a Type be 3 tokens an Identifier followed by 2 Keyword. |
Ok, playing devil's advocate again: If you're going to put the guards in, then what's the benefit to me as a programmer of a NotNull construct, whatever form it takes? I already use refactoring tools that will put the guard clauses in for me; I'm looking to de-clutter my code, not more-clutter it. Borrowing from a comment I made in January last year:
When coding, and I write a call to SomeMethod, the compiler and/or intellisense will let me know that I think that would cover at least 90% of my potential uses for non-null references. And the presence of NotNullable, or its punctuation-based shortcut, will tell me that there's a guard clause, it won't be any clearer to me if a code refactoring tool adds all the code in. If you've got a method with several 'not null' parameters, but has only one line of actual code, being able to declare that a parameter needs guarding will make the method much easier to follow. I get that attributes will also work, but as I mentioned, I think that would be inconsistent with how we handle Nullable(Of T). Of course my actual preference would be my other suggestion, which is an option which reverses the 'default nullability' of reference types, making it more like C#'s implementation, so that by default reference variables are 'not null' and have to be declared Nullable in exactly the same way as value types need to be. adapted from an earlier comment (leaving out most of it):
|
@pricerc Would I like the code cleaner YES and maybe the answer is to update the CodeFix to produce
Or with builti support for AnyNull
And the compiler produces something compatible with the 2 functions below
|
The If is optional and it only needed if you pass a StringBuilder |
I appreciate the code fixes and where you're going, but at the same time, that's not introducing an enhancement to the VB language, it's an enhancement to 'standard' refactoring tools. In fact, the same is true of suggestions involving the use of attributes instead of keywords - they're enhancing tooling, but not the language itself. Ironically, C# has stolen some of VB's thunder in that regard, e.g. with extension methods where they don't need to decorate them with attributes, and now possibly with (not-)nullable references as well. |
You need an enhancement to the language to make it simple
vs below which can be done today
AnyNull is a placeholder, a better name might be ThrowIfAnyNull and probably passing in Caller and line so the message point to the real issue vs the function doing the checking. It seems C# makes (Public?) functions with the first parameter "this" extension methods. I am not sure that is as flexible as VB that can extend any Class from a module (static non-inheritable) , and C# might be able to do that as well. VB could do this same thing without breaking anything but I would not spend any time doing it. |
I did not see any indication in this thread of what a nullable reference means. So we are talking about nullable references, where we should talk about non-nullable references. And what is a non-nullable reference? Besides the detection of possible null references, we could move the NullReferenceException to the assignment of a null value to a non-nullable reference; if you try to assign Null to such a reference, it means that the program does something illegal. This would move the exception from the first invocation of a method, property or field on a null reference to the point where the null reference is assigned. |
Except that the C# design consists of two parts:
AFAICT that's why the name of this feature is nullable reference types -- because that is the only syntax being added to the language. |
Point is that we are talking VB here, not C#. So I make it a VB issue, where the implementation can differ from C# as they are different languages. |
I'd say that VB should have a similar implementation as C#. VB is simple and beginner-friendly, but having to require |
@Happypig375 |
Which isn't to say that a successful C# implementation could not be used in VB.NET, if nothing more than a starting point for further discussion, And IMO the logical steps for the design decisions are the same for both languages:
I think any proposals to diverge from the C# design needs to provide strong justification why VB should be different from C#. Having said that, I could make a case that the syntax should not be the same:
Some other random thoughts:
|
Yes. I was sure this had been pointed out early in the thread, but I couldn't find it when I just looked.
Absolutely. We should be making changes for the benefit of VB developers. By all means, see how other languages do things, but VB has a 'style' which should be maintained, so features being borrowed from other languages need to be adapted so that they make sense in VB, and look like VB.
and
in VB, we have the luxury of 'Option' statements, so we could introduce an option to turn this on or off, with the 'legacy' option being the default.
Why shouldn't Nullable Random have members such as Value and HasValue? If we're talking about a VB-specific feature, I have seen no compelling reason why VB couldn't make Nullable(Of ReferenceType) behave the same way as Nullable(Of ValueType) - and provide HasValue and Value properties (note, I'm not making a judgement on the complexity of doing so). I've often wondered why reference types don't have an implicit implementation exactly like that, so that instead of
I think this is a major benefit, along with removing the need for manually coded guard clauses in methods. I.e. if the 'non-nullable references' option is turned on, then: Public Sub Routine(rnd As Random)
'do something
End Sub could be compiled as if you'd coded: Public Sub Routine(rnd As Random)
If rnd Is Nothing Then Throw New ArgumentNullException(NameOf(rnd)) ' or NullReferenceException
'do something
End Sub Additionally, if the method is private, an optimizer could move the guard clause to the calling method. |
That is absolutely a good idea. So we get (sorry @zspitz I really don't like the Or Nothing syntax; a type is one word in VB):
or
If you assign a nullable object to a non nullable object:
It is compiled as:
When a function result is assigned to a nullable object, it needs an extra variable in the resulting code:
But to be able to write rubust code, we should add an overload to TryCast:
This call differs from the existing TryCast that it won't return nothing, but a defined non nullable FailureValue instead. So we can assign like this (with Option ReferenceNotNullable Off):
This will make sure that the assignment will always succeed; when NullableObject is Nothing, the nonnullable reference MyDefaultValue will be assigned. |
That one is simple, just add the following extension methods:
|
But that's precisely the point of the syntax. The C# implementation does not create different types for nullable and non-nullable references; it's simply an annotation, and could easily be replaced with an attribute. Having the same syntax for "two things which are used the same way, and have more or less the same behavior" might be OK for C# (which uses the |
I would argue that it should be OK for VB too. When the move from VB6 to VB.Net happened, we got rid of Your example (: for inherits/implements) is a quite different to the discussion on reference vs value types, and fails to offer a compelling reason why VB couldn't or shouldn't make Nullable(Of ReferenceType) behave the same way as Nullable(Of ValueType).
Attributes are 'compiler hints', not language enhancements. Given the maturity of VB, many of the new concepts that get proposed for it could probably be implemented as attributes, but that wouldn't make them enhancements to the language. And surely the point of this vblang forum is to discuss potential enhancements to the VB language? I don't think that the fact that you can do something with an attribute is a good reason on its own to write off making an enhancement to the language itself. Not that I like talking about C# on the VB forum, but since you raised it, and extension methods in C# have already come up in this discussion - IIRC, when extension methods first happened, C# used attributes to enable them, just as VB still does. Except they later figured out a revised language syntax to handle this fairly popular construct, and dispensed with the clutter of the attribute. Taking a step back, and looking at design choices you'd make if you were starting from scratch with a 'new' VB. Would you choose to have two different types of structured type (classes and structures), or would you choose just one at let the compiler and run-time choose whether to put things on the heap or on the stack? If it were me, I'd choose the latter; it would simplify the language, since in most cases, on modern computers, with a modern O/S, it probably doesn't matter which you choose. Many languages don't offer the choice, for the good reason that it shouldn't matter to your common-or-garden variety software developer. Perhaps an expert doing fine-grained tuning of a problematic piece of code would have reason to specifically choose heap vs stack, but then perhaps that where attributes would work well, as compiler hints, for experts. |
I just did a test with C# (non) nullable types. So - whatever we do - it will be about syntaxial sugar and / or checking code generation. |
AFAICT you are proposing two separate things:
But in C#, nullable references are not a new type; they are nothing more than a compiler annotation. In that case, is the
Absolutely. But an attribute could certainly be used to power language enhancements; for example, because a method has the NB. There's also a balance in creating a new syntax. Part of the difficulty of implementing extension everything in C# is because the syntax used for methods can't be easily applied to other members.
I'm still thinking about this, and haven't come to a final conclusion.
Not really. As long as the VB compiler could reduce it to a CTS type, the CLR should handle it just fine. |
I don't think that's really practical. The primary distinction between classes and structures is whether instances of the type must be transferred by reference (classes) or can the value be transferred (structures). If I've written my program in such a way that it depends on one or the other, and then I modify my type causing the compiler to switch it from a structure to a class or vice versa, my program's behavior will change without any other indication. Thus, there is an explicit syntax difference between the definition of a class and a structure, because it affects the behavior of -- and my ability to reason about -- code that uses the type. |
And this is indeed the "wilderness" in the C# approach. And it is not a really good one. Rust handles This would indeed be introducing breaking changes in the BCL. I tend to think, VB would handle this quite well, due to And yes, I get the feeling, that this C#-driven development of the BCL is doing more harm than it adds valuable features. This is indeed a threath to the whole ecosystem. More and more, I understand Kathleens conservativeness. That said, I would still want diamond lambdas in VB. |
Is there already a plan for supporting the C# 8 features related to nullable references?
What might this look like in VB?
void Foo(Bar a, Bar? b) {}
->Sub Foo(a As Bar, b As Bar?)
or
Sub Foo(a As Bar, Optional b As Bar)
or even something else?
Even if you decided on the (breaking change?) Optional keyword then I suppose the IDE could support automatic conversion of the first syntax to save keystrokes.
There are bazillion details and corner cases, but I didn't see an existing issue on this topic, and I'm curious.
The text was updated successfully, but these errors were encountered: