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:
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:
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.