Jump to page: 1 26  
Page
Thread overview
[OT] my experience with nullable types (C#)
6 days ago
Kagamin
6 days ago
C# Nullable Fan
2 days ago
Walter Bright
6 days ago
Python
3 days ago
Kagamin
4 days ago
Dukc
2 days ago
Walter Bright
Re: [OT] OT: Null checks.
2 days ago
Timon Gehr
2 days ago
Walter Bright
2 days ago
Walter Bright
2 days ago
Timon Gehr
2 days ago
Walter Bright
2 days ago
Timon Gehr
2 days ago
Derek Fawcus
1 day ago
Timon Gehr
1 day ago
Walter Bright
1 day ago
Timon Gehr
14 hours ago
Walter Bright
10 hours ago
Timon Gehr
10 hours ago
Timon Gehr
2 days ago
Timon Gehr
2 days ago
Walter Bright
2 days ago
Timon Gehr
2 days ago
Kagamin
1 day ago
Timon Gehr
1 day ago
Walter Bright
1 day ago
Timon Gehr
14 hours ago
Walter Bright
11 hours ago
Timon Gehr
8 hours ago
Walter Bright
7 hours ago
Kagamin
7 hours ago
Kagamin
7 hours ago
Kagamin
7 hours ago
Kagamin
6 hours ago
Walter Bright
1 day ago
Timon Gehr
1 day ago
Kagamin
1 day ago
Timon Gehr
1 day ago
Kagamin
12 hours ago
Walter Bright
10 hours ago
Timon Gehr
6 hours ago
Walter Bright
12 hours ago
Walter Bright
11 hours ago
Timon Gehr
11 hours ago
Timon Gehr
6 hours ago
Walter Bright
7 hours ago
Max Samukha
6 hours ago
Walter Bright
1 day ago
Walter Bright
1 day ago
Timon Gehr
14 hours ago
Walter Bright
2 days ago
Max Samukha
2 days ago
Dadoum
2 days ago
Kagamin
6 days ago

Finally I had my chance to cope with nullable types in one of our C# codebases, and the experience wasn't nice.

I had a prejudice that nullable types give some kind of promise that you will have only a few of them, but now that I think about it I can't remember anyone making this promise, and reality is quick to shatter this prejudice in a very ugly way.
If you have a nullable type, all code now sees it as nullable, and you must insert null checks everywhere, even if you know it's not null by that point, and the volume of this null check spam is uncomfortably large. It's also not very clear what's the difference between me spamming null checks everywhere by hand and processor doing the same automatically.
Nullable types are retrofitted in dotnet and all legacy interfaces return nullable types, this greatly increases number of null checks.
DTOs are destroyed. How can you even have a DTO with nonnullable field? Initialize it to a stub value in default constructor? That's 1) NullObject pattern, 2) allocates extra garbage. The number of null checks is greatly increased.
Null check operator has pretty high precedence. If you want to null check an expression, you'll need to surround it with braces, this is especially annoying with await expression as checking the result of await is the best place for null check. Exclamation also looks like negation, imagine parsing if(a! == b), this happens unexpectedly often.

6 days ago

On Wednesday, 30 April 2025 at 07:17:37 UTC, Kagamin wrote:

>

If you have a nullable type, all code now sees it as nullable, and you must insert null checks everywhere

That is where null-conditional (?. and ?[]) and the null-coalescing (?? and ??==) operators come in handy.

They help to reduce the number of if statements needed.

6 days ago
On 30/04/2025 7:17 PM, Kagamin wrote:
> Finally I had my chance to cope with nullable types in one of our C# codebases, and the experience wasn't nice.
> 
> I had a prejudice that nullable types give some kind of promise that you will have only a few of them, but now that I think about it I can't remember anyone making this promise, and reality is quick to shatter this prejudice in a very ugly way.
> If you have a nullable type, all code now sees it as nullable, and you must insert null checks everywhere, even if you know it's not null by that point, and the volume of this null check spam is uncomfortably large. It's also not very clear what's the difference between me spamming null checks everywhere by hand and processor doing the same automatically.
> Nullable types are retrofitted in dotnet and all legacy interfaces return nullable types, this greatly increases number of null checks.
> DTOs are destroyed. How can you even have a DTO with nonnullable field? Initialize it to a stub value in default constructor? That's 1) NullObject pattern, 2) allocates extra garbage. The number of null checks is greatly increased.
> Null check operator has pretty high precedence. If you want to null check an expression, you'll need to surround it with braces, this is especially annoying with await expression as checking the result of await is the best place for null check. Exclamation also looks like negation, imagine parsing `if(a! == b)`, this happens unexpectedly often.

You are not alone in your pain tolerance for DFA based language features.

Without whole program analysis (purely research currently), you're stuck with accepting false positives if you want a 100% solution. This is a big failing point of DIP1000, it tries to be a 100% solution, and the resulting false positives are well beyond a lot of peoples pain threshold.

Given the different target audience of application VM's, and the fact that each language is doing it similarly, I expect that eliminating an entire class of issues their solution is right for them.

However they are not D, and encoding nullability into the type system like this with false positives isn't the right default. BUT this should not be the only mode that D compilers support (I would be using a 100% solution).

I.e. these cases should be caught by default:

```d
int* ptr;
int v = *ptr; // SEGFAULT
```

```d
int* identity(int* ptr) => ptr;

int v = *identity(null); // SEGFAULT
```

You can extend this to escape analysis, and uninitialized memory.

6 days ago

On Wednesday, 30 April 2025 at 07:17:37 UTC, Kagamin wrote:

>

Finally I had my chance to cope with nullable types in one of our C# codebases, and the experience wasn't nice.

That's what happens when you convert an old code base assuming nullability everywhere. And that's a good thing

>

I had a prejudice that nullable types give some kind of promise that you will have only a few of them, but now that I think about it I can't remember anyone making this promise, and reality is quick to shatter this prejudice in a very ugly way.

That's simply not true and it's dependent of the nullable context. By default, the nullable context is set to consider reference types as not nullable. Probably you are complaining about uninitialized references.

SomeClass c1; // this cannot be null
SomeClass? c2 // this can be null

In the first case, the compiler will complain if you dare to not initialize c1 through flow analysis, and will not bother you to check for nullability in your code. In the second case the compiler will let you use c2 uninitialized, but will tap you on the shoulder if you try to use it without checking for null.

>

If you have a nullable type, all code now sees it as nullable, and you must insert null checks everywhere, even if you know it's not null by that point, and the volume of this null check spam is uncomfortably large.

Please read again your own words, "if you have a nullable type, all code now sees it as nullable".

I find it normal, sometimes the compiler is not smart enough to determine if a null check is needed or not, but you can help him assuming the responsability. You can do it bluntly by using the notnull postfix operator (!) or by decorating your functions with specific attributes like NotNull or NotNullWhen.

>

It's also not very clear what's
the difference between me spamming null checks everywhere by hand and processor doing the same automatically.
Nullable types are retrofitted in dotnet and all legacy interfaces return nullable types, this greatly increases number of null checks.
DTOs are destroyed. How can you even have a DTO with nonnullable field? Initialize it to a stub value in default constructor?

  1. You can use the new required keyword.
  2. You can use the init property setter.
  3. You can initialize it in the constructor.
  4. You can provide a default value.
  5. You can declare it as nullable reference (?).

Depending on your use case, choose the best for you. Irrespective of the path, the compiler never fails to clearly identify in this case if you need a null check or not.

>

That's 1) NullObject pattern, 2) allocates extra
garbage. The number of null checks is greatly increased.
Null check operator has pretty high precedence. If you want to null check an expression, you'll need to surround it with braces, this is especially annoying with await expression as checking the result of await is the best place for null check. Exclamation also looks like negation, imagine parsing if(a! == b), this happens unexpectedly often.

I agree that the ?? operator precedence is surprising, but the compiler will not let you go without a null check if it's not 100% sure that a value is not null. That's the purpose of nullable references check.

You don't need a! == b, you can use directly a == b, there is no null check required here for the comparison to mandate the use of notnull operator (!)

Not null operator (!) is needed in two cases:

  1. When you dereference a nullable reference and the compiler cannot guarantee 100% that your reference cannot be null: someNullable!.SomeProp
  2. When you pass a nullable reference to a function which does not accept nullable references: func(someNullable!). Again, only if the compiler is not smart enough to guarantee 100% that someNullable is not null before the call through flow analysis.
6 days ago

On Wednesday, 30 April 2025 at 07:17:37 UTC, Kagamin wrote:

>

Finally I had my chance to cope with nullable types in one of our C# codebases, and the experience wasn't nice.

I had a prejudice that nullable types give some kind of promise that you will have only a few of them, but now that I think about it I can't remember anyone making this promise, and reality is quick to shatter this prejudice in a very ugly way.
If you have a nullable type, all code now sees it as nullable, and you must insert null checks everywhere, even if you know it's not null by that point, and the volume of this null check spam is uncomfortably large. It's also not very clear what's the difference between me spamming null checks everywhere by hand and processor doing the same automatically.
Nullable types are retrofitted in dotnet and all legacy interfaces return nullable types, this greatly increases number of null checks.

I had the complete opposite experience in Kotlin, where the default are non-null types.

It was a joy to work with. The nullability checks only came up when interfacing with Java code, but one way or another, you have to deal with nulls coming from Java anyway, either at compile time, or at debug time ;)

4 days ago

On Wednesday, 30 April 2025 at 07:17:37 UTC, Kagamin wrote:

>

Finally I had my chance to cope with nullable types in one of our C# codebases, and the experience wasn't nice.

When I was coding In C sharp, I defined a generic class called Ref that I used when I wanted to pass structs by reference, and also as my nullable type. I didn't like the standard nullable type because I couldn't change my structs in place with it. Admittedly this was largely because I was stubborn and wanted to use structs as in D (meaning: when I didn't want polymorphism) instead of using classes for many of those things like C# designers probably intended.

Reading your experience I wonder if mine was actually the best way to go about the nullable type.

3 days ago

On Wednesday, 30 April 2025 at 09:06:20 UTC, Python wrote:

>

You don't need a! == b, you can use directly a == b, there is no null check required here for the comparison to mandate the use of notnull operator (!)

Indeed can't find it now. I wonder where I saw it, maybe it was in intermediate code. Or maybe I was confused since the type system remains not very helpful: without nonnullability you're unsure if a pointer is null, with nonnullability you're unsure if a nullable pointer is not null, its type is still nullable, only the compiler knows its nullability.
I could refine the type into nonnullable to make it statically typed:

A? obj1=get();
A obj2=obj1!;
// obj1 is still nullable A?
2 days ago
On 4/30/2025 1:26 AM, Richard (Rikki) Andrew Cattermole wrote:
> int* ptr; int v = *ptr; // SEGFAULT

That case is caught by DMD if you compile with -O.
2 days ago
A null check should only be done when converting a nullable pointer to a non-nullable one. Doing a runtime null check before doing a dereference check is redundant, because the CPU will do it for you.
2 days ago
On 5/3/25 21:01, Walter Bright wrote:
> A null check should only be done when converting a nullable pointer to a non-nullable one. Doing a runtime null check before doing a dereference check is redundant, because the CPU will do it for you.

No, it will not. It's UB in modern compiler backends, and there are increasingly important targets such as WASM where you can just write through a null pointer without any page protection. It also does not work in real mode, as well as some bare-metal/embedded systems.

Furthermore, there are no null checks when you pass a dereferenced null pointer to a `ref` parameter.

In practice (outside your small DMC compiler backend box), you can only dereference a non-null pointer, so dereferencing a nullable pointer is actually explicitly one of the cases where you convert it to a non-nullable one...

Also, a segfault on some user's machine is a lot less useful than even a stack trace, and being able to collect some crash info in a `scope(failure)` or similar is even more useful. It can reduce a month-long heisenbug hunt into a 15 minute fix.

`assert(0)` in druntime is a similarly frustrating experience.

If there were a flag to enable null checks on any nullable pointer dereference, I would enable it immediately in all my projects.
« First   ‹ Prev
1 2 3 4 5 6