digitalmars.D - What ever happened to move semantics?
- Manu (34/34) Feb 26 I've started writing some code for the first time in years; and the time
- Mike Shah (38/85) Feb 26 I long suspected the lack of different containers is because the
- Richard (Rikki) Andrew Cattermole (15/15) Feb 26 We are aware of all of this.
- Mike Parker (8/15) Feb 27 I'll add that containers are specifically a goal right now.
- Walter Bright (10/12) Feb 26 Hi Manu! Good to hear from you!
- Max Samukha (2/5) Feb 26 ImportC was not a demand!
- Manu (16/23) Feb 27 =F0=9F=91=86=F0=9F=91=86=F0=9F=91=86
- Walter Bright (4/16) Feb 27 Proof that you read the dip!
- Timon Gehr (5/7) Feb 27 FWIW I have been pushing this a couple times at the DLF meetings, but in...
- Walter Bright (3/8) Feb 27 Reviewing the DIP would be a big help if that can work for you.
- Timon Gehr (199/234) Feb 28 Sure! A lot of good stuff in there. Here's my review.
- Timon Gehr (16/20) Feb 28 19. Explicit moves.
- Walter Bright (8/32) Feb 28 Forcing a move just sounds like trouble. If it is not the last use, and ...
- Timon Gehr (33/84) Feb 28 No, the idea is that the compiler enforces that it is indeed the last
- Don Allen (38/38) Feb 28 As I've said in previous posts, I've written a lot of Rust and
- Timon Gehr (11/36) Feb 28 Linear typing is a very old idea.
- Walter Bright (7/7) Feb 28 I do share your concern, Don. I worry that we know so well how C++ does ...
- electricface (5/13) Feb 28 Try using DFA technology not during normal compilation in the
- electricface (6/22) Feb 29 Try to linguistically mark the last usage of a variable, and then
- Don Allen (14/22) Mar 01 I've expressed sentiments like this before, Walter, most recently
- Walter Bright (30/71) Feb 28 DFA works with mathematical reliability (absent compiler bugs). The opti...
- Atila Neves (3/12) Feb 29 Not always. Some types have to be move-only.
- Jonathan M Davis (20/33) Feb 29 Yeah. One of the proposed ways to handle basic input ranges with Phobos ...
- Timon Gehr (24/122) Feb 29 This is not about catching bugs in the DFA. This is about allowing a
- Dukc (21/26) Feb 29 I'm a bit worried this would lead to implementation-defined
- Richard (Rikki) Andrew Cattermole (9/16) Feb 28 I've been exploring this problem recently with isolated.
- Max Samukha (2/5) Feb 28 Also, no discussion of `@disable`d move constructors.
- Jonathan M Davis (48/78) Feb 28 I've already run into issues trying to add copy constructors at work whe...
- Manu (12/38) Feb 27 ld
- Walter Bright (2/11) Feb 27 Yeah, you're right.
- Dakota (10/18) Feb 27 If you use D with betterC like c (not cpp), ImportC will work
- Max Samukha (4/6) Feb 28 I'm resorting to an argumentum ad industriam: move constructors
- zjh (3/5) Feb 26 You're absolutely right, it's just that there's no `container
- Guillaume Piolat (8/13) Feb 27 Trying to build that at:
- zjh (3/5) Feb 27 Thank you for your infomation.
- Sergey (19/20) Feb 27 Btw will be interesting to know the list of containers you demand.
- Richard (Rikki) Andrew Cattermole (5/8) Feb 27 Lol yes, if you want it to be all tidy with answers to many use cases
- Danilo (3/7) Feb 27 Maybe [tanya](https://code.dlang.org/packages/tanya) can help as
- Danilo (3/10) Feb 27 Forgot to add https://github.com/dlang-community/containers
I've started writing some code for the first time in years; and the time away and distance really gave me some fresh perspective. I started writing D because that's what I prefer, and I was shaken with the realisation that the situation is just so much worse than I was ever willing to admit to myself. This is a small greenfields app, and I want to develop it quickly... so I told myself that I'd try and use phobos. ...but it needs to run on microcontrollers, so I need to compile tiny code and avoid GC and stuff. Better-C seems like a good plan, except that phobos is just not compatible. So, I figured I'd just use phobos and worry about fixing up allocation semantics later when I want to release on hardware, so I can dev the business logic on PC quickly. Seems like a plan, except phobos is just BAD. API's are inconsistent and mostly horrible. Nothing is intuitive, and also I realise I'm writing myself into a huge overhaul when I want to forge a microcontroller build later. The biggest slap in the face though, which is honestly unexcusable 20 years on, is that D still has no real meaningful container library! How can a modern language not have a library of containers that let me organise my data?! I realised that the best and most usable containers are actually the stdcpp containers I was implementing years ago! But the thing that held up that work was that we STILL don't have any way to implement move semantics! There was a SAOC project to implement rvalue references which would open D up to implementing container libraries; but sadly the author just disappeared one day never to be seen again. Nobody else ever moved on that issue. Walter: It's been too long, there are still no containers, which is embarrassing and stdcpp is still blocked on this. I strongly urge you to drop whatever you are doing and finally implement an rvalue reference so we can *finally *implement move semantics, and with that, we can have a container library worthy of the language (I'll even start by finishing stdcpp which is blocked on that issue).
Feb 26
On Tuesday, 27 February 2024 at 02:28:32 UTC, Manu wrote:I've started writing some code for the first time in years; and the time away and distance really gave me some fresh perspective. I started writing D because that's what I prefer, and I was shaken with the realisation that the situation is just so much worse than I was ever willing to admit to myself. This is a small greenfields app, and I want to develop it quickly... so I told myself that I'd try and use phobos. ...but it needs to run on microcontrollers, so I need to compile tiny code and avoid GC and stuff. Better-C seems like a good plan, except that phobos is just not compatible. So, I figured I'd just use phobos and worry about fixing up allocation semantics later when I want to release on hardware, so I can dev the business logic on PC quickly. Seems like a plan, except phobos is just BAD. API's are inconsistent and mostly horrible. Nothing is intuitive, and also I realise I'm writing myself into a huge overhaul when I want to forge a microcontroller build later. The biggest slap in the face though, which is honestly unexcusable 20 years on, is that D still has no real meaningful container library! How can a modern language not have a library of containers that let me organise my data?! I realised that the best and most usable containers are actually the stdcpp containers I was implementing years ago! But the thing that held up that work was that we STILL don't have any way to implement move semantics! There was a SAOC project to implement rvalue references which would open D up to implementing container libraries; but sadly the author just disappeared one day never to be seen again. Nobody else ever moved on that issue. Walter: It's been too long, there are still no containers, which is embarrassing and stdcpp is still blocked on this. I strongly urge you to drop whatever you are doing and finally implement an rvalue reference so we can *finally *implement move semantics, and with that, we can have a container library worthy of the language (I'll even start by finishing stdcpp which is blocked on that issue).I long suspected the lack of different containers is because the built-in fixed-size array, dynamic array, and associative array are sufficient for getting most applications up and running quickly for those who are fine with linking in Phobos and D-Runtime. Perhaps in the same way that many folks using Python stick to lists and dictionaries for any small program. For folks with C++ backgrounds, more containers for performance trade-offs would indeed be nice to have to select from (e.g. unordered_map, map, etc.). std.container (as you probably know, but others may not so I'll link it here - https://dlang.org/phobos/std_container.html) does have a few data structures like std.array as a start (API similar to std::vector). I agree the container list could be expanded and would benefit greatly from move semantics (i.e. using rvalue reference) -- this is probably the one thing I miss from D in C++. I don't know the history regarding rvalue reference with D, but I did find [1][2] useful. Read through the dip and watching the talk as we speak. Curious your thoughts on containers: 1. Should we effectively mirror the cpp containers? 2. Add more specific ones (e.g. std.container.concurrent.array? std.container.lockfree, std.container.intrusive)? 3. Add more (e.g. std.graph, std.directedgraph, etc.)? ^ Question above are to help kickstart some of the discussion as I hear about rumblings of Phobos 3. Regarding the API: I believe there are rumblings of allocators here. Wonder if we again take the C++ approach? e.g. `auto arr = Array!(int, allocatorType)(1,2,3); // second parameter we specify allocator type/policy/strategy. I also *think* some proposal is in progress here -- others can comment to confirm. [1] Found your previous dip here. https://github.com/dlang/DIPs/blob/725541d69149bc85a9492f7df07360f8e2948bd7/DIPs/DIP1016.md [2] Andrei's talk 2019 talk: https://www.youtube.com/watch?v=aRvu2JGGn6E
Feb 26
We are aware of all of this. Move semantics are still on someones radar (I've forgotten who), it was wanted by Weka.io. The problem is the design of move semantics itself and motivation. Neither of which are contingent on Walter. Wait for DIP process to reopen shortly. A new standard library is currently in the works of being planned. It is lead by Adam Wilson who has buy in from both Walter and Atila. This ties into a new planned edition system for ensuring stability of language. https://github.com/LightBender/PhobosV3-Design Shared library support has improved quite a bit, dmd is still WIP for druntime, but Rainer has gotten dmd's core capabilities working now. I believe I have successfully designed the user experience surrounding it, although that has been implemented or accepted just yet. Which will help prevent linker errors in all common scenarios.
Feb 26
On Tuesday, 27 February 2024 at 06:19:53 UTC, Richard (Rikki) Andrew Cattermole wrote:A new standard library is currently in the works of being planned. It is lead by Adam Wilson who has buy in from both Walter and Atila. This ties into a new planned edition system for ensuring stability of language. https://github.com/LightBender/PhobosV3-DesignI'll add that containers are specifically a goal right now. Robert Schadek's upcoming DConf Online talk is related to that. He's volunteered to head up work on a container project, and Steven Schveighoffer has offered to help where he can. There's also somewhat related work on allocators that Atila and Paul Backus are heading up.
Feb 27
Hi Manu! Good to hear from you! We do have a DIP on move semantics I wrote a while back: https://github.com/dlang/DIPs/blob/master/DIPs/DIP1040.md but other demands always seem to get in the way. I'd appreciate your advice on the DIP.I realised that the best and most usable containers are actually the stdcpp containers I was implementing years ago!It's not surprising that the containers you worked on are the most useful to you! I'm curious about what containers are needed for a microcontroller? Since an overhaul of Phobos is underway, I'm especially interested in your takes on unintuitive/horrible aspects. I don't want to bias your response with my thoughts on the matter.
Feb 26
On Tuesday, 27 February 2024 at 07:20:46 UTC, Walter Bright wrote:We do have a DIP on move semantics I wrote a while back: https://github.com/dlang/DIPs/blob/master/DIPs/DIP1040.md but other demands always seem to get in the way.ImportC was not a demand!
Feb 26
On Tue, 27 Feb 2024 at 18:01, Max Samukha via Digitalmars-d < digitalmars-d puremagic.com> wrote:On Tuesday, 27 February 2024 at 07:20:46 UTC, Walter Bright wrote:=F0=9F=91=86=F0=9F=91=86=F0=9F=91=86 I've been really hammering the importance of this issue for *at least* 10 years. I'm so tired of hearing myself rant, I'm just way done. I'm still as convinced as ever that the single most important thing you or anybody could be doing for the language, is fixing the move hole. I'd like to be on the DIP review panel. There are several issues that stand out to me, which I think I'll need to sleep on to digest. value for extern(C++) is a gigantic breaking change. Approaching it from that angle is an interesting idea, but I wonder if it could be really noisy. Most code passes struct/class by ref in C++; by-val structs are fairly rare, so the idea might actually work out. If symmetry or someone has a small budget, we should fly a small group of key individuals into a small room, lock the doors for a few days, and just get it done.We do have a DIP on move semantics I wrote a while back: https://github.com/dlang/DIPs/blob/master/DIPs/DIP1040.md but other demands always seem to get in the way.ImportC was not a demand!
Feb 27
On 2/27/2024 4:16 AM, Manu wrote:I've been really hammering the importance of this issue for /at least/ 10 years. I'm so tired of hearing myself rant, I'm just way done. I'm still as convinced as ever that the single most important thing you or anybody could be doing for the language, is fixing the move hole. I'd like to be on the DIP review panel. There are several issues that stand out to me, which I think I'll need to sleep on to digest.I'm looking forward to your thoughts on it. (Anyone can review any DIP.)value for extern(C++) is a gigantic breaking change. Approaching it from that angle is an interesting idea, but I wonder if it could be really noisy. Most code passes struct/class by ref in C++; by-val structs are fairly rare, so the idea might actually work out.Proof that you read the dip!If symmetry or someone has a small budget, we should fly a small group of key individuals into a small room, lock the doors for a few days, and just get it done.Come to Seattle!
Feb 27
On 2/27/24 13:16, Manu wrote:I'm still as convinced as ever that the single most important thing you or anybody could be doing for the language, is fixing the move hole.FWIW I have been pushing this a couple times at the DLF meetings, but in the end somebody will have to put in the work to implement it in the compiler and I cannot spend the time required for that atm. The move hole is also an issue for tuple unpacking though.
Feb 27
On 2/27/2024 4:42 PM, Timon Gehr wrote:FWIW I have been pushing this a couple times at the DLF meetings, but in the end somebody will have to put in the work to implement it in the compiler and I cannot spend the time required for that atm. The move hole is also an issue for tuple unpacking though.Reviewing the DIP would be a big help if that can work for you. https://github.com/dlang/DIPs/blob/master/DIPs/DIP1040.md
Feb 27
On 2/28/24 02:06, Walter Bright wrote:On 2/27/2024 4:42 PM, Timon Gehr wrote:Sure! A lot of good stuff in there. Here's my review. Points 1 to 15 respond to the DIP contents. The main issue I see is the way move construction and assignment are declared by special-casing existing syntax that already means something else _and changing its observable behavior_. To fix this, I think there should be separate syntax for suppressing the destructor call. Furthermore, partial moving in general does not work in the way it is specified in the DIP, it bypasses the destructor of the enclosing struct without participation of that struct. Point 16 to 18 point out things that are missing from the DIP. The main issue I see here is that destructuring is missing from the DIP. This is crucial in order to be able to transform data from one type into data from another type while using only moves and no copies or destruction. 1. Regarding last use:FWIW I have been pushing this a couple times at the DLF meetings, but in the end somebody will have to put in the work to implement it in the compiler and I cannot spend the time required for that atm. The move hole is also an issue for tuple unpacking though.Reviewing the DIP would be a big help if that can work for you. https://github.com/dlang/DIPs/blob/master/DIPs/DIP1040.md```d S s; f(s); // copy f(s); // copy f(s); // move ```It would be useful to show examples with dynamic control flow (edit: I see some examples occur later too), such as: ```d S s foreach(i;0..3){ f(s); // ? } ``` I assume the line marked "?" will always copy? Maybe it would be better to allow implementation-defined copy elision (also see 11.). ```d S s; f(s); // copy f(s); // ? if(uniform(0,2)) return; f(s); // move ``` I assume the line marked "?" will always copy? Maybe it would be better to allow implementation-defined copy elision (also see 11.). 2. Regarding Existing State in D: - It would make sense to elaborate on ` disable`d copy constructors. This is similar to not implementing the `Copy` trait in Rust. The resulting values can only be moved. - In D, you can also have a `private` destructor. As far as I can tell, this is currently useless, but with move semantics this can be used to enforce explicit destruction via move, which is a nice way to design a library interface. 3. Regarding declaration syntax of Move Constructors and Move Assignment Operators I would highly recommend to use a distinct syntax for suppressing destruction of the argument. I will argue here specifically for the case of Move Constructors, but Move Assignment operators have exactly the same issue.A Move Constructor is a struct member constructor that moves, rather than copies, the argument corresponding to its first parameter into the object to be constructed. The argument is invalid after this move, and is not destructed. A Move Constructor for struct S is declared as: ```d this(S s) { ... } ```This is a breaking language change. Also, consider ```d struct S{ ... this(T)(T t){ ... } ... } ``` This constructor will be a move constructor iff T=S. Therefore, that the destructor is not called on the argument in some cases may be very surprising to programmers. A similar example is this one ```d struct S{ ... this(T...)(S s, T args){ ... } ... } ``` Here, the constructor is a move constructor iff no additional `args` are passed. Overall, the proposed syntax introduces a surprising special case. Also, what is the syntax for a copy constructor? Would it be `this(ref S s){ ... }` ? 4. Regarding `nothrow` on Move Constructors and Move Assignment Operators.The Move Constructor is always nothrow, even if nothrow is not explicitly specified. A Move Constructor that throws is illegal.This special case should be motivated in the DIP. I assume the motivation is that because the argument is not destructed, throwing is particularly error-prone here. In general, I would advise against built-in requirements on specified attributes unless absolutely necessary. 5. Regarding Default Move ConstructorIf a Move Constructor is not defined for a struct that has a Move Constructor in one or more of its fields, a default one is defined, and fields without a Move Constructor are moved using a bit copy.This is missing a specification of what the default move constructor does. (I assume it is implemented as a move for each field, in lexical order, where fields without a Move Constructor are moved using a bit copy.) 6. Regarding Default Move Constructor and Default Move Assignment Operator.If a Move Constructor is not defined for a struct that has a Move Assignment Operator, a default Move Constructor is defined and implemented as a move for each of its fields, in lexical order.This generated move constructor will often do the wrong thing. A correct way to do it would be to default-initialize a new instance and then call the Move Assignment Operator on it. It is also worth considering if instead, a Move Constructor Operator should not just be required to be defined explicitly in any struct that has an explicit Move Assignment Operator defined.If a Move Assignment Operator is not defined for a struct that has a Move Assignment Operator in one or more of its fields, a default Move Assignment Operator is defined, and fields without a Move Assignment Operator are moved using a bit copy. If a Move Assignment Operator is not defined for a struct that has a Move Constructor, a default Move Assignment Operator is defined and implemented as a move for each of its fields, in lexical order.This generated move assignment operator will usually do the wrong thing. A correct but inefficient way to do it would be to destroy the current object and reconstruct it using the Move Constructor. It is also worth considering if instead, a Move Assignment Operator should not just be required to be defined explicitly in any struct that has an explicit Move Constructor defined. 7. Regarding EMOAn EMO is a struct that has both a Move Constructor and a Move Assignment Operator. An EMO defaults to exhibiting move behavior when passed and returned from functions rather than the copy behavior of non-EMO objects.This definition is not self-contained and should therefore refer to the discussion further below for clarification. 8. Regarding Move RefA Move Ref is a parameter that is a reference to an EMO. (The ref is not used.)For small structs, the additional indirection from the implicit reference will introduce overhead. 9. Regarding NRVO of EMO objectsIf NRVO cannot be performed, s is copied to the return value on the caller's stack.This is surprising to me. I would have expected `s` to be moved to the return value on the caller's stack instead. 10. Regarding Returning an EMO by Move Ref This is too cute, because it changes the meaning of `return` in one specific special case. Consider: ```d struct S{ int* ptr; this(S s){ this.ptr=s.ptr; } void opAssign(S s){ this.ptr=s.ptr; } } S func(return S s){ return S(s); } ``` The `return` annotation is needed because the pointer again appears in the return value. Note that this is a simplified example, but we could think of similar ones with multiple involved pointers that need to be permuted (though I don't know how to implement that without destructuring or destruction). 11. Regarding Copy Elision Maybe it would be better to specify explicitly that an implementation is allowed to optimize the pattern: ```d auto s = t; // (copy) ... // arbitrary code not referring to `t` destroy(t); ``` to: ```d auto s = move(t); ``` 12. Regarding lifetimes. You make a point about nested functions and lambdas. However, this is not the only problem. Consider: ```d struct S{ int x; } int foo() safe{ S s; scope p = &s.x; bar(s); // last use of s, moved return *p; // bad memory access } ``` 13. Regarding partial move.Therefore, the generalized rule is that an access to an EMO field of an aggregate will be moved only if that is the last access of the containing variable.This does not work. You cannot elide the entire destructor of `S` based on moving a single field of `S`. 14. Regarding Destruction This is a bit inconsistent with what was presented earlier. I agree that implementation-defined copy elision is probably a good idea (see 11.). 15. Regarding C++ interop. I do not see anything obviously wrong, except that the requirement to opt out of rvalue references seems error prone. I think Manu has more expertise here. Also, it would be good to specify ` value` as a standalone thing in the DIP, as it may be useful beyond C++ interop (also see point 8.). What is missing from the DIP? 16. Missing: Redeclaration after Move ```d S s, t; func(s); // moved, `s` no longer accessible S s = t; // explicit construction via redeclaration ``` A nice feature of this is that the type of a variable can be changed on redeclaration. Note that Rust allows this. 17. Missing: Destructuring This is partially attempted in the DIP via partial move (which does not work). However, there must be a way to implement the following: ```d struct U(T...){ T fields; } struct S(T...){ T fields; disable ~this(); ... // need support from S to bypass destructor } // fields of resulting U must be moved from the fields of S U fromS(S s){ ... } ``` 18. Missing: Moving the receiver ```d struct S{ T foo() rvalue{ ... } disable ~this(); } ``` void main(){ S s; auto t=s.foo(); // last use of s } ```
Feb 28
On 2/28/24 15:17, Timon Gehr wrote:... What is missing from the DIP? ...19. Explicit moves. There is no way to force that a given occurrence of a variable is the last use and is moved. We can use std.algorithm.move, but the DIP as specified would just move a copy if something is used again. What I would like to see is: ```d void main(){ S s; foo(move(s)); auto t=s; // error, `s` has been moved S s; // ok, can redeclare `s` } ``` Maybe there needs to be a parameter annotation that forces a move, then `move` can be implemented in terms of that.
Feb 28
On 2/28/2024 6:22 AM, Timon Gehr wrote:On 2/28/24 15:17, Timon Gehr wrote:Forcing a move just sounds like trouble. If it is not the last use, and it is moved, then wouldn't the result be undefined behavior? Determining last use should be in the purview of the compiler, not the user, so it is reliable. The Ownership/Borrowing system does determine last use, using data flow analysis.... What is missing from the DIP? ...19. Explicit moves. There is no way to force that a given occurrence of a variable is the last use and is moved. We can use std.algorithm.move, but the DIP as specified would just move a copy if something is used again.What I would like to see is: ```d void main(){ S s; foo(move(s)); auto t=s; // error, `s` has been moved S s; // ok, can redeclare `s` } ``` Maybe there needs to be a parameter annotation that forces a move, then `move` can be implemented in terms of that.What is the point of declaring another variable of the same name? We already disallow shadowing declarations, and that has prevented a number of bugs at least in my own code (they're very difficult to spot with a visual check).
Feb 28
On 2/28/24 21:38, Walter Bright wrote:On 2/28/2024 6:22 AM, Timon Gehr wrote:No, the idea is that the compiler enforces that it is indeed the last use and produces a compile-time error message if it cannot prove that it is the case.On 2/28/24 15:17, Timon Gehr wrote:Forcing a move just sounds like trouble. If it is not the last use, and it is moved, then wouldn't the result be undefined behavior?... What is missing from the DIP? ...19. Explicit moves. There is no way to force that a given occurrence of a variable is the last use and is moved. We can use std.algorithm.move, but the DIP as specified would just move a copy if something is used again.Determining last use should be in the purview of the compiler, not the user, so it is reliable. ...Yes. It still holds that one may want to make sure that a value is really moved at a given point. Sometimes this matters. Anyway, this is by far not the most important point.The Ownership/Borrowing system does determine last use, using data flow analysis.From my previous post:What I would like to see is: ```d void main(){ S s; foo(move(s)); auto t=s; // error, `s` has been moved S s; // ok, can redeclare `s` } ``` Maybe there needs to be a parameter annotation that forces a move, then `move` can be implemented in terms of that.What is the point of declaring another variable of the same name?16. Missing: Redeclaration after Move ```d S s, t; func(s); // moved, `s` no longer accessible S s = t; // explicit construction via redeclaration ``` A nice feature of this is that the type of a variable can be changed on redeclaration. Note that Rust allows this.This is a relatively common idiom in languages that support moves. It is annoying if you have to invent a new name for each intermediate result. One use case would be type state: File!(FileState.flushed) file = open("file.txt"); File!(FileState.buffered) file = file.write("hello "); File!(FileState.buffered) file = file.writeln("world!"); // file.close(); // compile time error File!(FileState.flushed) file = file.flush(); file.close(); // moves "file" // file.write("hello"); // compile time error Assume you have some declarations like these and you want to comment out part of the statements. It would now be annoying to have to rename variables. I.e., you want to use the same name for different versions of the same thing, similarly to how you do not have to change the name of a variable when assigning to it.We already disallow shadowing declarations, and that has prevented a number of bugs at least in my own code (they're very difficult to spot with a visual check).The reason why shadowing is error prone is that multiple variables with overlapping lifetimes are in scope and the compiler arbitrarily picks one of them. This case is different, as only one variable of the same name exists at any given time. This is not error prone. Requiring unique names is more error prone in this case, as you can accidentally copy an older version of a variable. Anyway, this is not the most important thing, please do check out the points I initially included in my review. This point is just something I had forgotten to include.
Feb 28
As I've said in previous posts, I've written a lot of Rust and now a fair amount of D. I prefer D as it is, now that I've solved some issues with my own lack of knowledge about the available tools. I want to make a comment about all this chatter about move semantics. I freely admit that I have not read the proposal for doing this, if there is a fully-fleshed-out proposal, and I probably never will. My issue is this: Rust has led the move semantics charge. Grafting move semantics onto feels to me like me-too-ism and just cluttering the D environment with yet another wart that it does not need. Move semantics in Rust results in the programmer becoming an key part of the memory management system, whether they realize it or not. Anyone who has wrestled with the borrow checker or gotten themselves into lifetime hell with Rust will understand (unless they've drunk the Kool-Aid) what I am talking about. Rust is a fiendishly difficult language to learn. I've been doing this for a very long time and have written code in languages most of you have never heard of. None of them were as hard to learn as Rust and I include Haskell, which I know well. And what's the reward for all this diffculty? No GC. The Rust community's allergy to garbage collection is totally overblown, in my opinion. Yes, garbage collected languages are not good choices for embedded and/or real-time code. But ordinary applications? On today's multi-Ghz 32 Gb hardware? And the cost of avoiding garbage collection is great. You need to hear the observations of more people like me who know both Rust and D and I think the tune will be much the same -- D is much easier to learn and easier to use. The resulting code? D code runs fast, in my experience it is as fast as Rust, within a very acceptable tolerance. And benchmarks I've seen online confirm this. Why is an uninteresting language like Go so much more popular than Rust? It's easy to learn, gives acceptable performance, and has a rich environment. So before cluttering the D environment with an unneeded me-too effort (my opinion!) and wasting scarce developer resources on this when there is so much else to be done that D needs more than a borrow-checker, I urge you to think very carefully about this.
Feb 28
On 2/28/24 23:01, Don Allen wrote:As I've said in previous posts, I've written a lot of Rust and now a fair amount of D. I prefer D as it is, now that I've solved some issues with my own lack of knowledge about the available tools. I want to make a comment about all this chatter about move semantics. I freely admit that I have not read the proposal for doing this, if there is a fully-fleshed-out proposal, and I probably never will. ...Well, suit yourself.My issue is this: Rust has led the move semantics charge.Linear typing is a very old idea.Grafting move semantics onto feels to me like me-too-ism and just cluttering the D environment with yet another wart that it does not need.No, the warts are here now. Move semantics is a way to get rid of existing warts.Move semantics in Rust results in the programmer becoming an key part of the memory management system, whether they realize it or not.It's just another tool. Use it when it is appropriate.Anyone who has wrestled with the borrow checker or gotten themselves into lifetime hell with Rust will understand (unless they've drunk the Kool-Aid) what I am talking about.Move semantics is not the same as borrowing.... So before cluttering the D environment with an unneeded me-too effort (my opinion!) and wasting scarce developer resources on this when there is so much else to be done that D needs more than a borrow-checker, I urge you to think very carefully about this.You seem to confuse DIP1000 with move constructors. Move semantics is one of the things D needs more than a borrow checker. Anyway, move constructors _are_ among the top 10 priorities now for the language.
Feb 28
I do share your concern, Don. I worry that we know so well how C++ does it that we can't conceive of a better way. If we cannot do mucho better than C++ with this, why are we using D? D already does some things so much better than C++ by thinking "dammit there's got to be a better way than this mess!" Things like no ADL, decent modules, simple template syntax, easy to understand overloading, context-free grammar, slices, etc.
Feb 28
On Thursday, 29 February 2024 at 04:24:02 UTC, Walter Bright wrote:I do share your concern, Don. I worry that we know so well how C++ does it that we can't conceive of a better way. If we cannot do mucho better than C++ with this, why are we using D? D already does some things so much better than C++ by thinking "dammit there's got to be a better way than this mess!" Things like no ADL, decent modules, simple template syntax, easy to understand overloading, context-free grammar, slices, etc.Try using DFA technology not during normal compilation in the compiler, but in an alternative checking mode that maintains compilation performance while allowing program-assisted analysis.
Feb 28
On Thursday, 29 February 2024 at 05:51:32 UTC, electricface wrote:On Thursday, 29 February 2024 at 04:24:02 UTC, Walter Bright wrote:Try to linguistically mark the last usage of a variable, and then develop a fast verification algorithm to improve the performance of analyzing the last usage of variables. It can also use expensive but comprehensive algorithm to guide people in marking the last usage of a variable.I do share your concern, Don. I worry that we know so well how C++ does it that we can't conceive of a better way. If we cannot do mucho better than C++ with this, why are we using D? D already does some things so much better than C++ by thinking "dammit there's got to be a better way than this mess!" Things like no ADL, decent modules, simple template syntax, easy to understand overloading, context-free grammar, slices, etc.Try using DFA technology not during normal compilation in the compiler, but in an alternative checking mode that maintains compilation performance while allowing program-assisted analysis.
Feb 29
On Thursday, 29 February 2024 at 04:24:02 UTC, Walter Bright wrote:I do share your concern, Don. I worry that we know so well how C++ does it that we can't conceive of a better way. If we cannot do mucho better than C++ with this, why are we using D? D already does some things so much better than C++ by thinking "dammit there's got to be a better way than this mess!" Things like no ADL, decent modules, simple template syntax, easy to understand overloading, context-free grammar, slices, etc.I've expressed sentiments like this before, Walter, most recently at the time of the fork kerfuffle. I really value your careful, let's-think-it-through engineer's approach regarding the evolution of D. I'm not suggesting that the young-uns are always wrong. Quite the contrary; there are some very bright young people pushing D in various ways. The problem is that they aren't always right, which creates a wheat-from-the-chaff role for the project leader, sometimes requiring rejecting changes backed by many and other times implementing things that lack enthusiastic support, e.g, ImportC, which I hope people are beginning to understand that it is a major asset for D. I think you are doing a superb job of walking this fine line.
Mar 01
On 2/28/2024 1:03 PM, Timon Gehr wrote:No, the idea is that the compiler enforces that it is indeed the last use and produces a compile-time error message if it cannot prove that it is the case.DFA works with mathematical reliability (absent compiler bugs). The optimizer relies heavily on DFA; if DFA was unreliable the whole edifice will fall apart. Leave move vs copy to the compiler. The move vs copy/destroy choice is an optimization, and should be semantically thought of it that way.Yes. It still holds that one may want to make sure that a value is really moved at a given point. Sometimes this matters. Anyway, this is by far not the most important point.If more language features are needed to work around bugs in the DFA, you've failed as a language designer/implementer. :-/ Last use DFA can be implemented in a mathematically correct manner. The downside to DFA is it slows down the compiler, which concerns me in adding it to the front end semantics. I'm guessing is that's a reason why Rust has a reputation for slow compiles. (C++ doesn't have an excuse!)Interesting that you bring that up. I've been slowly leaning towards the "single assignment" style, where a variable is only assigned to once, when it is initialized. Sort of a "head const" thing. Some languages enforce this (can't remember which ones). I find it makes code more readable. I get the feeling that allowing not only the contents, but the type of the variable change after re-assignment makes for less coherent code. I'm not sure why, but I find Rust code hard to read. Maybe that's part of it. I like the O/B system so much I implemented it in D ( live), not only that, but have begun adopting it as my own coding style. And it looks sooo much nicer in D syntax!A nice feature of this is that the type of a variable can be changed on redeclaration. Note that Rust allows this.This is a relatively common idiom in languages that support moves. It is annoying if you have to invent a new name for each intermediate result. One use case would be type state: File!(FileState.flushed) file = open("file.txt"); File!(FileState.buffered) file = file.write("hello "); File!(FileState.buffered) file = file.writeln("world!"); // file.close(); // compile time error File!(FileState.flushed) file = file.flush(); file.close(); // moves "file" // file.write("hello"); // compile time error Assume you have some declarations like these and you want to comment out part of the statements. It would now be annoying to have to rename variables. I.e., you want to use the same name for different versions of the same thing, similarly to how you do not have to change the name of a variable when assigning to it.I've made that error myself now and then, usually as a result of moving code lines about.We already disallow shadowing declarations, and that has prevented a number of bugs at least in my own code (they're very difficult to spot with a visual check).The reason why shadowing is error prone is that multiple variables with overlapping lifetimes are in scope and the compiler arbitrarily picks one of them. This case is different, as only one variable of the same name exists at any given time. This is not error prone.Requiring unique names is more error prone in this case, as you can accidentally copy an older version of a variable.I can't remember making that error :-/Anyway, this is not the most important thing, please do check out the points I initially included in my review. This point is just something I had forgotten to include.Of course. This was just an easy to respond to issue. But a caveat. I'm kinda swamped at the moment. I'm working on some cool stuff for the upcoming DConf. I also wrote the Move/Copy/Forward DIP before I worked on the O/B system for D. The whole Move/Copy/Forward needs to be studied in the context of how it fits in with O/B. This is going to need some careful study.
Feb 28
On Thursday, 29 February 2024 at 04:17:38 UTC, Walter Bright wrote:On 2/28/2024 1:03 PM, Timon Gehr wrote:Not always. Some types have to be move-only.No, the idea is that the compiler enforces that it is indeed the last use and produces a compile-time error message if it cannot prove that it is the case.DFA works with mathematical reliability (absent compiler bugs). The optimizer relies heavily on DFA; if DFA was unreliable the whole edifice will fall apart. Leave move vs copy to the compiler. The move vs copy/destroy choice is an optimization, and should be semantically thought of it that way.
Feb 29
On Thursday, February 29, 2024 3:10:30 AM MST Atila Neves via Digitalmars-d wrote:On Thursday, 29 February 2024 at 04:17:38 UTC, Walter Bright wrote:Yeah. One of the proposed ways to handle basic input ranges with Phobos v3 is to make them be move-only / non-copyable (and really, it's either that or force them all to be reference types, both of which come with their own issues). And if we do decide to make basic input ranges non-copyable, then explicit moves are very much going to be a thing with them, and the more type-safe we can make that the better. Being able to have the compiler do automatic moves on last use would also be huge for that, since it would drastically reduce the number of explicit moves which would be required, but there would still be times when it would have to be explicit. Also, I'm pretty sure that the folks who have been the most motivated with regards to move constructors and move semantics (e.g. the folks at Weka) have wanted it explicitly because of issues surrounding non-copyable types. So, while having moves as an optimization is important (and can be big for both performance in general and for usability with non-copyable types), I don't think that that's actually the primary motivator behind the DIP in question - at least not with regards to the folks who were pushing for it to be created in the first place. - Jonathan M DavisOn 2/28/2024 1:03 PM, Timon Gehr wrote:Not always. Some types have to be move-only.No, the idea is that the compiler enforces that it is indeed the last use and produces a compile-time error message if it cannot prove that it is the case.DFA works with mathematical reliability (absent compiler bugs). The optimizer relies heavily on DFA; if DFA was unreliable the whole edifice will fall apart. Leave move vs copy to the compiler. The move vs copy/destroy choice is an optimization, and should be semantically thought of it that way.
Feb 29
On 2/29/24 05:17, Walter Bright wrote:On 2/28/2024 1:03 PM, Timon Gehr wrote:This is not about catching bugs in the DFA. This is about allowing a reader of the code to not have to execute the DFA in their head in order to see that something is the last use.No, the idea is that the compiler enforces that it is indeed the last use and produces a compile-time error message if it cannot prove that it is the case.DFA works with mathematical reliability (absent compiler bugs). The optimizer relies heavily on DFA; if DFA was unreliable the whole edifice will fall apart. Leave move vs copy to the compiler. The move vs copy/destroy choice is an optimization, and should be semantically thought of it that way.Yes. It still holds that one may want to make sure that a value is really moved at a given point. Sometimes this matters. Anyway, this is by far not the most important point.If more language features are needed to work around bugs in the DFA, you've failed as a language designer/implementer. :-/ ...Last use DFA can be implemented in a mathematically correct manner.No, it is undecidable. You can only implement a sound approximation.The downside to DFA is it slows down the compiler, which concerns me in adding it to the front end semantics. I'm guessing is that's a reason why Rust has a reputation for slow compiles. (C++ doesn't have an excuse!) ...Last-use analysis on a control-flow graph can be implemented in an efficient manner using strongly connected components. It is even easier to compute on a structured program. Rust has no goto.My example in fact follows that style. Each variable is a separate variable that is initialized once.Interesting that you bring that up. I've been slowly leaning towards the "single assignment" style, where a variable is only assigned to once, when it is initialized. Sort of a "head const" thing. Some languages enforce this (can't remember which ones). I find it makes code more readable. ...A nice feature of this is that the type of a variable can be changed on redeclaration. Note that Rust allows this.This is a relatively common idiom in languages that support moves. It is annoying if you have to invent a new name for each intermediate result. One use case would be type state: File!(FileState.flushed) file = open("file.txt"); File!(FileState.buffered) file = file.write("hello "); File!(FileState.buffered) file = file.writeln("world!"); // file.close(); // compile time error File!(FileState.flushed) file = file.flush(); file.close(); // moves "file" // file.write("hello"); // compile time error Assume you have some declarations like these and you want to comment out part of the statements. It would now be annoying to have to rename variables. I.e., you want to use the same name for different versions of the same thing, similarly to how you do not have to change the name of a variable when assigning to it.I get the feeling that allowing not only the contents, but the type of the variable change after re-assignment makes for less coherent code. ...This is not being proposed. Redeclaration after move is not the same as reassignment.I'm not sure why, but I find Rust code hard to read. Maybe that's part of it.Plenty of unfamiliar things in Rust to throw you off.I like the O/B system so much I implemented it in D ( live), not only that, but have begun adopting it as my own coding style. And it looks sooo much nicer in D syntax! ...live, while sharing some concepts, is ultimately not even the same kind of thing.Well, you can always make an error by moving some code lines into a scope in which they do not fit, but in this case, if you accidentally redeclare a variable of a name that already exists, you get a compile-time error.I've made that error myself now and then, usually as a result of moving code lines about. ...We already disallow shadowing declarations, and that has prevented a number of bugs at least in my own code (they're very difficult to spot with a visual check).The reason why shadowing is error prone is that multiple variables with overlapping lifetimes are in scope and the compiler arbitrarily picks one of them. This case is different, as only one variable of the same name exists at any given time. This is not error prone.Presumably you used in-place updates in cases where it would have been hard to keep track of different versions.Requiring unique names is more error prone in this case, as you can accidentally copy an older version of a variable.I can't remember making that error :-/ ...I do feel like the response was shot from the hip.Anyway, this is not the most important thing, please do check out the points I initially included in my review. This point is just something I had forgotten to include.Of course. This was just an easy to respond to issue. ...But a caveat. I'm kinda swamped at the moment. I'm working on some cool stuff for the upcoming DConf.Of course! :)I also wrote the Move/Copy/Forward DIP before I worked on the O/B system for D. The whole Move/Copy/Forward needs to be studied in the context of how it fits in with O/B. This is going to need some careful study.Sure. (This was also one of the things I pointed out.)
Feb 29
On Thursday, 29 February 2024 at 04:17:38 UTC, Walter Bright wrote:DFA works with mathematical reliability (absent compiler bugs). The optimizer relies heavily on DFA; if DFA was unreliable the whole edifice will fall apart. Leave move vs copy to the compiler. The move vs copy/destroy choice is an optimization, and should be semantically thought of it that way.I'm a bit worried this would lead to implementation-defined behaviour on whether uses of a struct with a disabled copy constructor would compile. But otherwise I agree with this. Well, the spec currently doesn't allow these elision in all cases, for example it says: ```D struct A { this(ref return scope A another) {} } void fun(A a) {} void main() { A a; fun(a); // copy constructor gets called } ``` ...but I tend to agree it should be implementation-defined whether the copy constructor is called here.
Feb 29
On 29/02/2024 3:17 AM, Timon Gehr wrote:12. Regarding lifetimes. You make a point about nested functions and lambdas. However, this is not the only problem. Consider: |struct S{ int x; } int foo() safe{ S s; scope p = &s.x; bar(s); // last use of s, moved return *p; // bad memory access }|I've been exploring this problem recently with isolated. What this suggests to me is that all move operator supporting types are implicitly isolated. Both s and p would belong to the same subgraph, when s gets moved, the subgraph that s and p are in would get invalidated making the return not possible. Interesting, there appears to be some inter-relationship here that I had not considered.
Feb 28
On Wednesday, 28 February 2024 at 14:17:44 UTC, Timon Gehr wrote:- It would make sense to elaborate on ` disable`d copy constructors. This is similar to not implementing the `Copy` trait in Rust. The resulting values can only be moved.Also, no discussion of ` disable`d move constructors.
Feb 28
On Wednesday, February 28, 2024 7:17:44 AM MST Timon Gehr via Digitalmars-d wrote:I've already run into issues trying to add copy constructors at work where code broke because it had declared this(T)(T t) { ...} and that then conflicted with a copy constructor which was automatically added when I added a copy constructor to the type of one its member variables. Fortunately, the result was a compiler error, and I could then add a template constraint to avoid having the templated constructor take the type that it was constructing, but the risks with something like this suddenly being turned into a move constructor certainly should be considered. In some cases, such constructors likely work just fine when given a variable of the type that they're constructing, whereas in others, they're probably never actually used that way and wouldn't compile if you tried. So, if such a constructor became a move constructor, a type that was perfectly moveable before could become an error to try to move - or do something that is very much not a move if the constructor happens to compile when given a variable of the type that it's constructing, but it does something rather different from a move. Of course, to an extent, the same could be said of this(S s) { ... } though I would guess that in most cases, having that turned into a move constructor would work, whereas a templated constructor would be much more likely to not work with the type being constructed. Another idea here would be that if we required something like an attribute on the constructor - e.g. movecons - then the compiler could not only see that you intended to make it a move constructor, but it could give you an error if it didn't have the appropriate signature. I'm not sure that we really want to go that route, but if we did require an attribute, at least we wouldn't accidentally have existing constructors turn into move constructors - and having the compiler verify for you that it's treating it as a move constructor could be valuable.A Move Constructor is a struct member constructor that moves, rather than copies, the argument corresponding to its first parameter into the object to be constructed. The argument is invalid after this move, and is not destructed. A Move Constructor for struct S is declared as: ```d this(S s) { ... } ```This is a breaking language change. Also, consider ```d struct S{ ... this(T)(T t){ ... } ... } ``` This constructor will be a move constructor iff T=S. Therefore, that the destructor is not called on the argument in some cases may be very surprising to programmers.4. Regarding `nothrow` on Move Constructors and Move Assignment Operators.Yeah. I don't know if it ever makes sense for move constructors to throw (e.g. arguably it shouldn't be allowed destructors to throw, though IIRC, it is currently allowed). That being said, IMHO, we should be absolutely minimizing how much we require _any_ kind of function attribute. It causes all kinds of trouble for code that doesn't want to (or can't) use them. At least with nothrow, you can do something like catch the exception and then assert false, but IMHO, in general, core language functionality should not require specific attributes. As it is, I'm having quite a bit of trouble with copy constructors and attribute requirements (e.g. getting compilation errors with regards to druntime code that insists on pure), and I need to put together some bug reports on it. Attributes can be great in theory, but they simply don't work in many cases with complex code. - Jonathan M DavisThe Move Constructor is always nothrow, even if nothrow is not explicitly specified. A Move Constructor that throws is illegal.This special case should be motivated in the DIP. I assume the motivation is that because the argument is not destructed, throwing is particularly error-prone here. In general, I would advise against built-in requirements on specified attributes unless absolutely necessary.
Feb 28
On Tue, 27 Feb 2024 at 22:16, Manu <turkeyman gmail.com> wrote:On Tue, 27 Feb 2024 at 18:01, Max Samukha via Digitalmars-d < digitalmars-d puremagic.com> wrote:asOn Tuesday, 27 February 2024 at 07:20:46 UTC, Walter Bright wrote:=F0=9F=91=86=F0=9F=91=86=F0=9F=91=86 I've been really hammering the importance of this issue for *at least* 10 years. I'm so tired of hearing myself rant, I'm just way done. I'm still =We do have a DIP on move semantics I wrote a while back: https://github.com/dlang/DIPs/blob/master/DIPs/DIP1040.md but other demands always seem to get in the way.ImportC was not a demand!convinced as ever that the single most important thing you or anybody cou=ldbe doing for the language, is fixing the move hole. I'd like to be on the DIP review panel. There are several issues that stand out to me, which I think I'll need to sleep on to digest. value for extern(C++) is a gigantic breaking change. Approaching it from that angle is an interesting idea, but I wonder if it could be really noisy. Most code passes struct/class by ref in C++; by-val structs are fairly rare, so the idea might actually work out. If symmetry or someone has a small budget, we should fly a small group of key individuals into a small room, lock the doors for a few days, and jus=tget it done.I'd like to see the DIP address this idiom (which Scott Meyers termed a 'universal reference'), which is ESSENTIAL in extern(C++) and must be covered or practically no C++ code can ever be represented: template<T> void fun(T&& universalRef); In this case; the template will be instantiated as `Ty&&` or `const Ty&` depending on the rvalue-ness of the argument supplied at the call site. I think `auto ref` needs to be covered by the DIP, and probably considered with respect to this point.
Feb 27
On 2/27/2024 4:45 AM, Manu wrote:I'd like to see the DIP address this idiom (which Scott Meyers termed a 'universal reference'), which is ESSENTIAL in extern(C++) and must be covered or practically no C++ code can ever be represented: template<T> void fun(T&& universalRef); In this case; the template will be instantiated as `Ty&&` or `const Ty&` depending on the rvalue-ness of the argument supplied at the call site. I think `auto ref` needs to be covered by the DIP, and probably considered with respect to this point.Yeah, you're right.
Feb 27
On Tuesday, 27 February 2024 at 07:56:44 UTC, Max Samukha wrote:On Tuesday, 27 February 2024 at 07:20:46 UTC, Walter Bright wrote:If you use D with betterC like c (not cpp), ImportC will work very well. If you want to use well maintained C library (some of them take hundreds of thousands of manpower hours), you will need betterC. And some library even write by cpp, still provide C interface for easy integration with other language like: rust, ruby, lua, php, java. For me ImportC is more important than DIP1040, it open the doors for very many possibilities.We do have a DIP on move semantics I wrote a while back: https://github.com/dlang/DIPs/blob/master/DIPs/DIP1040.md but other demands always seem to get in the way.ImportC was not a demand!
Feb 27
On Wednesday, 28 February 2024 at 06:06:23 UTC, Dakota wrote:For me ImportC is more important than DIP1040, it open the doors for very many possibilities.I'm resorting to an argumentum ad industriam: move constructors have been explicitly demanded for at least a decade by the prominent industrial users.
Feb 28
On Tuesday, 27 February 2024 at 02:28:32 UTC, Manu wrote:Walter: It's been too long, there are still no containers, which is embarrassing and stdcpp is still blocked on this.You're absolutely right, it's just that there's no `container library`, and `'stdcpp'` is a bit unstable.
Feb 26
On Tuesday, 27 February 2024 at 07:44:26 UTC, zjh wrote:On Tuesday, 27 February 2024 at 02:28:32 UTC, Manu wrote:Trying to build that at: https://github.com/Inochi2D/numem And indeed we hit the lack of move semantics, as it's possible to make a vector<shared_ptr!T> but of course vector<unique_ptr!T> encounters issues (it's very much like trying to do a vector of auto_ptr in C++98). Curious what Robert will say about how to make proper containers.Walter: It's been too long, there are still no containers, which is embarrassing and stdcpp is still blocked on this.You're absolutely right, it's just that there's no `container library`, and `'stdcpp'` is a bit unstable.
Feb 27
On Tuesday, 27 February 2024 at 10:57:00 UTC, Guillaume Piolat wrote:Trying to build that at: https://github.com/Inochi2D/numemThank you for your infomation.
Feb 27
On Tuesday, 27 February 2024 at 02:28:32 UTC, Manu wrote:Walter: It's been too long, there are still no containers,Btw will be interesting to know the list of containers you demand. Because many times people spoke about containers, but there are hundreds of them (as Mike mentioned some families. I would also add probabilistic containers to the party).. People from industry are usually talking like "containers from std are good only for simple things. If you want to solve some real and hard problem - you have to implement your specific/custom version of the container exactly for your particular problem set". So containers from std should be just simple to use and integrated well.. but if you need performance/low memory footprint, you will implement it from scratch anyway. And I think it is worth to mention https://code.dlang.org/packages/ikod-containers - nogc, safe containers that worked well for me. It has a few structures, but could be base for other implementations. Rikki: This answer sounds like "come back in 10 years" :)
Feb 27
On 27/02/2024 9:44 PM, Sergey wrote:Rikki: This answer sounds like "come back in 10 years" :)Lol yes, if you want it to be all tidy with answers to many use cases with clear solutions it's like that. If however you want hope that things are going to change and we won't keep doing what we've done, then it is hopeful too!
Feb 27
On Tuesday, 27 February 2024 at 08:44:59 UTC, Sergey wrote:And I think it is worth to mention https://code.dlang.org/packages/ikod-containers - nogc, safe containers that worked well for me. It has a few structures, but could be base for other implementations.Maybe [tanya](https://code.dlang.org/packages/tanya) can help as well, as package or base.
Feb 27
On Tuesday, 27 February 2024 at 08:56:10 UTC, Danilo wrote:On Tuesday, 27 February 2024 at 08:44:59 UTC, Sergey wrote:Forgot to add https://github.com/dlang-community/containers before pressing (Send) 😏And I think it is worth to mention https://code.dlang.org/packages/ikod-containers - nogc, safe containers that worked well for me. It has a few structures, but could be base for other implementations.Maybe [tanya](https://code.dlang.org/packages/tanya) can help as well, as package or base.
Feb 27