Open WhiteBlackGoose opened 2 years ago
We have discussed this on the server, but just to have it documented here too.
If we can solve the compatibility issues, I'd be all for an explicit option type.
C# Nullables are metadata, attributes that doesn't offer any guarentee.
In theory, this is also true for the type system, but making these attribute lie is 'harder': casting is checked, and unsafe cast are 'hidden' behind Unsafe
api.
On the other hand, it's very easy to make C# nullables lies, an operator !
exists just for this purpose.
This is because C# nullables cannot cover every scenario.
On top of what @WhiteBlackGoose propose
Verify
, and Trust
.Verify
would be the default mode, it ensure every object annotated as non-nullable of a C# API by inserting runtime nullchecks when doing interop with C#.
This would allow draco references to stay "safe" from badly annotated C# APIs.
Trust
would be an opt-in mode. People seeking performance would opt-in this mode and no check would be performed, a badly annotated type reference will leak a null into draco code, may throw a nullref later, or pass this nullref back to C#, from a draco API declaring it give non null reference type.
Attributes should be provided to temporarly switch the behavior.
I don't think trust is necessary, since badly annotated code can be considered as a bug, we could say we have nothing to do on our side.
But since nullables attributes can easily lie, if Draco heavily rely on a lot of badly annotated C# code, Draco non-nullables types will look as unsafe and non reliables as C# ones.
The Verify
mode allow the same level of strictness as the type system allow, but at runtime, since it's not possible to do better.
The key here is to keep C# interop both backward and forward (that is, use C# API from Fresh and use Fresh API from C#) but make sure to keep the code safe and consistent.
C#'s approach
C# has a separate type for value type nullables, but has annotations for reference types. It creates some inconsistent behaviour, to be precise:
Inconsistent deconstructure
Inconsistent generics
Consider two methods:
Let's substitute
T = int
:See sharplab.
Erasable nullables
Universal type
Instead of having annotations and a type for value types, I suggest having a single type. To avoid confusion, let's call it Null for now:
C# API from Fresh
C#:
Fresh:
Fresh API from C
It's exactly the same in the opposite direction. That's how it will be alised runtime-wise: present NRTs as Null<>, present Nullable<> as Null<>.
Null exists only at compile time
Runtime-wise looks like
Unresolved
As type argument
What about cases when there's no way to erase it? E. g. when used as a type argument:
Or,
Should we unconditionally materialize Null<> when used as a type argument?
Then
typeof(Null<>)
will work as expected.Nested nullables
E. g.
Should all but the innermost materialize into
Null
? Should we prohibit this behaviour?Relations with Options
Should we have explicit Option type if we manage to design a consistent nullable?