What ever happened to move semantics?

Timon Gehr timon.gehr at gmx.ch
Thu Feb 29 10:52:12 UTC 2024


On 2/29/24 05:17, Walter Bright wrote:
> 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. :-/
> ...

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.

> 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.

> 
>>> 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.
> 
> 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.
> ...

My example in fact follows that style. Each variable is a separate 
variable that is initialized once.

> 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.

> 
>>> 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.
> 
> I've made that error myself now and then, usually as a result of moving 
> code lines about.
> ...

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.

>> 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 :-/
> ...

Presumably you used in-place updates in cases where it would have been 
hard to keep track of different versions.

> 
>> 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.
> ...

I do feel like the response was shot from the hip.

> 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.)


More information about the Digitalmars-d mailing list