Literal types
Quirin Schroll
qs.il.paperinik at gmail.com
Mon Jul 7 15:54:05 UTC 2025
On Friday, 4 July 2025 at 16:16:05 UTC, IchorDev wrote:
> On Wednesday, 25 June 2025 at 17:56:09 UTC, Quirin Schroll
> wrote:
>> I don’t know if I’m getting this 100% correct, but saying `[1,
>> 2]` is an `int[]` isn’t the full story since it’s more like an
>> `int[2]` that “decays” to a `int[]` (which usually ends up on
>> the heap) unless you “catch” it early enough. Catching such a
>> literal isn’t too difficult, the `staticArray` function does
>> it.
>
> There’s a really clever idea in here somewhere but I think you
> didn’t quite hit the nail on the head. The one-way implicit
> casting of literals often gets in my way…
> ```
> auto x = 1;
> ushort y = x; //ERR: `1` is an `int`… ugh
> ushort z = 1; //OK: `1` is actually `ushort` now
> auto a = [1,2];
> ubyte[] b = a.dup(); //ERR: `[1,2]` is an `int[]`… ugh
> ubyte[] c = [1,2]; //OK: on second thought, `[1,2]` can totally
> be a `ubyte[]`…
> ```
> One thing that could mitigate this is having explicit suffixes
> for char, byte, and short.
While I’d like to have those for symmetry and consistency,
`short(1)` actually works.
But that’s not your actual issue here. You can’t initialize a
smaller integer type from a _run-time_ value of a bigger integral
type. Plain literals have a default decay type which isn’t
maximally narrow, but `int`. D inherited that from C, so that
what looks like C and compiles, acts like C, for the most part.
The importance of this principle has declined over the years, but
for very basic stuff, it’s still relevant.
There’s virtually no difference between `auto x = short(1)` and
`short x = 1`.
> But what would be really nice is if the language could keep
> track of when a variable’s type was inferred from nothing but a
> literal, and then allow the usual type conversion restrictions
> to be bent a little based on how the literal could’ve been
> interpreted as a different type.
You can’t easily do that with run-time values.
> It’s a similar idea to https://dlang.org/spec/type.html#vrp
> which never accounts for variables that have just been
> unconditionally assigned a literal value.
That’s because VRP is an expression feature. It doesn’t interact
with statements. I don’t know how VRP is implemented, but it
treats integral types as (unions of ) intervals. That’s probably
a lot easier to do within expressions than over statements where
you’d have to store the intervals with variables indefinitely.
> Oddly, the idea that I described already exists for enums:
> ```d
> enum int[] x = [1,2];
> ushort[] y = x; //no error?!
> ushort[2] z = x; //still no error!!?!
> ```
> Hopefully that makes sense.
It makes sense because an `enum` isn’t a run-time value, but
quite close to a named literal with explicit decay type. (By the
latter, I mean that `enum short h = 10` has type `short` not
`int`, but behaves like the literal `10`; the same is true for
`enum short[] hs = [1,2]` which has type `short[]` but you can
infer its length and assign it to a `ubyte[]`.)
It also works with `immutable` values with compile-time values:
```d
immutable int v = 10;
immutable int[] vs = [v,v];
void main()
{
byte w = v; // okay
byte[] ws = vs; // error
}
```
The slice thing doesn’t work with `immutable` because slices are
somewhat reference types. It works with `enum` because `x` in
your example is a literal. `ws = vs` wouldn’t ever allocate, but
`y = x` actually will (unless optimized out) in the sense that
it’s not `@nogc`.
> TL;DR: Let variables that are initialised/unconditionally
> assigned literals follow the same implicit cast rules as the
> literal, meaning that `int x=1; byte y=x;` compiles since `1`
> can be inferred as a byte.
It won’t happen because it’s hard to implement, hard to reason
through in general, and brings little value in return. In many
cases, restructuring the code works.
More information about the dip.ideas
mailing list