- New primitive types (
nint
andnuint
)
Proposal: #435
The current spec states that nint
and nuint
are reserved keywords in a type context but there
are contexts, like qualified names, that allow types but are not type contexts. Since nint
and
nuint
have static members, we want users to be able to access them, so they should be allowed
in qualified names.
A follow-up question is what to do about existing members on the underlying IntPtr
type. There are some members, like Add and Subtract, which may be misleading because
they are not equivalent to +
and -
due to over/underflow checking.
There are two views here. On the one hand, the proposal to use IntPtr means that
some implementation details for IntPtr will inevitably leak through. Since users
will sometimes want to use an IntPtr as an nuint
to do pointer arithmetic, any
methods on IntPtr may be useful and this would just be making barriers.
On the other hand, nint
is being added as a type for conceptually different reasons. It
shouldn't be assumed that everything that applies to IntPtr should be applicable to nint
.
There's a follow-up question as whether we would exclude everything on IntPtr except for an
include list, or include everything except for an exclude list. These may seem very different,
but after more thought it's likely they're very similar. Since the framework would probably very
rarely add new members to IntPtr after the language change (like with System.Int32), what we pick
now is probably what will be present for a very long time, and anything new would probably be
done with consultation from the language team.
Our tentative conclusion is that we should exclude some members. The following are questionable:
- Zero (use the
0
literal) - Size (use sizeof)
- Add, Subtract (part of the point of this type is to use "proper" C# arithmetic)
- ToInt32, ToInt64, ToPointer (use the language-defined conversions)
Unfortunately, IntPtr is "signed" but is often used to represent pointers, which are unsigned
according to framework guidelines. In the current design, IntPtr has an identity conversion to
nint
, which could cause the unfortunate scenario
IntPtr M(IntPtr p)
{
nint x = p;
x >> 1;
return x;
}
This would use signed >>
, which is probably incorrect if the value is assumed to be an unsigned
pointer. This is not currently a problem with IntPtr because IntPtr does not define any bitwise
operators.
Unfortunately, keeping an identity conversion is an important part of compatibility. We would
like to support users who are currently using IntPtr, but would like to use nint
, to go from
IntPtr[]
to nint[]
. Without an identity conversion, we don't see how this would be possible
in the language today.
Adding an entirely new concept in the language to support this without identity conversions is probably not worth it. Unless we can find a simple improvement to the design, we will probably have to accept this behavior.
Since IntPtr and nint are the same CLR underlying type, they will be regarded as having the same
signature. The compiler is able to see the difference. Should we regard them as identical for the
purposes of OHI? This is similar to scenarios like tuple names, which are represented as
attributes and thus not visible as part of the CLR type. However, in OHI we provide an error if
tuple names change in an override because this could be the source of a bug (e.g., if the names
were accidentally swapped). In this case we think IntPtr and nint
are similar enough that we
should regard the OHI behavior similar to dynamic
/object
, where it is allowed.
For use as an enum underlying type, we think nint
/nuint
should be allowed as long as it's not
too much implementation work. The legal values would be the same as the valid constants for the
underlying.
For sizeof, we think it should be supported in unsafe code, just like sizeof(IntPtr)
, but with
no special behavior in safe code.