Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cast(ref T)... as shorthand for *cast(T*)&... #20644

Open
TurkeyMan opened this issue Jan 7, 2025 · 49 comments · May be fixed by #20728
Open

cast(ref T)... as shorthand for *cast(T*)&... #20644

TurkeyMan opened this issue Jan 7, 2025 · 49 comments · May be fixed by #20728

Comments

@TurkeyMan
Copy link
Contributor

TurkeyMan commented Jan 7, 2025

Can we enhance cast expressions to do lvalue thunk's?
This classic pattern *cast(T*)&...; there's a lot of cognitive baggage going on there!
We should be able to write cast(ref T)... instead.

@dkorpel
Copy link
Contributor

dkorpel commented Jan 7, 2025

I think re-interpret casting is ugly by design. cast(ref T) looks like a safe operation.

@TurkeyMan
Copy link
Contributor Author

We'll it's obviously a @safe violation... so it's not :P
I'm very tired of ugly thunk's. I don't think this is something that affects very many people, since most people don't do this much anyway.

@dkorpel
Copy link
Contributor

dkorpel commented Jan 7, 2025

Imagine code looking something like this:

import std.bitmanip;
void main()
{
    read!ushort(cast(ubyte[]) [0xAA, 0xBC, 0xDE]);
}

Which then gets changed to:

-    read!ushort(cast(ubyte[]) [0xAA, 0xBC, 0xDE]);
+    read!ushort(cast(ref ubyte[]) [0xAA, 0xBC, 0xDE]);

Is it obvious we're doing a re-interpret cast of an int[] suddenly? To me, it looks like it promotes the rvalue to an lvalue. You have to specifically know the meaning we gave to cast(ref). In contrast, nobody accidentally writes:

-    read!ushort(cast(ubyte[]) [0xAA, 0xBC, 0xDE]);
+    read!ushort(*cast(ubyte[]*) &[0xAA, 0xBC, 0xDE]);

since most people don't do this much anyway

As they shouldn't, but nice-looking syntax makes it much easier to do accidentally or haphazardly.

@TurkeyMan
Copy link
Contributor Author

TurkeyMan commented Jan 7, 2025

Imagine code looking something like this:

import std.bitmanip;
void main()
{
    read!ushort(cast(ubyte[]) [0xAA, 0xBC, 0xDE]);
}

Ummm, no that's not what this is about. Infact, this is invalid in the case you show.

Which then gets changed to:

-    read!ushort(cast(ubyte[]) [0xAA, 0xBC, 0xDE]);
+    read!ushort(cast(ref ubyte[]) [0xAA, 0xBC, 0xDE]);

Is it obvious we're doing a re-interpret cast of an int[] suddenly? To me, it looks like it promotes the rvalue to an lvalue. You have to specifically know the meaning we gave to cast(ref). In contrast, nobody accidentally writes:

I'm not sure what you're trying to demonstrate here, it just looks invalid to me.

-    read!ushort(cast(ubyte[]) [0xAA, 0xBC, 0xDE]);
+    read!ushort(*cast(ubyte[]*) &[0xAA, 0xBC, 0xDE]);

This is invalid code, and so the shorthand I proposed is equally invalid for the same reason; you can't cast an rvalue to an lvalue.

since most people don't do this much anyway

As they shouldn't, but nice-looking syntax makes it much easier to do accidentally or haphazardly.

Aside from the conceptual errors above, which makes me suspect you might not have understood what it is I'm proposing, or why, but generally I don't buy into this point either...
I wouldn't describe cast(ref X) as 'nice looking', but it is clear; and that's the point.
Reinterpret casts are hard to read, and consequently error prone. That matters even more for an unsafe operation.
There are three pieces of relevant syntax to perform a reinterpret cast; a &, and 2x *'s required to be written in the right places distributed throughout the expression. As soon as the type expression gets even a little bit long, it gets hard to see and reason about.

I firmly think writing ref clearly inside the cast is much easier to see and understand.

It has nothing to do with making it 'nice-looking', it's about making a complex and difficult to reason about expression reasonable.

The current situation with the * .. * .. &, it's easy to miss one of these symbols, and it's also easy to mis-type and/or forget to write one... and the thing is; failure to write it correctly doesn't necessarily lead to a compile error; it's roughly equally likely to lead to unintended behaviour.
The pattern as fundamentally dangerous, and I'd like to see it be more explicit, clearly state the intent, and therefore less error-prone.

@dkorpel
Copy link
Contributor

dkorpel commented Jan 8, 2025

I know that my example is not what you are trying to do. Let me clarify.

I said "unsafe constructs should look unsafe"
You said "cast(ref T) is obviously unsafe"
Then I tried to make a point that cast(ref T) only looks unsafe when you already know what it is specified to do.

I attempted to illustrate this point by giving a hypothetical scenario of someone trying to pass an rvalue to a ref parameter, getting an error, and trying out cast(ref T) thinking it would simply convert the argument to an lvalue, and accidentally ending up reinterpret casting instead. Now you're right that cast(ref T) in your proposal doesn't actually create an lvalue, so the compiler would raise an error before the programmer sees the bug at runtime. I forgot that, and evidently it's not the best example.

I wouldn't describe cast(ref X) as 'nice looking', but it is clear; and that's the point.

My point is, it's not clear. Let me try a different angle: I gave this ChatGPT this promp:

Someone proposed adding a new construct, cast(ref T), to the D programming language. What do you suspect it means?

And it answered:

Hypothesis: cast(ref T) could mean casting a value or variable to a reference to type T, ensuring that the resulting value is treated as a reference (or lvalue) to a variable of type T.

And it gave this code example:

int x = 10;
auto y = cast(ref int) x; // `y` is now a reference to `x`
y = 20;                  // Modifies `x` directly

And that would also be my first impression of the syntax.

But even if the proposed syntax was reinterpret_cast(T), I suspect Walter will disapprove of the idea, because there's precedence of feature request that would improve the aesthetics of things that are ugly by design.

  • version(A | B) algebra
  • @trusted blocks
  • Casting function attributes like pure and @nogc

He's been against all of these. However, if you go through with this, it's ultimately up to him and Atila to evaluate and decide.

@dkorpel
Copy link
Contributor

dkorpel commented Jan 8, 2025

The current situation with the * .. * .. &, it's easy to miss one of these symbols, and it's also easy to mis-type and/or forget to write one... and the thing is; failure to write it correctly doesn't necessarily lead to a compile error; it's roughly equally likely to lead to unintended behaviour.

If you do this a lot, you can always define your own helper function.

T reinterpret_cast(T, V)(V value) => *cast(T*)& value;

float x = reinterpret_cast!float(0x3F800000);

I know I know, you don't want call instructions, and you don't want the debugger to step into trivial functions like that, but I'm starting to see a pattern here: perhaps we just need to make pragma(inline, true) work like you want to, and then we wouldn't need to implement simple functions as new language features in the compiler.

@TurkeyMan
Copy link
Contributor Author

TurkeyMan commented Jan 8, 2025

My point is, it's not clear. Let me try a different angle: I gave this ChatGPT this promp:

Someone proposed adding a new construct, cast(ref T), to the D programming language. What do you suspect it means?

And it answered:

Hypothesis: cast(ref T) could mean casting a value or variable to a reference to type T, ensuring that the resulting value is treated as a reference (or lvalue) to a variable of type T.

This is exactly correct! That's why I suggest that syntax, because it seems logical and self-evident.

And it gave this code example:

int x = 10;
auto y = cast(ref int) x; // `y` is now a reference to `x`
y = 20;                  // Modifies `x` directly

And that would also be my first impression of the syntax.

The point where GPT failed is not noticing that y was not declared ref, so the initialisation of y was a copy... but the right-hand side of the initialisation was indeed a ref to the original x.
It also doesn't help that ChatGPT is generally super-bad at dlang ;)

@dkorpel
Copy link
Contributor

dkorpel commented Jan 8, 2025

This is exactly correct! That's why I suggest that syntax, because it seems logical and self-evident.

I gotta admit that it's technically correct, but the focus is clearly on 'ensuring that the resulting value is treated as a reference' (which it fails to do in the code example) and not on re-interpret casting. When I ask about type mismatches, it says:

Given D's emphasis on type safety and avoiding undefined behavior, the compiler would most likely:

Reject cast(ref int) floatVar for type mismatch unless explicitly allowed through unchecked reinterpretation (e.g., cast() without type safety checks).

Of course I don't want to give too much weight to an LLM's opinion, but if the syntax was truly self-evident, there wouldn't even be a discussion here. It doesn't help that cast() already has so many gotcha's / inconsistencies depending on type, CTFE/runtime, and literal value/variable.

@TurkeyMan
Copy link
Contributor Author

Maybe cast needs a do-over like C++ did 15 years ago...
I do feel that D's cast is a pretty serious language weakness, it doesn't distinguish safe or unsafe casts, and doesn't help you do the right thing in any cases at all.

@dkorpel
Copy link
Contributor

dkorpel commented Jan 8, 2025

I do feel that D's cast is a pretty serious language weakness

I agree. From the top of my head:

  • Defining opCast on a struct locks you out of the default struct cast behavior (Fix Bugzilla 22293: opCast!bool for Nullable phobos#9039)
  • Casting arrays sometimes casts the elements keeping length, and sometimes reinterpret casts the array bytes (depends on static/dynamic, ctfe/runtime) and expands/shrinks accordingly
  • Empty slices cast to bool can be either true or false

etc.

@ntrel
Copy link
Contributor

ntrel commented Jan 8, 2025

As there's no opCast for a pointer, the original pattern is more flexible.

See also #18852 which seems the same. As that says, the feature would allow some @safe reinterpret casts as lvalues, though we might want to see good use cases for those first.

@TurkeyMan
Copy link
Contributor Author

As there's no opCast for a pointer, the original pattern is more flexible.

I'm not sure what you are saying here?
If you're saying this suggestion subverts opCast, that is precisely the point. This syntax will never call opCast.

See also #18852 which seems the same. As that says, the feature would allow some @safe reinterpret casts as lvalues, though we might want to see good use cases for those first.

Nice to know that Andrei had the same thought.

@WalterBright
Copy link
Member

Consider:

t = *cast(T*)&s;
t = cast(ref T) s;

I have a hard time finding something wrong with it. Looking at @dkorpel's reinterpret_cast, that has a weakness because it always returns an rvalue, and the *cast& leaves an lvalue as an lvalue.

In fact, cast(ref T)e should probably be disallowed if e is an rvalue.

Certainly, cast to ref should always be marked unsafe.

I totally agree with @dkorpel saying that doing nasty things should look nasty. But every case should be evaluated on its individual merits. I admit I'm kinda seduced by this one.

@TurkeyMan
Copy link
Contributor Author

Consider:

t = *cast(T*)&s;
t = cast(ref T) s;

I have a hard time finding something wrong with it. Looking at @dkorpel's reinterpret_cast, that has a weakness because it always returns an rvalue, and the *cast& leaves an lvalue as an lvalue.

In fact, cast(ref T)e should probably be disallowed if e is an rvalue.

Absolutely.

Certainly, cast to ref should always be marked unsafe.

Pretty sure any such thunk is unsafe.

I totally agree with @dkorpel saying that doing nasty things should look nasty. But every case should be evaluated on its individual merits. I admit I'm kinda seduced by this one.

Huzzah. I honestly didn't see that coming!

@dkorpel
Copy link
Contributor

dkorpel commented Jan 15, 2025

Looking at @dkorpel's reinterpret_cast, that has a weakness because it always returns an rvalue, and the *cast& leaves an lvalue as an lvalue.

That's easily fixed. I made my example with rvalues, but you can also make it work on both with auto ref, or lvalues only with ref:

-T reinterpret_cast(T, V)(V value) => *cast(T*) &value;
+ref T reinterpret_cast(T, V)(ref V value) => *cast(T*) &value;

@TurkeyMan
Copy link
Contributor Author

Oh don't say that, Walter's mildly persuaded. That never happens!

@WalterBright
Copy link
Member

That never happens!

They don't call me Dr No for nothing!

@WalterBright
Copy link
Member

@dkorpel ok

WalterBright added a commit to WalterBright/dmd that referenced this issue Jan 18, 2025
@JohanEngelen
Copy link
Contributor

this needs explicit definition (and a test case) of what auto y = cast(ref T) x; means for the type of y.

I think the pattern is: auto a = cast(<A>) b; where the type of a becomes <A>. For example:

auto a = cast (int*) b; --> int* a = cast(int*) b;
auto a = cast (shared int*) b; --> shared int* a = cast(int*) b;
auto a = cast (immutable) b; --> immutable a = cast(immutable) b;
etc.

So that would mean:
auto y = cast(ref T) x; ---> ref T y = cast(ref T) x;
But I think this is not true (because the rewrite *cast(T*)& dereferences the pointer back to T type)
auto y = cast(ref T) x; ---> T y = cast(ref T) x;
this to me is very confusing and error prone in code review.

Therefore, I agree with Dennis that the chosen syntax cast(ref T) is wrong. Consider the meaning of cast(T*) and how it is not at all what cast(ref T) does.

WalterBright added a commit to WalterBright/dmd that referenced this issue Jan 18, 2025
@WalterBright
Copy link
Member

Consider the meaning of cast(T*) and how it is not at all what cast(ref T) does.

You're quite right in that cast(ref T)e does not convert e to an indirect reference.

But that wouldn't make much sense anyway, as I can't think of a use for it that makes any sense. Or maybe my imagination is faulty :-/

I think of it differently, as in "refer to e as if it were of type T", and then it makes perfect sense.

When Manu first proposed it, my first reaction was negative, too. But it grew on me. It makes D code nicer looking, and that is very much a goal of D. When I write expressions like *cast(T*)&e I always have to check it closely to make sure I did it right. Even writing that expression in this post caused me to slow down and type it in carefully. The same when I review such code. with cast(ref T)e it's so much easier to see it's right.

It's ok if we overload the meaning of a keyword now and then. This use quickly becomes very natural.

WalterBright added a commit to WalterBright/dmd that referenced this issue Jan 18, 2025
WalterBright added a commit to WalterBright/dmd that referenced this issue Jan 18, 2025
WalterBright added a commit to WalterBright/dmd that referenced this issue Jan 18, 2025
@JohanEngelen
Copy link
Contributor

It's ok if we overload the meaning of a keyword now and then. This use quickly becomes very natural.

I disagree. This is not going to be something you see every day. I'm convinced most people will read the cast as casting to ref. Like exactly what it says it does, but then doesn't...

It shouldn't be too hard to come up with a better syntax.

@Herringway
Copy link
Contributor

It's ok if we overload the meaning of a keyword now and then. This use quickly becomes very natural.

If the benefit to this syntax sugar was more than just saving a few keystrokes, I'd be more inclined to agree with this.

@TurkeyMan
Copy link
Contributor Author

It's ok if we overload the meaning of a keyword now and then. This use quickly becomes very natural.

If the benefit to this syntax sugar was more than just saving a few keystrokes, I'd be more inclined to agree with this.

This doesn't mitigate a few key strokes, it mitigates confusion and error.
Thunk's are hard to read, and hard to get right especially when T is some kind of pointer; in those cases you need to carefully count the number of star's making sure to add one to have confidence you got it right, and getting it wrong will usually fail silently.

@TurkeyMan
Copy link
Contributor Author

It's ok if we overload the meaning of a keyword now and then. This use quickly becomes very natural.

I disagree. This is not going to be something you see every day. I'm convinced most people will read the cast as casting to ref. Like exactly what it says it does, but then doesn't...

I'm not sure what you mean, that's EXACTLY what it does. I think the syntax couldn't be clearer or more precise.

It shouldn't be too hard to come up with a better syntax.

It's perfect, no better syntax could possibly exist, it couldn't be more precisely expressing what it does.

@TurkeyMan
Copy link
Contributor Author

Consider the meaning of cast(T*) and how it is not at all what cast(ref T) does.

You're quite right in that cast(ref T)e does not convert e to an indirect reference.

I'm not sure what you mean here, that's exactly what it says, and that's exactly what it should do.

cast(ref uint)myFloat = 0x3F800000;

No reason this expression shouldn't work just how it looks like it should.

I don't think the meaning is overloaded in any way, I think it's precisely that it looks like it means.

@Herringway
Copy link
Contributor

It's ok if we overload the meaning of a keyword now and then. This use quickly becomes very natural.

If the benefit to this syntax sugar was more than just saving a few keystrokes, I'd be more inclined to agree with this.

This doesn't mitigate a few key strokes, it mitigates confusion and error. Thunk's are hard to read, and hard to get right especially when T is some kind of pointer; in those cases you need to carefully count the number of star's making sure to add one to have confidence you got it right, and getting it wrong will usually fail silently.

Now you just have to carefully count the number of asterisks and subtract one instead of adding one.

@TurkeyMan
Copy link
Contributor Author

It's ok if we overload the meaning of a keyword now and then. This use quickly becomes very natural.

If the benefit to this syntax sugar was more than just saving a few keystrokes, I'd be more inclined to agree with this.

This doesn't mitigate a few key strokes, it mitigates confusion and error. Thunk's are hard to read, and hard to get right especially when T is some kind of pointer; in those cases you need to carefully count the number of star's making sure to add one to have confidence you got it right, and getting it wrong will usually fail silently.

Now you just have to carefully count the number of asterisks and subtract one instead of adding one.

Why would you subtract one? The T you write is exactly the T you get. It might even be an alias that was determined elsewhere, and the alias works as written un-modified.

@Herringway
Copy link
Contributor

It's ok if we overload the meaning of a keyword now and then. This use quickly becomes very natural.

If the benefit to this syntax sugar was more than just saving a few keystrokes, I'd be more inclined to agree with this.

This doesn't mitigate a few key strokes, it mitigates confusion and error. Thunk's are hard to read, and hard to get right especially when T is some kind of pointer; in those cases you need to carefully count the number of star's making sure to add one to have confidence you got it right, and getting it wrong will usually fail silently.

Now you just have to carefully count the number of asterisks and subtract one instead of adding one.

Why would you subtract one? The T you write is exactly the T you get. It might even be an alias that was determined elsewhere, and the alias works as written un-modified.

That's exactly why you need to subtract one. If you dereference a T*, you get a T. For example, assuming var is an void***, wouldn't cast(ref T**)var be the equivalent of *cast(T***)var? cast(ref T)var would be a mistake. You still need to count your asterisks.

@TurkeyMan
Copy link
Contributor Author

Why would you subtract one? The T you write is exactly the T you get. It might even be an alias that was determined elsewhere, and the alias works as written un-modified.

That's exactly why you need to subtract one. If you dereference a T*, you get a T. For example, assuming var is an void***, wouldn't cast(ref T**)var be the equivalent of *cast(T***)var? cast(ref T)var would be a mistake. You still need to count your asterisks.

Yeah no, I don't know how you're managing to overcomplicate this, but what you say just appears to demonstrates a deep misunderstanding of a thunk. No need to overcomplicate your conception of this, the most obvious and logical interpretation of the syntax is exactly what it should mean.

@TurkeyMan
Copy link
Contributor Author

this needs explicit definition (and a test case) of what auto y = cast(ref T) x; means for the type of y.

Exactly what it means if you didn't type ref. I propose absolutely no special casing.

I think the pattern is: auto a = cast(<A>) b; where the type of a becomes <A>. For example:

auto a = cast (int*) b; --> int* a = cast(int*) b;
auto a = cast (shared int*) b; --> shared int* a = cast(int*) b;
auto a = cast (immutable) b; --> immutable a = cast(immutable) b;
etc.

So that would mean:
auto y = cast(ref T) x; ---> ref T y = cast(ref T) x;
But I think this is not true (because the rewrite *cast(T*)& dereferences the pointer back to T type)
auto y = cast(ref T) x; ---> T y = cast(ref T) x;
this to me is very confusing and error prone in code review.

Therefore, I agree with Dennis that the chosen syntax cast(ref T) is wrong. Consider the meaning of cast(T*) and how it is not at all what cast(ref T) does.

This doesn't mitigate a few key strokes, it mitigates confusion and error.
Thunk's are hard to read, and hard to get right especially when T is some kind of pointer; in those cases you need to carefully count the number of star's making sure to add one to have confidence you got it right, and getting it wrong will usually fail silently.

Sorry, I reread and I follow what you're saying now. You note that the traditional thunk adds an additional * to complement the defence. That's not necessary with this syntax; hence the "no need to add an extra *". You just handle the type your want, and no need to manipulate it. If may be an alias with no extra grammar required.
Frankly, thus decision is demonstrating the exact reason this syntax is necessary.

@WalterBright
Copy link
Member

I'm not sure what you mean here

It means:

cast(ref T)e

is literally replaced with:

*cast(T*)&e

in the implementation.

@WalterBright
Copy link
Member

this needs explicit definition (and a test case) of what auto y = cast(ref T) x; means for the type of y.

It explicitly means:

auto y = *cast(T*)&x;

Nothing more, nothing less. It is a rewrite of the AST prior to semantic analysis. I.e. it is nothing more than syntactic sugar. No further test cases are needed.

@TurkeyMan
Copy link
Contributor Author

this needs explicit definition (and a test case) of what auto y = cast(ref T) x; means for the type of y.

It explicitly means:

auto y = *cast(T*)&x;

Nothing more, nothing less. It is a rewrite of the AST prior to semantic analysis. I.e. it is nothing more than syntactic sugar. No further test cases are needed.

Yes, exactly this.
The implications for type inference with respect to auto are identical.

I presume lvalue assignment is also possible, just as: *cast(uint*)&myFloat = 0x3F800000 would naturally work?
Maybe add that unit test to confirm?

@WalterBright
Copy link
Member

Of course it works, because the AST for cast(ref uint)f = 0x3F800000; is literally created as *cast(uint*)&f = 0x3F800000;

I mean literally literally. Look at the implementation! It's just 4 lines. Literally! Literally! cast(ref uint)f never appears in the AST. Nothing more needs to be defined or tested or pondered. It's just sugar.

@TurkeyMan
Copy link
Contributor Author

TurkeyMan commented Jan 19, 2025

Nothing more needs to be defined or tested or pondered. It's just sugar.

Yes, exactly. It's perfect!
👌
🙏

@JohanEngelen
Copy link
Contributor

this needs explicit definition (and a test case) of what auto y = cast(ref T) x; means for the type of y.

It explicitly means:

auto y = *cast(T*)&x;

It is of course of no use writing that in the PR. You have to put it in the spec. And test it.

Nothing more, nothing less. It is a rewrite of the AST prior to semantic analysis. I.e. it is nothing more than syntactic sugar. No further test cases are needed.

the point is: test what auto y = cast(ref T) x should be doing to the type of y (or perhaps just disallow it / warning); it is highly non-obvious. Why would something being syntactic sugar or not even matter for testing it or not??
Again and again stuff is being added without any significant testing, thus more bugs to come in the future...

@pbackus
Copy link
Contributor

pbackus commented Jan 19, 2025

If we want to make reinterpreting casts less confusing, it seems to me like the obvious way to do it is to add a reinterpretCast!T helper function to Phobos or druntime.

pragma(inline, true)
ref T reinterpretCast(T, U)(ref U u)
{
    return *cast(T*) &u;
}

Both *cast(T*) &foo and cast(ref T) foo are equally cryptic and inscrutable to unfamiliar readers. The fact that the second one is slightly easier to type is not a very compelling motivation for adding new syntax.

Keep in mind that we've had complaints in the past about "syntax churn" from maintainers of tooling projects, like libdparse and sdfmt. So even a purely-additive syntactic change like this one is not 100% free.

@WalterBright
Copy link
Member

It is of course of no use writing that in the PR. You have to put it in the spec.

https://github.com/dlang/dlang.org/pull/4164/files

And test it.

https://github.com/dlang/dmd/pull/20728/files

Again and again stuff is being added without any significant testing, thus more bugs to come in the future...

*cast(T*)&u has been around since the dawn of D, and is used a lot. All that needs to be tested is checking the sugar. There is no semantic change to be tested.

it is highly non-obvious.

Think of cast(ref T) x as "refer to x as if it was of type T".

@WalterBright
Copy link
Member

cryptic and inscrutable to unfamiliar readers

Isn't that true with about everything in a programming language? I look at other languages, and see plenty of unfamiliar constructs.

we've had complaints in the past about "syntax churn" from maintainers of tooling projects

And I am sensitive to that. That's why I expended a fair amount of effort making the lexer and parser each be standalone modules, so other tools can incorporate it without needing to invent their own.

The fact that the second one is slightly easier to type is not a very compelling motivation for adding new syntax.

Indeed, that is always a difficult judgement call to make. A major goal of D is to make it pleasant to read (but not necessarily easier to type). When reinterpret_cast was added to C++, I immediately thought it was ugly and time hasn't improved it. (What it did wasn't immediately obvious to unfamiliar readers, either.) What I like about cast(ref T) is it is easy on the eyes.

@WalterBright
Copy link
Member

Speaking of "syntax churn", please consider these other requests for syntactic sugar:

#20624
#20645
#20658

(And please judge them on those pages, not here!)

@pbackus
Copy link
Contributor

pbackus commented Jan 19, 2025

When reinterpret_cast was added to C++, I immediately thought it was ugly and time hasn't improved it. (What it did wasn't immediately obvious to unfamiliar readers, either.) What I like about cast(ref T) is it is easy on the eyes.

I agree that reinterpretCast is not going to win any awards for beauty, but it does have the advantage of having a name that can easily be googled or searched for in documentation.

Also, if your objection is mainly to the name reinterpretCast rather than to the general idea of using a helper function, we can always choose a shorter, less ugly name. Some ideas:

  • x.typePaint!int
  • x.interpretAs!int
  • x.as!int (maybe this one is too terse?)

@JohanEngelen
Copy link
Contributor

It is of course of no use writing that in the PR. You have to put it in the spec.

https://github.com/dlang/dlang.org/pull/4164/files

And test it.

https://github.com/dlang/dmd/pull/20728/files

Again and again stuff is being added without any significant testing, thus more bugs to come in the future...

*cast(T*)&u has been around since the dawn of D, and is used a lot. All that needs to be tested is checking the sugar. There is no semantic change to be tested.

Fine. The point still stands (unsurprisingly): nowhere do you test that cast(ref T) indeed exactly rewrites as *cast(T*)&u.

@Herringway
Copy link
Contributor

Speaking of "syntax churn", please consider these other requests for syntactic sugar:

#20624 #20645 #20658

(And please judge them on those pages, not here!)

What I'm most concerned about between this language change and the ones linked is that none of them are going through the DIP process. Perhaps not surprising, given that these proposals taking the direct route have gotten more useful feedback in just a few weeks than the entire new process has in nearly a year. They might even be accepted and implemented! You just can't get that level of attention from the DIP process, even right back when that was promised.

@WalterBright
Copy link
Member

indeed exactly rewrites as

The test is that the same results result. Frankly, none of the compiler tests test AST tree topology. If you would like to add a specific test, please describe it.

@WalterBright
Copy link
Member

What I'm most concerned about between this language change and the ones linked is that none of them are going through the DIP process

https://www.digitalmars.com/d/archives/digitalmars/dip/development/First_Draft_cast_ref_T_..._as_shorthand_for_cast_T_..._497.html

@WalterBright
Copy link
Member

the advantage of having a name that can easily be googled or searched for in documentation.

Ironic that you mention that! The reason for the very existence of the cast keyword is because the C cast is not searchable. The cast(ref syntax is also searchable, as one could search for "cast ref" or simply "cast" because the documentation for cast ref is under "cast" in the spec.

@Herringway
Copy link
Contributor

What I'm most concerned about between this language change and the ones linked is that none of them are going through the DIP process

https://www.digitalmars.com/d/archives/digitalmars/dip/development/First_Draft_cast_ref_T_..._as_shorthand_for_cast_T_..._497.html

I should have been more specific. I meant the normal DIP process, not the special Walter DIP process. The normal DIP process starts with posting a proposal on the DIP ideas forum and getting none of the promised feedback from you and/or Atila. It's been rather discouraging for us peasants.

@TurkeyMan
Copy link
Contributor Author

the point is: test what auto y = cast(ref T) x should be doing to the type of y (or perhaps just disallow it / warning); it is highly non-obvious.

I don't understand, why do you think it's non-obvious? What would you imagine that's different from what it does?

You could have:

void f(ref T x)
{
  auto y = x;
}

I think everyone should understand what this does, and what seems most obvious to me, is that this syntax is exactly this same thing.
There's no special behaviour or meaning. No surprises that I can see.

@WalterBright
Copy link
Member

@Herringway I have not proposed any of these ideas. They all came from others. Some took umbrage at being asked to write a DIP. Perhaps you or someone else could help them write the DIP? In the meantime, we have an implementation to try out the idea.

@dkorpel dkorpel linked a pull request Jan 20, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants