Thread overview
Explicit implicit conversions
February 06

Inspired by this post.

The idea is simple: Extend implicit conversions to all types and values. For built-in types, one can do this: ushort(myUByte) That works because a ubyte can be converted to ushort. In some cases, the static type has to be changed by explicitly requesting a conversion that is intended to be fool-proof. (A cast would allow for narrowing or other unsafe conversions.)

D has many implicit conversions that are “obviously” correct:

  • Numeric types when the range of the operand is fully included in the target type, e.g.:
    • ubyteushortuintulong
    • byteshortintlong
    • ubyteshort
    • ushortint
    • uintlong
    • floatdoublereal
  • Adding const
  • Removing function attributes that make guarantees, e.g. @safe or pure.
  • Derived to base class or interface

The idea is to allow, e.g. Type(expression) evaluate to expression with a static type of Type.

Something like this is needed to aid the compiler in figuring out what you want:

interface I { }
interface J { }

class C : I, J { }
class D : I, J { }

void main()
{
    I[] xs = [new C, new D]; // Error: cannot implicitly convert expression `[new C, new D]` of type `Object[]` to `I[]`
}

One solution: Use cast(I), but cast is not the right tool: It results in null for a failed cast, but we want to express the cast shouldn’t fail, and we want an error should we be mistaken.

auto xs = [I(new C), new D]; // proposed: good, typeof(xs) is `I[]`

Another examples is when you want to control what type is inferred by IFTI:

void f(T)(T x, T y) { pragma(msg, T); }

f(new C, new D); // Error: template `f` is not callable using argument types `!()(C, D)`
f(I(new C), I(new D)); // proposed: good, prints "I"

It could be implemented by lowering to ((Type __result) => __result)(expression).

Of course, that lowering can be provided by a function template:

auto ref R implicitCast(R, T)(auto ref T x) => x;

The main issue with that is that it’s much wordier.

February 06

On Thursday, 6 February 2025 at 02:32:54 UTC, Quirin Schroll wrote:

>

The idea is to allow, e.g. Type(expression) evaluate to expression with a static type of Type.

Unfortunately this syntax conflicts with constructors and static opCall, so it is probably a non-starter.

>

Of course, that lowering can be provided by a function template:

auto ref R implicitCast(R, T)(auto ref T x) => x;

The main issue with that is that it’s much wordier.

Do we expect this to be used often enough that its length will be a big deal?

February 06

On Thursday, 6 February 2025 at 03:03:05 UTC, Paul Backus wrote:

>

On Thursday, 6 February 2025 at 02:32:54 UTC, Quirin Schroll wrote:

>

The idea is to allow, e.g. Type(expression) evaluate to expression with a static type of Type.

Unfortunately this syntax conflicts with constructors and static opCall, so it is probably a non-starter.

>

Of course, that lowering can be provided by a function template:

auto ref R implicitCast(R, T)(auto ref T x) => x;

The main issue with that is that it’s much wordier.

Do we expect this to be used often enough that its length will be a big deal?

The deal is that something that’s that trivial shouldn’t require a workaround, not even an easy one. It would have to be in object.d to be easily used, otherwise I’d rather two-line it with a local variable than two-line it with an import statement.

Really, there’s almost no benefit anymore if you have to import:

import std.conv : implicitCast; // meh
f(x.implicitCast!R);
February 06
On 2/5/2025 6:32 PM, Quirin Schroll wrote:
> The idea is to allow, e.g. `Type(expression)` evaluate to `expression` with a static type of `Type`.

D doesn't do that because:

1. the grammar is ambiguous (confused with function calls)

2. it is not greppable

3. an "is" expression can already do that, and a template can wrap it

February 07

On Thursday, 6 February 2025 at 14:45:43 UTC, Quirin Schroll wrote:

>

The deal is that something that’s that trivial shouldn’t require a workaround, not even an easy one. It would have to be in object.d to be easily used, otherwise I’d rather two-line it with a local variable than two-line it with an import statement.

Really, there’s almost no benefit anymore if you have to import:

import std.conv : implicitCast; // meh
f(x.implicitCast!R);

Where is all this whining about imports when it comes to other simple library functions, like max or abs? It seems to me like "having to import" is something that people love to complain about in DIP threads, but have no trouble with when it comes to actually writing code.

In any case, the primary goal here (and with the analogous proposal for reinterpreting casts) is to help D programmers write correct, readable, and maintainable code, not to minimize the number of lines they have to type.

February 13
On 2/6/2025 6:52 PM, Paul Backus wrote:
> In any case, the primary goal here (and with the analogous proposal for reinterpreting casts) is to help D programmers write correct, readable, and maintainable code, not to minimize the number of lines they have to type.

Spot on.

Consider:
```
const foo = @import("foo");
```
vs
```
import foo;
```

and

```
var x: i32 = 1;
comptime var y: i32 = 1;
```
vs
```
int x = 1;
enum y = 1;
```


https://ziglang.org/documentation/