Skip to content

Add std.conv.bitCast#10651

Merged
LightBender merged 2 commits intodlang:masterfrom
pbackus:type-paint
Feb 28, 2025
Merged

Add std.conv.bitCast#10651
LightBender merged 2 commits intodlang:masterfrom
pbackus:type-paint

Conversation

@pbackus
Copy link
Contributor

@pbackus pbackus commented Feb 27, 2025

This helper function is a more readable alternative to the traditional pointer-cast-based syntax for reinterpreting casts.

The name bitCast is used for this operation in several other languages, including C++ [1], Swift [2], C# [3], Zig [4], and LLVM IR [5].

[1] https://en.cppreference.com/w/cpp/numeric/bit_cast
[2] https://developer.apple.com/documentation/swift/unsafebitcast(_:to:)
[3] https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.unsafe.bitcast
[4] https://ziglang.org/documentation/0.10.0/#bitCast
[5] https://llvm.org/docs/LangRef.html#bitcast-to-instruction


Alternative to dlang/dmd#20728

For related discussion, see dlang/dmd#20644

@dlang-bot
Copy link
Contributor

Thanks for your pull request and interest in making D better, @pbackus! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please verify that your PR follows this checklist:

  • My PR is fully covered with tests (you can see the coverage diff by visiting the details link of the codecov check)
  • My PR is as minimal as possible (smaller, focused PRs are easier to review than big ones)
  • I have provided a detailed rationale explaining my changes
  • New or modified functions have Ddoc comments (with Params: and Returns:)

Please see CONTRIBUTING.md for more information.


If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment.

Bugzilla references

Your PR doesn't reference any Bugzilla issue.

If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog.

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub run digger -- build "master + phobos#10651"

@pbackus
Copy link
Contributor Author

pbackus commented Feb 27, 2025

CC @atilaneves this is a new public symbol.

@LightBender
Copy link
Contributor

Would std.conv.paint be a valid name, or is it too vague?

@pbackus
Copy link
Contributor Author

pbackus commented Feb 27, 2025

I wouldn't call paint invalid, necessarily, but it lacks the established usage of typePaint (as far as I know), and one-word names carry a much higher risk of name collision than two-word names (e.g., you could imagine a graphics library having a paint function).

@TurkeyMan
Copy link
Contributor

TurkeyMan commented Feb 27, 2025

It's a function. You've turned a thunk into a function call...

What's the objection to Walter's PR?

This is pointless... it's kinda like min, it's unlikely to bother, and just write x<y?x:y most of the time instead.

@pbackus
Copy link
Contributor Author

pbackus commented Feb 28, 2025

It's a function. You've turned a thunk into a function call...

Added pragma(inline, true); thanks for the reminder.

What's the objection to Walter's PR?

On record here: dlang/dmd#20644 (comment)

This is pointless... it's kinda like min, it's unlikely to bother, and just write x<y?x:y most of the time instead.

I guess this is just a matter of taste. I would almost always write min(x, y) instead of x < y ? x : y.

@TurkeyMan
Copy link
Contributor

Well you can push your thing as you like, but why stand in the way of Walter's PR?

@TurkeyMan
Copy link
Contributor

Also worth asking; who is this intended to be used by? Did anybody ever ask for this?

@Herringway
Copy link
Contributor

Looks a lot nicer than the casts I've been littering my code with lately. Sounds good to me!

@pbackus
Copy link
Contributor Author

pbackus commented Feb 28, 2025

Well you can push your thing as you like, but why stand in the way of Walter's PR?

I'm not "standing in the way" of anything. I'm offering my opinion on his PR and presenting an alternative. I certainly hope he'll take my feedback seriously, but at the end of the day, it's his call.

Also worth asking; who is this intended to be used by? Did anybody ever ask for this?

I could ask the same about cast(ref T).

If we decide typePaint isn't worth merging, I'm fine with that. But it seems to me like if it's not even worth adding a simple library function for this, it cannot possibly be worth adding dedicated syntax.

@LightBender
Copy link
Contributor

This is pointless... it's kinda like min, it's unlikely to bother, and just write x<y?x:y most of the time instead.

Ok. This got me to do a double-take. How is writing min(x,y) worse than the keyboard gynamastics of x<y?x:y. Not only is it significantly harder to read, it is entirely non-obvious what is going on here. And the ergonomics of the second example make my wrists cringe a bit.

@LightBender
Copy link
Contributor

@pbackus

one-word names carry a much higher risk of name collision than two-word names (e.g., you could imagine a graphics library having a paint function).

Good point, maybe paintType, since we're "painting a type as"?

I know I'm nitpicking names, but I gave ya'll fair warning I was going to be like this with V3 on the way.

@jmdavis
Copy link
Member

jmdavis commented Feb 28, 2025

The name typePaint comes from the D language spec, which uses it to describe casts between pointers and class references [1], and casts between arrays with different element types [2] [3].

Personally, I'd prefer something like reinterpretCast. I don't recall running into the term "type painting" before, and searching for it online isn't turning up any relevant hits for me. Given that the term is used in the spec, presumably, it already exists, and maybe it's a lot more commonly known that it looks like, but even the spec seems to feel the need to point out that it's talking about a reinterpret cast, so I don't know why it doesn't just use that term instead.

Well you can push your thing as you like, but why stand in the way of Walter's PR?

It's obtuse syntax. Granted, I don't think that typePaint is particularly good, because I'm not familiar with that terminology, but having an actual name for the cast will make it way clearer what's going on. And if the compiler can't optimize the function in such a way that there's no performance difference, then we have issues anyway. And maybe dmd can't, since it's optimizer isn't great, but I'd be very surprised if ldc or gdc didn't optimize this function out of existence, and pragma(inline, true) should enforce it anyway.

This is pointless... it's kinda like min, it's unlikely to bother, and just write x<y?x:y most of the time instead.

I guess this is just a matter of taste. I would almost always write min(x, y) instead of x < y ? x : y.

I'm the same way. min is clearer, and you don't run the risk of accidentally getting the order wrong. min will also work with more than two arguments.

And if someone feels that way about min, why is *cast(T*) &value vs typePaint any different? You can write out the "simple" cast and risk screwing it up, or you can just use the name and not have to worry about writing the cast out correctly. And Walter's PR doesn't really improve things, because it's sufficiently obtuse that unless you're using it constantly, you're going to have to look it up when you use it, whereas typePaint - or even better, reinterpretCast - has an actual name where you stand a decent chance of remembering it.

@pbackus
Copy link
Contributor Author

pbackus commented Feb 28, 2025

@jmdavis I didn't use reinterpretCast because Walter objected quite strongly to the name reinterpretCast. You can read his reply here on the forums:

https://forum.dlang.org/post/vmmk8u$276h$1@digitalmars.com

@LightBender
Copy link
Contributor

Hmmm, interpretAs might be a good naming compromise?

@TurkeyMan
Copy link
Contributor

TurkeyMan commented Feb 28, 2025

Well you can push your thing as you like, but why stand in the way of Walter's PR?

I'm not "standing in the way" of anything. I'm offering my opinion on his PR and presenting an alternative. I certainly hope he'll take my feedback seriously, but at the end of the day, it's his call.

Also worth asking; who is this intended to be used by? Did anybody ever ask for this?

I could ask the same about cast(ref T).

Well me at least, and Walter it turns out.

But it seems to me like if it's not even worth adding a simple library function for this, it cannot possibly be worth adding dedicated syntax.

This logic is backwards... it's definitely not worth adding a library function or I would have done that decades ago.
The expression in the language is simple, elegant, and useful. I don't really see why it's objectionable. Why would you even be motivated to propose this in contrast? You obviously think it's better...

@jmdavis
Copy link
Member

jmdavis commented Feb 28, 2025

@jmdavis I didn't use reinterpretCast because Walter objected quite strongly to the name reinterpretCast. You can read his reply here on the forums:

https://forum.dlang.org/post/vmmk8u$276h$1@digitalmars.com

<sigh> It's nice and clear, and it's an ugly cast by nature that shouldn't be done very often anyway.

Hmmm, interpretAs might be a good naming compromise?

I like it better than talking about type painting.

The expression in the language is simple, elegant, and useful. I don't really see why it's objectionable. Why would you even be motivated to propose this in contrast? You obviously think it's better...

It's objectionable because it's not even vaguely clear what it means, whereas having a function allows us to have a clear name. This is one area where I think that C++ improved on C and where we arguably regressed in comparison to C++. We have a single cast operator that does everything and the kitchen sink, making it harder to know what's going on in some cases, whereas C++ has named cast operators where it's clear what they're actually doing.

@pbackus
Copy link
Contributor Author

pbackus commented Feb 28, 2025

@LightBender I have no personal objection to interpretAs, but I suspect others might—people feel very strongly about brevity.

The most concise name for this concept that I've been able to find prior usage of is bitCast, which comes from C++20. If the consensus is that bitCast is clearer than typePaint, I'm happy to switch.

Edit: "bit cast" is also used in Swift's unsafeBitCast

Edit: also .NET's Unsafe.BitCast

Edit: also Zig's @bitCast

Edit: also LLVM IR's bitcast

@0xEAB
Copy link
Member

0xEAB commented Feb 28, 2025

Would std.conv.paint be a valid name, or is it too vague?

IMHO that would be too generic.

Actually I think paint is the worse part of the name.
(One could also argue that it might be prone to symbol clashes (esp. with graphics libraries) – but as someone who usually imports std.conv selectively, I’m not keen on bringing this up as a reasonable argument.)

I have no personal objection to interpretAs, but I suspect others might—people feel very strongly about brevity.

The most concise name for this concept that I've been able to find prior usage of is bitCast, which comes from C++20. If the consensus is that bitCast is clearer than typePaint, I'm happy to switch.

Yeah, both sound better to me – interpretAs or bitCast.

@0xEAB
Copy link
Member

0xEAB commented Feb 28, 2025

I didn't use reinterpretCast because Walter objected quite strongly to the name reinterpretCast. You can read his reply here on the forums:

https://forum.dlang.org/post/vmmk8u$276h$1@digitalmars.com

I might be misinterpreting said statement (or missing something entirely), but I read that as a critique on the syntax rather than the name.

@pbackus
Copy link
Contributor Author

pbackus commented Feb 28, 2025

@0xEAB I think when taken together with his next reply, it's fairly clear that his main objection is to the name, but he does have other reasons for favoring built-in syntax over library code:

https://forum.dlang.org/post/vmq5o2$18d2$1@digitalmars.com

@TurkeyMan
Copy link
Contributor

TurkeyMan commented Feb 28, 2025

This is pointless... it's kinda like min, it's unlikely to bother, and just write x<y?x:y most of the time instead.

Ok. This got me to do a double-take. How is writing min(x,y) worse than the keyboard gynamastics of x<y?x:y. Not only is it significantly harder to read, it is entirely non-obvious what is going on here. And the ergonomics of the second example make my wrists cringe a bit.

Well you left out import lib.util : min;... which is yuck! I mean, that makes my wrists cringe!

But it's the additional function calls for trivia that annoy me. min is a fundamental operation; it's a single opcode right beside add. Imagine if I had to write import std.math : +;

But conceptual issues aside, it's just a practical deal-breaker in a number of ways. Most importantly, you can't highlight the text and have your debugger pop-up the evaluation. Debuggers will evaluate expressions, but they won't run functions, so despite being a single opcode, using pointless functions terminate your ability to evaluate such trivial expressions in your debugger.
When reading the code, pointless function calls break up the code a lot polluting the vicinity with a whole bunch of register allocations and call/return which makes it harder to read and step through the program. They rarely come in singles; often a whole bunch of them in parallel which REALLY bloat out the vicinity, totally crowding out the actual code that you're trying to read. Another issue is that you need to pay close attention and take care to issue step-in and step-over commands correctly, and it's impossible to get that right when a min is an argument to a function, so you need to split the expression out to its own line, or else you have to do an annoying step-in, step-out, step-in sequence to enter the function you want to inspect.

It's just shit all round. "min(a, b) kinda looks nicer" is the only argument in favour of min. Sadly, that mostly irrelevant advantage doesn't carry its weight.

@TurkeyMan
Copy link
Contributor

TurkeyMan commented Feb 28, 2025

@0xEAB I think when taken together with his next reply, it's fairly clear that his main objection is to the name, but he does have other reasons for favoring built-in syntax over library code:

https://forum.dlang.org/post/vmq5o2$18d2$1@digitalmars.com

Sorry, what? I couldn't care less about the name. (although I do think it's shit 😝)

(EDIT: Sorry; I just realised you meant Walter)

@LightBender
Copy link
Contributor

Edit: "bit cast" is also used in Swift's unsafeBitCast

Edit: also .NET's Unsafe.BitCast

Edit: also Zig's @bitCast

Edit: also LLVM IR's bitcast

@pbackus Thank you for doing this research legwork, this is exactly the kind of naming process I am hoping we can achieve throughout the Phobos 3 process.

I posted a poll in Discord before I saw this. I'll let it run, but I suspect I know how this ends.

@pbackus
Copy link
Contributor Author

pbackus commented Feb 28, 2025

For anyone curious about the assembly this generates, here's a godbolt link: https://d.godbolt.org/z/a4nco9sh7

TL;DR is that LDC produces identical assembly for both the built-in and library versions, even with no optimizations. GDC apparently doesn't respect pragma(inline, true), so it emits a call unless you compile with -O or use the GDC-specific @always_inline attribute.

@TurkeyMan
Copy link
Contributor

TurkeyMan commented Feb 28, 2025

These are practical issues; optimisation is irrelevant, and so is inlining.
The expression evaluator doesn't care if a function is inline, it just won't evaluate it.

To make cast into a function is to ruin cast expressions in the debugger for everyone everywhere. (just like min/max)

I have no idea why this thread even exists; cast(ref T) is simple and elegant.

@0xEAB
Copy link
Member

0xEAB commented Feb 28, 2025

I think when taken together with his next reply, it's fairly clear that his main objection is to the name, but he does have other reasons for favoring built-in syntax over library code:

https://forum.dlang.org/post/vmq5o2$18d2$1@digitalmars.com

@pbackus My interpretation of “The shorter the name, and the less ugly it is, the more it risks breaking code.” would be that we have to call it reinterpretCastTo too make it uglier.

But I have to admit that I might still be overlooking something as I couldn’t find the other reference you’ve mentioned. Also I’m not a native English speaker

@TurkeyMan
Copy link
Contributor

I think when taken together with his next reply, it's fairly clear that his main objection is to the name, but he does have other reasons for favoring built-in syntax over library code:
https://forum.dlang.org/post/vmq5o2$18d2$1@digitalmars.com

@pbackus My interpretation of “The shorter the name, and the less ugly it is, the more it risks breaking code.” would be that we have to call it reinterpretCastTo too make it uglier.

But I have to admit that I might still be overlooking something as I couldn’t find the other reference you’ve mentioned. Also I’m not a native English speaker

You know what solution has exactly zero risk of breaking code, and also completely avoids this kind of bike-shedding? ;)

@0xEAB
Copy link
Member

0xEAB commented Feb 28, 2025

and also completely avoids this kind of bike-shedding? ;)

AFAICT it was the very thing that triggered this “bike-shedding” :P

@jmdavis
Copy link
Member

jmdavis commented Feb 28, 2025

I have no idea why this thread even exists; cast(ref T) is simple and elegant.

It exists because other folks don't agree that cast(ref T) is simple and elegant. Rather, it's obtuse and feels like it's an attempt to shoehorn a new type of cast into the language by simply grabbing syntax from elsewhere.

And I doubt that most of the rest of us agree that a function is a problem in the way that you do. Having functions in a debugger is perfectly normal and not something that most of us worry about, and if you really want to use an explicit cast, nothing is stopping you from using the explicit cast right now, even if it's uglier, just like you can avoid writing out min or max. I'm fairly certain that most folks find min and max to be far more readable and maintainable. Prior to this conversation, I have never heard of anyone complaining about something being a function call in a debugger.

Of course, we can add the obtuse casting syntax and add the library function. Personally, even then, I'd favor the library function, because then you get a clear name. I very much doubt that I will remember cast(ref T) well enough to not have to look it up when I need it or when I run into it, because reinterpret casts simply don't come up very often in my experience. At least with the explicit cast that you have to do now, what's happening is logical even if you have work through all of the various bits of syntax, whereas cast(ref T) seems practically random.

@TurkeyMan
Copy link
Contributor

and also completely avoids this kind of bike-shedding? ;)

AFAICT it was the very thing that triggered this “bike-shedding” :P

No. I mean, if you're saying this is in response to my thing; then it's a complete waste of time... I'll never use it, it's just not fit for purpose.

The weird thing is that you all suddenly feel so strongly and excited about this matter, why didn't any of you write this pointless function decades ago?
And who is it for? Nobody has asked for this.

@0xEAB
Copy link
Member

0xEAB commented Feb 28, 2025

why didn't any of you write this pointless function decades ago? And who is it for? Nobody has asked for this.

well… https://github.com/adamdruppe/arsd/blob/bd502fb91204de400b46607702cbde222e7bd297/core.d#L184-L218

@jmdavis
Copy link
Member

jmdavis commented Feb 28, 2025

The weird thing is that you all suddenly feel so strongly and excited about this matter, why didn't any of you write this pointless function decades ago? And who is it for? Nobody has asked for this.

I've thought for a while now that we should add casting functions to Phobos like C++ has with its casts (maybe not the exact same set, but something similar). It would make casts clearer and less error-prone. It just hasn't yet been a high enough priority for me to sit down and work through and propose a set of such functions.

Either way, I really don't want the built-in cast to become yet more complicated and confusing, and IMHO, cast(ref T) does precisely that.

@TurkeyMan
Copy link
Contributor

TurkeyMan commented Feb 28, 2025

I have no idea why this thread even exists; cast(ref T) is simple and elegant.

It exists because other folks don't agree that cast(ref T) is simple and elegant. Rather, it's obtuse and feels like it's an attempt to shoehorn a new type of cast into the language by simply grabbing syntax from elsewhere.

'Shoehorn'? I really feel like a clearer syntax is literally impossible. There's definitely no 'shoehorn'-ing here, it's the most perfect representation of the expression that I think is possible. I suggested it because I thought it was super-elegant, and it turned out Walter was also instantly persuaded.

Of course, we can add the obtuse casting syntax and add the library function. Personally, even then, I'd favor the library function, because then you get a clear name. I very much doubt that I will remember cast(ref T) well enough to not have to look it up when I need it or when I run into it, because reinterpret casts simply don't come up very often in my experience. At least with the explicit cast that you have to do now, what's happening is logical even if you have work through all of the various bits of syntax,

If, as you say, it "doesn't come up often"... then strictly speaking; this doesn't affect you.
If you've managed to live and work this long and this matter doesn't seem relevant to you, that's because it's not!

whereas cast(ref T) seems practically random.

See, I couldn't disagree more; I think it's the most self-evident expression possible. It does exactly what I would guess it does if I saw it in the wild having never seen it before. I don't see how any other interpretation is possible, and I can't imagine an alternative expression that would be clearer. This is DEFINITELY not clearer: *cast(T*)&t...

But like you say, you will never encounter this, so it doesn't affect you. You might be forgiven for having no intuition around this, since it's not a part of your world-view.

@jmdavis
Copy link
Member

jmdavis commented Feb 28, 2025

If, as you say, it "doesn't come up often"... then strictly speaking; this doesn't affect you.

It does affect me. No, it's not something that I run into daily, but if anything, that makes it that much worse if it's using obtuse syntax, because then when I do run into it, I have to figure out what it's doing. With a named function, the code actually tells me what it's doing.

This is DEFINITELY not clearer: cast(T)&t...

It's clearer insofar as it's possible to work through what it's doing, whereas with something like cast(ref T), you have to just know what it's doing, because you remember it. In either case, reinterpretCast or bitCast would be far clearer.

@Herringway
Copy link
Contributor

I have no idea why this thread even exists; cast(ref T) is simple and elegant.

It exists because other folks don't agree that cast(ref T) is simple and elegant. Rather, it's obtuse and feels like it's an attempt to shoehorn a new type of cast into the language by simply grabbing syntax from elsewhere.

'Shoehorn'? I really feel like a clearer syntax is literally impossible. There's definitely no 'shoehorn'-ing here, it's the most perfect representation of the expression that I think is possible. I suggested it because I thought it was super-elegant, and it turned out Walter was also instantly persuaded.

Of course, we can add the obtuse casting syntax and add the library function. Personally, even then, I'd favor the library function, because then you get a clear name. I very much doubt that I will remember cast(ref T) well enough to not have to look it up when I need it or when I run into it, because reinterpret casts simply don't come up very often in my experience. At least with the explicit cast that you have to do now, what's happening is logical even if you have work through all of the various bits of syntax,

If, as you say, it "doesn't come up often"... then strictly speaking; this doesn't affect you. If you've managed to live and work this long and this matter doesn't seem relevant to you, that's because it's not!

whereas cast(ref T) seems practically random.

See, I couldn't disagree more; I think it's the most self-evident expression possible. It does exactly what I would guess it does if I saw it in the wild having never seen it before. I don't see how any other interpretation is possible, and I can't imagine an alternative expression that would be clearer. This is DEFINITELY not clearer: *cast(T*)&t...

But like you say, you will never encounter this, so it doesn't affect you. You might be forgiven for having no intuition around this, since it's not a part of your world-view.

This seems to be veering very far off topic. If you want your special cast syntax, then write a DIP and implement it.

@TurkeyMan
Copy link
Contributor

It's not obtuse... it's the most logical and clear expression that I think is actually possible.

That said, maybe it's worth the nit-pick that this isn't a 'reinterpret cast'; at least, not in C++ terms that I think you're all relating back to. reinterpret_cast does not implement a type-pun. If you follow that line of thinking, you're begging for confusion.

It's clearer insofar as it's possible to work through what it's doing...

I mean, this pre-violates your proposition (you have to assume to understand every feature of the complex expression) in the very next sentence...

...whereas with something like cast(ref T), you have to just know what it's doing, because you remember it.

Oh, come on! That's true for LITERALLY EVERY SINGLE THING if you take a purist point of view. Otherwise you have to accept that you lean on associated knowledge of things in principle and then make some reasonable educated guess about what the conjunction of concepts could mean when encountering anything new.

I feel pretty confident there's no other reasonable conclusion about what that expression could possibly mean besides what it actually means.

I know you're going to resist that in principle, but in a world where this shitfight didn't exist and you just happened to stumble upon that expression in the wild by surprise and had to guess what it did (because you couldn't look it up), I have high confidence you would reach the proper conclusion, even though you don't want to admit it. I personally think it's blindingly obvious, but even if you don't find it obvious, I don't think a wrong conclusion is realistically possible.

@TurkeyMan
Copy link
Contributor

This seems to be veering very far off topic. If you want your special cast syntax, then write a DIP and implement it.

It's already done, and it's sitting there hanging. You downvoted it.

@0xEAB
Copy link
Member

0xEAB commented Feb 28, 2025

and had to guess what it did

void foo(ref int bar);

foo(              0x1337); // cannot pass rvalue argument `4919` of type `int` to parameter `ref int bar`
foo(cast(ref int) 0x1337); // ok

@Herringway
Copy link
Contributor

This seems to be veering very far off topic. If you want your special cast syntax, then write a DIP and implement it.

It's already done, and it's sitting there hanging. You downvoted it.

There is no DIP to downvote.

@TurkeyMan
Copy link
Contributor

TurkeyMan commented Feb 28, 2025

and had to guess what it did

void foo(ref int bar);

foo(              0x1337); // cannot pass rvalue argument `4919` of type `int` to parameter `ref int bar`
foo(cast(ref int) 0x1337); // ok

Huh? That would emit the same error as above for the same reason.
You can't write &0x1337...

@0xEAB
Copy link
Member

0xEAB commented Feb 28, 2025

Huh? That would emit the same error as above for the same reason.

Indeed, that’s the issue. Why have cast(ref T) when it cannot cast things to ref T?

@TurkeyMan
Copy link
Contributor

TurkeyMan commented Feb 28, 2025

Huh? That would emit the same error as above for the same reason.

Indeed, that’s the issue. Why have cast(ref T) when it cannot cast things to ref T?

I don't understand what you're trying to say... are you somehow surprised that you can't convert an rvalue to ref?

@0xEAB
Copy link
Member

0xEAB commented Feb 28, 2025

We have a visual pattern that doesn’t work out in practice.

void foo1(    int bar) {}
void foo2(ref int bar) {}

foo1(cast(    int) 1.337);
foo2(cast(ref int) 1.337);  // looks so similar, yet happens to be utter nonsense

Edit: Keep in mind, we’re talking about “and had to guess what it did”.

@TurkeyMan
Copy link
Contributor

Well your example won't compile... I still don't get what you're trying to show.

@0xEAB
Copy link
Member

0xEAB commented Feb 28, 2025

We have an outlier here:

void foo1(const     int bar) {}
void foo2(ref       int bar) {}

foo1(cast(          int) 1.0);
foo1(cast(const     int) 1.0);
foo1(cast(immutable int) 1.0);
foo2(cast(ref       int) 1.0); // This syntax does something else.

I still don't get what you're trying to show.

If we were to have just to stumble upon that expression in the wild by surprise and had to guess what it did (because we couldn't look it up), syntactic sugar for promoting an rvalue to a referenceable lvalue the inline way would be a fair guess.

@jmdavis
Copy link
Member

jmdavis commented Feb 28, 2025

It's not obtuse... it's the most logical and clear expression that I think is actually possible.

That said, maybe it's worth the nit-pick that this isn't a 'reinterpret cast'; at least, not in C++ terms that I think you're all relating back to. reinterpret_cast does not implement a type-pun. If you follow that line of thinking, you're begging for confusion.

It's clearer insofar as it's possible to work through what it's doing...

I mean, this pre-violates your proposition (you have to assume to understand every feature of the complex expression) in the very next sentence...

...whereas with something like cast(ref T), you have to just know what it's doing, because you remember it.

Oh, come on! That's true for LITERALLY EVERY SINGLE THING if you take a purist point of view. Otherwise you have to accept that you lean on associated knowledge of things in principle and then make some reasonable educated guess about what the conjunction of concepts could mean when encountering anything new.

I feel pretty confident there's no other reasonable conclusion about what that expression could possibly mean besides what it actually means.

It's certainly true that with anything new in the language, you have something new to learn and remember, but IMHO cast(ref T) doesn't fit with the rest of the language, because ref is not part of the type and can only be used in a restricted set of contexts. If you're going to add new syntax to the existing cast operator, then it probably is the best choice for what's being done here, but I don't buy for a second that it's obvious. If anything, it looks more like an rvalue is somehow being turned into an lvalue. And this proposed syntax is for an operation that in my experience is relatively rare, so we'd be adding special syntax for an uncommon operation, which is generally a bad idea IMHO.

So, if it were my decision, I'd definitely decide against cast(ref T). Walter seems to be in favor of it, so if Atila is as well, and someone actually writes a DIP for it, then it will presumably make it into the language, but I'm against it, and I hope that I never have to actually deal with it.

But whether it's added to the language or not, I think that adding a templated function to Phobos which handles it - and thus actually names what it's doing - is a good thing to do have. If anything, we should have that with more kinds of casts, since it would not only allow for greater clarity in code, but it would allow us to catch some of the mistakes that can happen with casts given how blunt they are (e.g. if we had a function which cast away const, then it could give an error if you passed it something that wasn't const as well as ensure that no other type qualifiers changed at the same time and that the type itself didn't change aside from the qualifiers). I've been considering doing that for a while now, and maybe I should have already.

@TurkeyMan
Copy link
Contributor

We have an outlier here:

void foo1(const     int bar) {}
void foo2(ref       int bar) {}

foo1(cast(          int) 1.0);
foo1(cast(const     int) 1.0);
foo1(cast(immutable int) 1.0);
foo2(cast(ref       int) 1.0); // This syntax does something else.

I still don't get what you're trying to show.

If we were to have just to stumble upon that expression in the wild by surprise and had to guess what it did (because we couldn't look it up), syntactic sugar for promoting an rvalue to a referenceable lvalue the inline way would be a fair guess.

ref isn't related to const/immutable at all, and what it means in a function signature is exactly the same as what it means in the cast expression. You have to assume you know what ref means at all, otherwise you're begging for a similar level of mysticism when the user encounters ref in the function argument...
This feels like a serious reach.

@LightBender
Copy link
Contributor

LightBender commented Feb 28, 2025

@pbackus @TurkeyMan

I spoke with @WalterBright a few hours ago and in the time honored tradition of design by committee we arrived at the solution that gives everyone something to hate.

We're going to get both.

This function will be merged with whatever name we settle on.
And cast(ref T) will be made available for use where Phobos is not available. Or by those who want to use it specifically.

@ArthaTi
Copy link
Contributor

ArthaTi commented Feb 28, 2025

I support bitCast because to me, it signifies that the cast occurs at bit level. reinterpret carries about the same, nonspecific meaning as cast. You can ask "reinterpret what?" and the answer will be "bits".

@LightBender
Copy link
Contributor

LightBender commented Feb 28, 2025

bitCast is winning the vote handily here and in Discord.

@pbackus all this needs is a change-log entry and name change and we're good to merge.

This helper function is a more readable alternative to the traditional
pointer-cast-based syntax for reinterpreting casts.

The name bitCast is used for this operation in several other languages,
including C++ [1], Swift [2], C# [3], Zig [4], and LLVM IR [5].

[1] https://en.cppreference.com/w/cpp/numeric/bit_cast
[2] https://developer.apple.com/documentation/swift/unsafebitcast(_:to:)
[3] https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.unsafe.bitcast
[4] https://ziglang.org/documentation/0.10.0/#bitCast
[5] https://llvm.org/docs/LangRef.html#bitcast-to-instruction
@pbackus pbackus changed the title Add std.conv.typePaint Add std.conv.bitCast Feb 28, 2025
@pbackus
Copy link
Contributor Author

pbackus commented Feb 28, 2025

Renamed and added changelog.

@LightBender LightBender merged commit 6722d0a into dlang:master Feb 28, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants