www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - [OT] Move semantics in a nutshell

reply Sergey <kornburn yandex.ru> writes:
This is really nice tutor about details of C++ and Rust:
- strong/weak invariants
- move semantics
- shared

I think D should consider to not repeat C++ "errors"..

https://www.youtube.com/watch?v=Klq-sNxuP2g

I wonder about Kinke, Walter and Manu opinion on
destructive vs non-destructive move
Nov 07
next sibling parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Friday, 7 November 2025 at 19:31:31 UTC, Sergey wrote:

 https://www.youtube.com/watch?v=Klq-sNxuP2g
 invarients are super duper important
 (ints just work tho)
 some of the worse sorts of c++ complexity
 rust being annoying
What a very strong argument for never ever doing this, but strangely the author thinks this is all important Int "has the invariant" that its bits "represent" 0 from int.max, this is a perfect "bipart map" of the real hardware .... Or you can just say "Ints airnt stupid" --- Theres is like 1 "invariant" anyone ever cares about "does it exist?", why not just make a warper of slices to `length()=>data.ptr is null ? 0 : data.length` woaw so much safety
Nov 07
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11/8/25 00:47, monkyyy wrote:
 On Friday, 7 November 2025 at 19:31:31 UTC, Sergey wrote:

 
 https://www.youtube.com/watch?v=Klq-sNxuP2g
 invarients are super duper important
 (ints just work tho)
 some of the worse sorts of c++ complexity
 rust being annoying
What a very strong argument for never ever doing this, but strangely the author thinks this is all important Int "has the invariant" that its bits "represent" 0 from int.max, this is a perfect "bipart map" of the real hardware .... Or you can just say "Ints airnt stupid" ...
You don't have to share takes this nonsensical every time someone overuses a word not defined on simple wikipedia. It's a choice.
 ---
 
 Theres is like 1 "invariant" anyone ever cares about "does it exist?", 
"Does it exist?" is not an invariant, this is a predicate. "it exists" is an invariant (or "if condition holds then it exists"), but what is "it"? In practice, what is "it" is described by another invariant.
 why not just make a warper of slices to `length()=>data.ptr is null ? 
 0 : data.length`
 ...
"It's a slice" is an invariant I care about. And even if you don't tie your invariants to data types, there is still an abundance of them, even if you refuse to describe them in these terms. Programs simply don't degrade gracefully under random bit flips in CPU registers, even if they use only `int`s. The hardware design itself has invariants everywhere, including within the implementation of the instructions that manipulate integers.
 woaw so much safety 
Unsafe C code, such as the linux kernel, is full of invariants. x) In fact, the less safe your language is, the more you have to explicitly think about invariants in order to write good programs. It's just more likely that they remain arcane knowledge instead of being documented, perhaps even within the type system.
Nov 08
parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Saturday, 8 November 2025 at 11:00:25 UTC, Timon Gehr wrote:
 
 You don't have to share takes this nonsensical every time 
 someone overuses a word not defined on simple wikipedia. It's a 
 choice.

 Programs simply don't degrade gracefully under random bit flips 
 in CPU registers, even if they use only `int`s

 Unsafe C code, such as the linux kernel, is full of invariants.
Whats the (very important) invariants on int? How does my bag of ints prevent cosmic rays using these lovely c++ concepts of xvalues, chvalues?
Nov 08
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11/8/25 17:38, monkyyy wrote:
 On Saturday, 8 November 2025 at 11:00:25 UTC, Timon Gehr wrote:
 You don't have to share takes this nonsensical every time someone 
 overuses a word not defined on simple wikipedia. It's a choice.

 Programs simply don't degrade gracefully under random bit flips in CPU 
 registers, even if they use only `int`s

 Unsafe C code, such as the linux kernel, is full of invariants.
Whats the (very important) invariants on int?
In terms of the restricted definition shown in the video, it depends on the program. Let's say you have something like the following piece of C code: static int i; // ... i *= 2; // ... Now your program immediately has a joint invariant involving `i` and the program counter.
 How does my bag of ints 
 prevent cosmic rays using these lovely c++ concepts of xvalues, chvalues?
The video is a critique of C++ and it barely shows any Rust. The way you are conceptualizing this makes no sense to me.
Nov 08
parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Saturday, 8 November 2025 at 22:45:29 UTC, Timon Gehr wrote:
 On 11/8/25 17:38, monkyyy wrote:
 
 Whats the (very important) invariants on int?
code
 The way you are conceptualizing this makes no sense to me.
Hot take: `int` has no (existence) limitations, your code you want to hint at at some function failure but you didn't do that ```d void foo(int i){assert(i!=3);} ``` In this code, int still is a perfect data type, its foo that breaks if you do the unthinkable and call it with 3. ```d int[256] myarray; ubyte myindex; ref int get()=>myarray[myindex]; ``` This code does not have an existence invariant unless you want to claim the compiler or os will break in some profoundly stupid way. In which case I'm not trying to fix it. If d does "move", it will get c++ "move" complexity to some degree. C++ makes some stupid problems to even have. I suggest you avoid having stupid problems. I will not be writing code the correctly handles zvalues 7 op overloads with fvalues. --- Allocation (or "that data better exist when I ask for it") isn't that hard, you dont need smart pointers with dozens of overloads and 100 pages of "undefined behavior" that I aint reading or a mathematical proof engine and lots of shame about safety; big arrays, one op overload `opIndex`, the range api with 3 functions. Maybe a sumtype and nullable here or there. When defining an opIndex you got yourself an int, and plenty of context maybe just try solving the problem then and there. I suggest making a singular ugly trade off, instead of the piles and piles of complexity c++ and rust just inject into the core language and excited to talk about their massive amounts of complexity, slow compiles, and 10000 man hours solutions.
Nov 08
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11/9/25 01:31, monkyyy wrote:
 On Saturday, 8 November 2025 at 22:45:29 UTC, Timon Gehr wrote:
 On 11/8/25 17:38, monkyyy wrote:
 Whats the (very important) invariants on int?
code
 The way you are conceptualizing this makes no sense to me.
Hot take: `int` has no (existence) limitations,
`int` gives a meaning to all bit patterns. Not all numbers are the right answer, or even a sensible answer. It's inevitable that a useful program will have some invariants involving `int` data, even if your programming language never crashes and has no UB. Invariants in general touch more than one variable at a time, data type invariants are are just a method to package them in a way that is often a bit more manageable.
 your code you want to 
 hint at at some function failure but you didn't do that
 ...
My code was a fine illustration of the point that just because every bit pattern is a valid `int` you are not banishing invariants involving that `int`. Not every bit pattern in RAM is made equal. Some of them are actually preferable.
 ```d
 void foo(int i){assert(i!=3);}
 ```
 
 In this code, int still is a perfect data type, its foo that breaks if 
 you do the unthinkable and call it with 3.
 ...
Clearly `int` is not the ideal type for this function argument.
 ```d
 int[256] myarray;
 ubyte myindex;
 ref int get()=>myarray[myindex];
 ```
 
 This code does not have an existence invariant unless you want to claim 
 the compiler or os will break in some profoundly stupid way. In which 
 case I'm not trying to fix it.
 ...
Look who is relying on language invariants and OS invariants now.
 If d does "move", it will get c++ "move" complexity to some degree. C++ 
 makes some stupid problems to even have.
The C++ design is uniquely... C++.
 I suggest you avoid having stupid problems.
Not having moves leads to stupid problems. Breaking strong invariants leads to stupid problems too, but there is plenty of prior art with `T.init`. Anyway, I have my preferences, but I don't get to have the final say of what features make it into D in what form, nor do I have the spare time required to make a convincing case for my preferences. Personally I am not a fan of either `T.init` nor husks, but I think moves are useful. In the real world, you will always have some stupid problems, you can just try to choose which ones.
 I will not be writing code the correctly handles 
 zvalues 7 op overloads with fvalues.
 ...
This is simply not what is being proposed. What is being proposed though is something that requires weaker invariants from all movable types, which demonstrably leads to stupid problems. Ironically you attack invariants as being somehow a C++ or Rust thing that magically contributes to their problems. No, invariants are a thing, programming languages have to contend with them, and there are better and worse ways to do it. The video goes like "if not A then B". You go like "B is stupid, hence A is stupid".
 ---
 
 Allocation (or "that data better exist when I ask for it") isn't that 
 hard,
Famous last words. Anyway, even if it is not that hard, getting it wrong even once can have (and often does have) devastating consequences. People are imperfect and having a lot of bugs that are avoidable using a better programming methodology is also a stupid problem.
 you dont need smart pointers with dozens of overloads and 100 
 pages of "undefined behavior" that I aint reading or a mathematical 
 proof engine and lots of shame about safety; big arrays, one op overload 
 `opIndex`, the range api with 3 functions. Maybe a sumtype and nullable 
 here or there.
 ...
Yes, types with internal invariants such as arrays, sumtype and nullable are indeed useful for dealing with memory allocation. Bonus points if they are movable. But these don't solve all stupid problems. Now you e.g. often enough have to deal with `int`s that are actually references. What works in 100-line programs often enough no longer works at scale. What works for you may not work for other applications.
 When defining an opIndex you got yourself an int, and plenty of context 
 maybe just try solving the problem then and there. I suggest making a 
 singular ugly trade off, instead of the piles and piles of complexity c+ 
 + and rust just inject into the core language and excited to talk about 
 their massive amounts of complexity, slow compiles, and 10000 man hours 
 solutions.
There is no panacea and if you think you found one you are probably wrong.
Nov 09
parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Sunday, 9 November 2025 at 10:19:33 UTC, Timon Gehr wrote:
 On 11/9/25 01:31, monkyyy wrote:
 
 When defining an opIndex you got yourself an int, and plenty 
 of context maybe just try solving the problem then and there. 
 I suggest making a singular ugly trade off, instead of the 
 piles and piles of complexity c+ + and rust just inject into 
 the core language and excited to talk about their massive 
 amounts of complexity, slow compiles, and 10000 man hours 
 solutions.
There is no panacea and if you think you found one you are probably wrong.
I know yall opinions on actually failsafe code, but you jumped from "make one ugly tradeoff" to "panacea" ```d import std; template innate(T){ T innate; } struct myarray(T){ T[] data; ref T opIndex(int i){ if(i < 0){ return innate!T; } if(i >= data.length){ return innate!T; } return data[i]; } //todo more op overloads alias data this; } unittest{ myarray!int foo; foo[-1000]=3; foo[1]=6; foo.map!(a=>assert(0)); foo~=1; foo~=[2,3]; foreach(e;foo){ e.writeln; } } ``` People make stupid problems, an opIndex is a function, I give an int(and I do mean int, size_t is bad design) and I expect a T back, I use my huge, massive brain to consider all possible ints given the context of my data structure. I dont know how for mere mortals to handle the *manual data flow analysis* involved with this 2^32 by 2^64 possibility space; but perhaps you could be convinced it will in fact not crash.
 Look who is relying on language invariants and OS invariants 
 now.
The size of ints is hardware level and n^2. Its not the yours or walters or even c's. Maybe it was boole's maths. That would be correct in any statically typed language and Im not thinking about d's number propitiation thingy at all here. --- and dont go saying that headers and static types are good ideas because of invarents; they are at least as old as c
Nov 09
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11/9/25 17:35, monkyyy wrote:
 On Sunday, 9 November 2025 at 10:19:33 UTC, Timon Gehr wrote:
 On 11/9/25 01:31, monkyyy wrote:
 When defining an opIndex you got yourself an int, and plenty of 
 context maybe just try solving the problem then and there. I suggest 
 making a singular ugly trade off, instead of the piles and piles of 
 complexity c+ + and rust just inject into the core language and 
 excited to talk about their massive amounts of complexity, slow 
 compiles, and 10000 man hours solutions.
There is no panacea and if you think you found one you are probably wrong.
I know yall opinions
We are a group with a diverse set of opinions. I also don't think you actually know my opinions. It appears to me that we barely speak the same language sometimes.
 on actually failsafe code,
Oh neat, another invariant. Completely fail safe code (i.e., with a correctness guarantee that it will not cause UB or crashes assuming the hardware is able to maintain its invariants) does not exist if you are running an OS, due to the potential of out of memory errors. Furthermore, (conditional) fail safety does not establish any sort of liveness properties, so your programs can also just stop responding to inputs or producing outputs while it cycles through now meaningless bit patterns in RAM, which is arguably an even more severe problem than a crash. Of course, it could also just start overwriting your hard disk with random garbage, which is even worse. It can also be vulnerable to an arbitrary code execution exploit, etc. Anyway, aiming for fail safety first is nice for some things (e.g., in some games, getting a "correct" result is usually less important than not crashing), but it is not always the right tradeoff. It's really annoying to debug and it is not always possible, e.g. if you interact with the OS or libraries. Invariants are also not saying "the program crashes sometimes", they are saying "the program only uses these specific combinations of bit patterns".
 but you jumped from "make one ugly tradeoff" to "panacea"
 ...
No, there was more context. You were literally saying you solve everything C++ and Rust are trying to address with this one ugly tradeoff.
 ```d
 import std;
 template innate(T){
      T innate;
 }
 struct myarray(T){
      T[] data;
      ref T opIndex(int i){
          if(i < 0){
              return innate!T;
          }
          if(i >= data.length){
              return innate!T;
          }
          return data[i];
      }
      //todo more op overloads
      alias data this;
 }
 unittest{
      myarray!int foo;
      foo[-1000]=3;
      foo[1]=6;
      foo.map!(a=>assert(0));
      foo~=1;
      foo~=[2,3];
      foreach(e;foo){
          e.writeln;
      }
 }
 ```
 
 People make stupid problems, an opIndex is a function, I give an int(and 
 I do mean int, size_t is bad design) and I expect a T back, I use my 
 huge, massive brain to consider all possible ints given the context of 
 my data structure.
 
 I dont know how for mere mortals to handle the *manual data flow 
 analysis* involved with this 2^32 by 2^64 possibility space; but perhaps 
 you could be convinced it will in fact not crash.
 ...
I am not sure why I should particularly care about this program, it does not seem like something I would get utility out of running. As I said, the problems that C++ or Rust try to address with their type systems pop up at a larger scale. In any case, I can perhaps prove that this will not crash assuming some sort of ideal semantics in particular relying on invariants established by the compiler tying the source code to the execution behavior and some assumptions on the execution environment. In practice though it might fail with e.g. OOM, or because stdout cannot be written to, or maybe we can do some shenanigans with `--DRT=` arguments, etc. There are plenty of ways your code can still crash. In the end, a crash is nothing other than a particular kind of bit pattern in your computer's memory, and "not crashing" is an invariant you are seeking to establish. I however usually care about more than it just not crashing (and at some point, an uncontrolled blizzard of random garbage behaviors or freezing up or externally controllable code execution becomes way worse than even a crash). Furthermore, dependencies I have to interact with will still crash when fed random garbage. Being able to handle exceptions and for some applications even errors is usually a better tradeoff for me.
 Look who is relying on language invariants and OS invariants now.
The size of ints is hardware level and n^2. Its not the yours or walters or even c's. Maybe it was boole's maths. ...
You are relying on so much more than the size of ints. Anyway, it stands that it makes little sense to say one should not consider invariants important because C++ or Rust have some questionable designs.
 That would be correct in any statically typed language..
Even in some dynamic ones. Exactly my point.
Nov 09
parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Sunday, 9 November 2025 at 23:41:28 UTC, Timon Gehr wrote:
 Invariants are also not saying "the program crashes sometimes", 
 they are saying "the program only uses these specific 
 combinations of bit patterns".
You claimed my ubyte example was "an invariant", using all 2^8 of ubytes space isn't a *specific* combination. Your using math language and logic here, you can say "the empty set is a set" but nah that aint real english 0 be a special case, if I handle all possible bit patterns my "invariants" drop to be `assert(1==1,"math stopped working")`type things; if your disk runs out of memory, or cosmic rays corrupt the os, or or or or or or; these airnt my problems anymore. I write `opIndex(int)` I think "hmmm what are the possible `int`s" and then I handle those cases, I dont "solve" problems with rants in docs, or add function coloring or ask for a big math proofer. Insiting the way I write code fits your mental model while I continuous insit I'm not, is insane. Uncle bob may walk into a code base and go "hmmm, yes this is a visitor pattern", but if the author generally says "uncle bob is a con artist and should be shoot", It may "fit" but it doesn't "use" uncle bobs ideas. I consider everything I do to just be dumb strong types. If its a panacea, its been there since the beginning and it aint that hard to call the so called experts with their "important exceptions" stupid. `1/0==int.max` god damn hardware bugs making me write work'onds.
 invariants are a thing, programming languages have to contend 
 with them
The compiler is unable to act on "slices cant point at invalid memory" so it doesnt and the os's whines; it can and does have opinions on int sizes and its 1 to 1 with reality. Types are more fundamental ground truth for what compilers work with, ints are far more real as something in a header then a rant in the spec.
 it could also just start overwriting your hard disk with random 
 garbage
I aint never seen an undefined behavior do this even once `int.max+1` maybe its an int.max maybe its int.min, maybe its 0, maybe is some pathological platform its a random int, but I aint never seen no delete harddrive unless you let code from 4chan run, and let me tell ya it wasnt by mistake.
Nov 09
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11/10/25 01:49, monkyyy wrote:
 On Sunday, 9 November 2025 at 23:41:28 UTC, Timon Gehr wrote:
 Invariants are also not saying "the program crashes sometimes", they 
 are saying "the program only uses these specific combinations of bit 
 patterns".
You claimed my ubyte example was "an invariant",
It's an example of relying on a lot of invariants to establish a few more invariants.
 using all 2^8 of ubytes 
 space isn't a *specific* combination. Your using math language and logic 
 here, you can say "the empty set is a set" but nah that aint real 
 english 0 be a special case, if I handle all possible bit patterns my 
 "invariants" drop to be `assert(1==1,"math stopped working")`type 
 things; if your disk runs out of memory, or cosmic rays corrupt the os, 
 or or or or or or; these airnt my problems anymore.
That's the point of invariants... Bit patterns that are not used are not your problem anymore. Your program and so much else is just bits in memory too.
 I write 
 `opIndex(int)` I think "hmmm what are the possible `int`s" and then I 
 handle those cases,
In your example you also had to think about "hmmm what are the possible `T[]`. Actually you had to think about "hmmm what are the possible combinations of `int`s and `T[]`s. And somehow you disregarded the `T[]`s where the length and ptr are out of synch or the ptr points to inaccessible memory.
 I dont "solve" problems with rants in docs,
But you did not consider invalid slices your problem anymore, because someone wrote rants in docs about what `T[]` is supposed to mean and did their best to implement that behavior in the compiler and runtime. This is why people sometimes like to use user-defined or language types with invariants (e.g., slices). "hmmm what are the possible `T`s" and then handling these cases, while not having to deal with all of the additional nonsensical cases by doing random nonsense.
 or add function coloring or ask for a big math proofer.
 ...
You don't have to do either of these things. Just accept that someone else might like engaging in these activities. Don't tread on them.
 Insiting the way I write code fits your mental model while I continuous 
 insit I'm not, is insane.
It's not a specific property of your code, it's just a fact of life that some people talk about using certain terms. I don't need you to accept these terms, I am just explaining why others think the underlying concepts referred to by these terms are important and you are wasting your time by trying to push your way of thinking onto them.
 Uncle bob may walk into a code base and go 
 "hmmm, yes this is a visitor pattern", but if the author generally says 
 "uncle bob is a con artist and should be shoot",
How hard is it to not suggest people should be shot for talking. I think this is the bare minimum.
 It may "fit" but it doesn't "use" uncle bobs ideas.
 ...
Sure, people reinvent equivalent concepts all the time. Translating between different viewpoints can still be illuminating, especially when someone keeps telling other people they are stupid or should have violence done to them just for talking about the same things in terms more commonly understood in their specific communities. Anyway, you are entirely correct that you don't have to "use" abstractions for something to be _an example_ of what is being talked about in the abstract. And even if something is an example of an abstract concept, that does not at all preclude there being essential new insights in the example that are not captured by the abstraction. Not all abstractions are useful, but the higher the level of abstraction, the more likely it is that something important is also captured by the abstraction. x)
 I consider everything I do to just be dumb strong types. If its a 
 panacea, its been there since the beginning and it aint that hard to 
 call the so called experts with their "important exceptions" stupid. 
 `1/0==int.max` god damn hardware bugs making me write work'onds.
 ...
Maybe just build your own language, run without an OS, build workarounds for any remaining hardware traps into the compiler, run on (so-called) bare metal, and discover your own terms for describing the wonderful worlds of lack of memory protection that will open up before you. As you correctly point out, it's not that hard. You could also get an FPGA or something and fix the crashes at the hardware level, though integrating this with other components might be a bit harder.
 invariants are a thing, programming languages have to contend with them
The compiler is unable to act on "slices cant point at invalid memory" so it doesnt and the os's whines; it can and does have opinions on int sizes and its 1 to 1 with reality. ...
The CPU architecture is basically a virtual machine integrated with the OS at this point. 1 to 1 with reality? Certainly not.
 Types are more fundamental ground truth for what compilers work with, 
 ints are far more real as something in a header then a rant in the spec.
 
 it could also just start overwriting your hard disk with random garbage
I aint never seen an undefined behavior do this even once `int.max+1` maybe its an int.max maybe its int.min, maybe its 0, maybe is some pathological platform its a random int, but I aint never seen no delete harddrive unless you let code from 4chan run, and let me tell ya it wasnt by mistake.
Look no further than encryption-based ransomware (e.g. EternalBlue exploited by WannaCry and NotPetya/ExPetr). It's not a theoretical scenario at all, though you might call it something else. I don't know. I guess it has either not happened to you yet or you found your own canonical language to speak about it and therefore everyone else who suffered from these experiences is stupid or something. Anyway, I was not even talking about undefined behavior, I was talking about exploitable gadgets or other accidental an undesirable behaviors that I suspect will basically inevitably form at an even higher rate in 100% failsafe computations. Note that I don't think D is the most fruitful ground for these "fail-safe" experiments, Walter is adamant about having programs crash at the first sign of trouble. He often shares that this opinion was shaped by experiences with programming computers before memory protection was a common thing and he sometimes laments that younger generations had not had to go through such experiences. It has been the most opinionated area of D's design and I am very happy that we are moving away a bit from the most extreme position where the language fights you at every step of the way if you go as far as trying to make a custom crash dump. The opposite extreme is not suitable for most people either, though. And this unfortunately implies that there is less support for these alternative ways of doing computing. I don't know any way around that, finite resources are an inevitable fact of life. What we can hopefully agree on though is that commodity computing hardware must not be locked down in order to enforce a certain style of computing or programming methodology.
Nov 10
next sibling parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Monday, 10 November 2025 at 12:42:10 UTC, Timon Gehr wrote:
 In your example you also had to think about "hmmm what are the 
 possible `T[]`. Actually you had to think about "hmmm what are 
 the possible combinations of `int`s and `T[]`s.
That ubyte code was a static array A static 256 length array with a ubyte index has no "specific limitations on its bit pattern" if it fails its a os level failure and I couldn't operate anyway.
 Look no further than encryption-based ransomware (e.g. 
 EternalBlue exploited by WannaCry and NotPetya/ExPetr). It's 
 not a theoretical scenario at all,
 I don't think D is the most fruitful ground for these 
 "fail-safe" experiments
Such encryption is **very** well defined, as is what version of rm -* will delete your harddrive. They escape the box using *well-defined undefined-behavior*, keep in big databases with lots of formal writting, with lots of theory and powerpoints and terminology; that walter for his faults as an airplane surgeon, thought "we can fix this quickly with an api solution" while ranting and raving that the c committy were full of idiots. Walter is over confident calling it the most important thing ever, and the range api was an ovisous next step, then merging data structures into the std but... opps. Oh well. We have 3 template languages, and 3 suggestions for memory safety, c++, rant in long spec call users bad if they violate your rules, rust and its piles of math, walter and "guys lets just make a breaking change around string handling" and shipping a data structure. Im for api level solutions and calling slow moving "experts" idiots
Nov 10
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11/10/25 17:13, monkyyy wrote:
 On Monday, 10 November 2025 at 12:42:10 UTC, Timon Gehr wrote:
 In your example you also had to think about "hmmm what are the 
 possible `T[]`. Actually you had to think about "hmmm what are the 
 possible combinations of `int`s and `T[]`s.
That ubyte code was a static array ...
Sorry for not being clear, you are correct that "your example code" more naturally refers to the `ubyte` example due to recency bias. I was here talking about your earlier example with a `T[]`, where the point is even a bit more obvious.
 A static 256 length array with a ubyte index has no "specific 
 limitations on its bit pattern" if it fails its a os level failure and I 
 couldn't operate anyway.
 ...
The simple fact is that you are able to relate types to execution behavior. This is because the compiler produces binaries that are related in a specific way to your source code. I.e., it establishes (or at least aims to establish) very specific invariants on source code and corresponding binaries. Undefined behavior is an example of weak invariants, for a language without undefined behavior, these invariants are stronger. Your workarounds against default behaviors that result in crashes and/or UB are workarounds against weak invariants.
 Look no further than encryption-based ransomware (e.g. EternalBlue 
 exploited by WannaCry and NotPetya/ExPetr). It's not a theoretical 
 scenario at all,
Such encryption is **very** well defined, as is what version of rm -* will delete your harddrive. ...
The compiler produced an executable that allows an attacker to encrypt or wipe the hard drive remotely when none of these capabilities were spelled out in the source code of the program. It's a prime example of undefined behavior in action that illustrates that the scenarios I brought up are realistic in the context in which you had placed them.
Nov 10
parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Monday, 10 November 2025 at 16:53:28 UTC, Timon Gehr wrote:
 It's a prime example of undefined behavior in action that 
 illustrates that the scenarios I brought up are realistic in 
 the context in which you had placed them.
**well-defined undefined behavior**; rants in a spec. I know that every math textbook possibly for every year of school disagrees with 1/0==int.max(possibly those math text books out number humans); and compilers are very very very clever detecting when they can change how your code works obeying such a spec with 1000000 lines of code in llvm to enable what c++ does and this takes an hour of computer to handle these massive projects. and theres the cve database, theres the iso standards there nist, ieee float spec and blah de blah There was a 50 step process that created the last memory exploit with hyper autists quoting formal texts for why they were right to let it happen. So much to know, such big words, all tabulated and organized. --- ... judge a tree by its fruits Its called "undefined behavior" yet its in these books, we can all just look at the results, theres plenty of learning resources about each, theres rants on forums on each little elemental design flaw and why its a "impossible to fix" problem. The unknowable behavior is running 1000000 lines of code written by 10000 people, and thats the cause of the ransom ware. It wasn't the so called "undefined behavior" of being lazy and missing a bounds check, each step of the long process is quite aware people miss bound checks and we all have direct experience with segfaults. Its impossible to comprehend the ever growing machine, but you can simply have the arrogance to say its wrong and just do things. We are defining things all the time, make new elemental pieces to solve problems even if people say they are hard and require the big pile of complexity or cite the big pile of complexity inability to solve it. ez as `1/0==int.max`, yes, I know what a math book would say and have seen a calculator. Cut the Gordian knot.
Nov 11
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 11/11/25 18:35, monkyyy wrote:
 On Monday, 10 November 2025 at 16:53:28 UTC, Timon Gehr wrote:
 ,,,
The unknowable behavior is running 1000000 lines of code written by 10000 people, and thats the cause of the ransom ware.
Sure, and specs are shorter, so if you can get code to comply to the spec and if you only have to rely on the spec to interact with the code, then that keeps complexity at bay to some extent. It will not fix everything, but it just has to improve the status quo. You don't have to naively believe every idea either solves all problems or is completely useless.
 ...
 ez as `1/0==int.max`, yes, I know what a math book would say
It certainly would not say that `a/b` means "divide and round to the next integer towards zero except if `b==0` or the result does not fit inside the data type then crash".
 and have seen a calculator. Cut the Gordian knot.
You don't actually have solutions that are analogous to cutting the knot. x) Also note that quite a bit of the complexity is due to social/legal/political problems. Anyway, if you are at it, fix `%`. `x%0 == x` at least makes sense.
Nov 11
prev sibling parent Dukc <ajieskola gmail.com> writes:
On Monday, 10 November 2025 at 12:42:10 UTC, Timon Gehr wrote:
 Maybe just build your own language, run without an OS, build 
 workarounds for any remaining hardware traps into the compiler, 
 run on (so-called) bare metal, and discover your own terms for 
 describing the wonderful worlds of lack of memory protection 
 that will open up before you.
There's an easier way. I suggest playing with an Altair 8800 simulator. I don't recall what it does on things like division by zero, but in general it has the habit of just keeping going no matter how wrong your machine code is. It just won't do what you wanted and might well overwrite your code. The utility of HLT instruction (or whatever it was, but it's the machine code equivalent of `assert(false)`) will be evident. Crashing is a feature, not a bug (but it is a response to a bug). Also remember, D is designed by an airplane engineer. This means the program written in it *must* crash before the plane it could be flying does. Walter sure has refused to alter much lesser convictions of his than this one.
Nov 11
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11/7/25 20:31, Sergey wrote:
 This is really nice tutor about details of C++ and Rust:
 - strong/weak invariants
 - move semantics
 - shared
 
 I think D should consider to not repeat C++ "errors"..
 
 https://www.youtube.com/watch?v=Klq-sNxuP2g
Manu has had a strong preference for caller-side destruction, which essentially implies that moved-from locations need to leave behind a "husk" to destroy, in general weakening the type's invariant (the strawman alternative being to add additional runtime boolean flags to the function ABI, with additional branches in the destruction logic). Walter is reluctant to do flow analysis in the frontend and DIP1000 relies on lifetimes of stack locations following the stack discipline. This similarly prefers the "husk" design. I am not sure what is Kinke's perspective as he has not commented as much about the topic. Supporting "destructive moves" implies callee-side destruction is more natural (which actually happens to be the traditional convention with `extern(D)`). However, this then either requires a bit more advanced static analysis than what DMD is currently doing, or introducing additional runtime boolean flags and branches to the destruction logic (though the flags would not have to pass function boundaries). Personally I think adding implicit additional boolean flags and branching is a no-go, so the tradeoff revolves around either adding non-trivial logic to the type checker in order to attempt to ensure that the old values remain inaccessible, or settling on the "husk" design. Personally, I expect husks to win and invariants to lose in D, at least in the near term. While this C++ issue is then not resolved, Manu's design does already avoid the "too many reference types" problem from C++.
Nov 08
next sibling parent reply Sergey <kornburn yandex.ru> writes:
On Saturday, 8 November 2025 at 15:44:11 UTC, Timon Gehr wrote:
 On 11/7/25 20:31, Sergey wrote:
 I am not sure what is Kinke's perspective as he has not 
 commented as much about the topic.
I meant this proposal https://forum.dlang.org/post/vehdu0$1tiu$1 digitalmars.com
 Personally, I expect husks to win and invariants to lose in D, 
 at least in the near term. While this C++ issue is then not 
 resolved, Manu's design does already avoid the "too many 
 reference types" problem from C++.
Thank you Timon! Very interesting observations.
Nov 08
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 11/8/25 17:40, Sergey wrote:
 On Saturday, 8 November 2025 at 15:44:11 UTC, Timon Gehr wrote:
 On 11/7/25 20:31, Sergey wrote:
 I am not sure what is Kinke's perspective as he has not commented as 
 much about the topic.
I meant this proposal https://forum.dlang.org/post/ vehdu0$1tiu$1 digitalmars.com ...
That proposal leaves behind husks reset to `T.init`, but I think that was not really the key focus nor a distinguishing feature of that proposal. I think the key thing there was the somewhat different proposal for how to define and handle move vs forward. When the topic was later specifically invariants vs husks, IIRC he did not comment.
 Personally, I expect husks to win and invariants to lose in D, at 
 least in the near term. While this C++ issue is then not resolved, 
 Manu's design does already avoid the "too many reference types" 
 problem from C++.
Thank you Timon! Very interesting observations.
Thanks. :)
Nov 08
prev sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
To add to this:

On 09/11/2025 4:44 AM, Timon Gehr wrote:
 On 11/7/25 20:31, Sergey wrote:
 This is really nice tutor about details of C++ and Rust:
 - strong/weak invariants
 - move semantics
 - shared

 I think D should consider to not repeat C++ "errors"..

 https://www.youtube.com/watch?v=Klq-sNxuP2g
Manu has had a strong preference for caller-side destruction, which essentially implies that moved-from locations need to leave behind a "husk" to destroy, in general weakening the type's invariant (the strawman alternative being to add additional runtime boolean flags to the function ABI, with additional branches in the destruction logic). Walter is reluctant to do flow analysis in the frontend and DIP1000 relies on lifetimes of stack locations following the stack discipline. This similarly prefers the "husk" design.
Dmd does do data flow analysis, but its all very limited to a single forward pass type stuff, including DIP1000. There is also some VRP and at least one other thing. A single forward pass just means that you do a tree walk on the AST. This isn't how a DFA engine is usually written. They'll normally have a analysis algorithm that dictates how it is analyzed. For DFA the normal group of algorithms are called work list. They are slow, not a good fit for D by default. The single forward pass approach is used in my fast DFA engine which was merged last month. You can do an awful lot in such an engine, and will gain a lot more capability if it confirms to be good after a release.
 I am not sure what is Kinke's perspective as he has not commented as 
 much about the topic.
 
 
 Supporting "destructive moves" implies callee-side destruction is more 
 natural (which actually happens to be the traditional convention with 
 `extern(D)`). However, this then either requires a bit more advanced 
 static analysis than what DMD is currently doing, or introducing 
 additional runtime boolean flags and branches to the destruction logic 
 (though the flags would not have to pass function boundaries).
The flags are used for exceptions handling, its not a new thing. Backends are very good at optimizing such flags.
 Personally I think adding implicit additional boolean flags and 
 branching is a no-go, so the tradeoff revolves around either adding non- 
 trivial logic to the type checker in order to attempt to ensure that the 
 old values remain inaccessible, or settling on the "husk" design.
 
 Personally, I expect husks to win and invariants to lose in D, at least 
 in the near term. While this C++ issue is then not resolved, Manu's 
 design does already avoid the "too many reference types" problem from C++.
Right now only the last copy is promoted to a move, so this whole issue really only comes down to manually written moves. Quite frankly those can be system and that removes the entire problem as far as the language is concerned. Overall I don't understand the point of move constructors, its not like you can use them to aid ownership transfer systems and as far as I'm aware they are the only thing that really benefits from it.
Nov 08
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11/8/25 18:55, Richard (Rikki) Andrew Cattermole wrote:
 ...
 
 The single forward pass approach is used in my fast DFA engine which was 
 merged last month. You can do an awful lot in such an engine, and will 
 gain a lot more capability if it confirms to be good after a release.
 ...
Looking forward to testing it out.
 I am not sure what is Kinke's perspective as he has not commented as 
 much about the topic.


 Supporting "destructive moves" implies callee-side destruction is more 
 natural (which actually happens to be the traditional convention with 
 `extern(D)`). However, this then either requires a bit more advanced 
 static analysis than what DMD is currently doing, or introducing 
 additional runtime boolean flags and branches to the destruction logic 
 (though the flags would not have to pass function boundaries).
The flags are used for exceptions handling, its not a new thing. Backends are very good at optimizing such flags.
 Personally I think adding implicit additional boolean flags and 
 branching is a no-go, so the tradeoff revolves around either adding 
 non- trivial logic to the type checker in order to attempt to ensure 
 that the old values remain inaccessible, or settling on the "husk" 
 design.

 Personally, I expect husks to win and invariants to lose in D, at 
 least in the near term. While this C++ issue is then not resolved, 
 Manu's design does already avoid the "too many reference types" 
 problem from C++.
Right now only the last copy is promoted to a move,
Last time I checked, no such promotion happens beyond some very specific special cases. While the DIP that proposed last-use analysis had been accepted, I don't expect it's going to be implemented anytime soon. Unless your DFA helps with that?
 so this whole issue 
 really only comes down to manually written moves.
Well, given there is actually last-use analysis then you can simply check whether manually written moves coincide with last uses. The moves that need to be ` system` are the ones that the type checker is not able to prove ` safe`, like with basically any other feature.
 Quite frankly those 
 can be  system and that removes the entire problem as far as the 
 language is concerned.
 ...
Not exactly. Additionally there still needs to be some way to manually elide destructor calls so the language does not accidentally attempt to destroy "unsafe husks". Also, it's a bit of a cop out and likely does not match common expectations on ergonomics of move semantics in new languages going forward.
 Overall I don't understand the point of move constructors, its not like 
 you can use them to aid ownership transfer systems and as far as I'm 
 aware they are the only thing that really benefits from it.
 
The main utility of move constructors is in supporting internal references. In a husk design, they may also need to be written specifically such as to leave behind a benign husk (e.g., this is the case in C++).
Nov 08
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 09/11/2025 12:26 PM, Timon Gehr wrote:
 On 11/8/25 18:55, Richard (Rikki) Andrew Cattermole wrote:
 I am not sure what is Kinke's perspective as he has not commented as 
 much about the topic.


 Supporting "destructive moves" implies callee-side destruction is 
 more natural (which actually happens to be the traditional convention 
 with `extern(D)`). However, this then either requires a bit more 
 advanced static analysis than what DMD is currently doing, or 
 introducing additional runtime boolean flags and branches to the 
 destruction logic (though the flags would not have to pass function 
 boundaries).
The flags are used for exceptions handling, its not a new thing. Backends are very good at optimizing such flags.
 Personally I think adding implicit additional boolean flags and 
 branching is a no-go, so the tradeoff revolves around either adding 
 non- trivial logic to the type checker in order to attempt to ensure 
 that the old values remain inaccessible, or settling on the "husk" 
 design.

 Personally, I expect husks to win and invariants to lose in D, at 
 least in the near term. While this C++ issue is then not resolved, 
 Manu's design does already avoid the "too many reference types" 
 problem from C++.
Right now only the last copy is promoted to a move,
Last time I checked, no such promotion happens beyond some very specific special cases. While the DIP that proposed last-use analysis had been accepted, I don't expect it's going to be implemented anytime soon. Unless your DFA helps with that?
It cannot. 1. Its missing value tracking (+ VRP is next up after it goes into a release) 2. This alters the AST beyond flagging an AST node. I.e. its ok to flag an expression as non-null, but not remove a node. I suspect the best way to handle this is to ensure that the called functions split out the blit/destructor call in such a way that they can be removed during optimization with a custom backend pass.
 so this whole issue really only comes down to manually written moves.
Well, given there is actually last-use analysis then you can simply check whether manually written moves coincide with last uses. The moves that need to be ` system` are the ones that the type checker is not able to prove ` safe`, like with basically any other feature.
Okay yeah that makes sense.
 Quite frankly those can be  system and that removes the entire problem 
 as far as the language is concerned.
 ...
Not exactly. Additionally there still needs to be some way to manually elide destructor calls so the language does not accidentally attempt to destroy "unsafe husks". Also, it's a bit of a cop out and likely does not match common expectations on ergonomics of move semantics in new languages going forward.
 Overall I don't understand the point of move constructors, its not 
 like you can use them to aid ownership transfer systems and as far as 
 I'm aware they are the only thing that really benefits from it.
The main utility of move constructors is in supporting internal references. In a husk design, they may also need to be written specifically such as to leave behind a benign husk (e.g., this is the case in C++).
Yeah I get that you need a hook for it. But not how we've tuned them so that they are mutually inclusive of copy constructors.
Nov 09