Jump to page: 1 25  
Page
Thread overview
Struct should be invalid after move
Nov 27, 2018
Sebastiaan Koppe
Nov 27, 2018
Nicholas Wilson
Nov 27, 2018
Stanislav Blinov
Nov 27, 2018
Manu
Nov 27, 2018
kinke
Nov 28, 2018
Sebastiaan Koppe
Nov 28, 2018
Jonathan M Davis
Nov 28, 2018
Sebastiaan Koppe
Nov 28, 2018
Jonathan M Davis
Nov 27, 2018
Jonathan M Davis
Nov 27, 2018
Sebastiaan Koppe
Nov 27, 2018
Jonathan M Davis
Nov 27, 2018
Stanislav Blinov
Nov 27, 2018
Jonathan M Davis
Nov 27, 2018
John Colvin
Nov 27, 2018
Sebastiaan Koppe
Nov 27, 2018
Alex
Nov 27, 2018
Sebastiaan Koppe
Nov 27, 2018
Alex
Nov 27, 2018
Stanislav Blinov
Nov 27, 2018
Alex
Nov 27, 2018
Stanislav Blinov
Nov 27, 2018
Sebastiaan Koppe
Nov 27, 2018
Alex
Nov 27, 2018
Stanislav Blinov
Nov 27, 2018
Sebastiaan Koppe
Nov 28, 2018
Stefan Koch
Nov 28, 2018
Sebastiaan Koppe
Nov 28, 2018
John Colvin
Nov 27, 2018
burjui
Nov 28, 2018
Paul Backus
Nov 30, 2018
burjui
Nov 28, 2018
sanjayss
Nov 28, 2018
Sebastiaan Koppe
Nov 28, 2018
Atila Neves
Nov 28, 2018
H. S. Teoh
Nov 28, 2018
sanjayss
Nov 28, 2018
Neia Neutuladh
Nov 28, 2018
Stanislav Blinov
Nov 28, 2018
Neia Neutuladh
November 27, 2018
I have a non-copyable struct and I really want a compiler error whenever I access it after it has been moved.

---
struct Handle {
    ...
    @disable this(this);
    ...
}

void main() {
    import std.algorithm : move;
    auto handle = getOne();
    auto second = handle.move;  /// line 14
    auto third = handle.move;    ///  <- compiler error, variable handle is invalid after line 14
}
---

I believe this would prevent some nasty bugs when dealing with these structs.

What do you think?
November 27, 2018
On Tuesday, 27 November 2018 at 08:00:22 UTC, Sebastiaan Koppe wrote:
> I have a non-copyable struct and I really want a compiler error whenever I access it after it has been moved.
>
> ---
> struct Handle {
>     ...
>     @disable this(this);
>     ...
> }
>
> void main() {
>     import std.algorithm : move;
>     auto handle = getOne();
>     auto second = handle.move;  /// line 14
>     auto third = handle.move;    ///  <- compiler error, variable handle is invalid after line 14
> }
> ---
>
> I believe this would prevent some nasty bugs when dealing with these structs.
>
> What do you think?

we need an `@invalidate`s attribute for that, would apply to pointers passed to free etc. Probably would need a DIP though. I like it.
November 27, 2018
On Tuesday, 27 November 2018 at 08:37:32 UTC, Nicholas Wilson wrote:
> On Tuesday, 27 November 2018 at 08:00:22 UTC, Sebastiaan Koppe wrote:
>> I have a non-copyable struct and I really want a compiler error whenever I access it after it has been moved.
>>
>> ---
>> struct Handle {
>>     ...
>>     @disable this(this);
>>     ...
>> }
>>
>> void main() {
>>     import std.algorithm : move;
>>     auto handle = getOne();
>>     auto second = handle.move;  /// line 14
>>     auto third = handle.move;    ///  <- compiler error, variable handle is invalid after line 14
>> }
>> ---
>>
>> I believe this would prevent some nasty bugs when dealing with these structs.
>>
>> What do you think?
>
> we need an `@invalidate`s attribute for that, would apply to pointers passed to free etc. Probably would need a DIP though. I like it.

Yeah, that could be an awesome addition. However, it's not as simple, because this should be legal too:

```
auto handle = getOne();
auto second = handle.move; // `handle` becomes invalid
// ...
handle = getOne(); // `handle` is valid again
```
November 27, 2018
On Tuesday, November 27, 2018 1:00:22 AM MST Sebastiaan Koppe via Digitalmars-d wrote:
> I have a non-copyable struct and I really want a compiler error whenever I access it after it has been moved.
>
> ---
> struct Handle {
>      ...
>      @disable this(this);
>      ...
> }
>
> void main() {
>      import std.algorithm : move;
>      auto handle = getOne();
>      auto second = handle.move;  /// line 14
>      auto third = handle.move;    ///  <- compiler error, variable
> handle is invalid after line 14
> }
> ---
>
> I believe this would prevent some nasty bugs when dealing with these structs.
>
> What do you think?

Well, the DIP to add opPostMove has been approved, and if opPostMove is then @disabled, then that should disable moving entirely (making it a compile error for it to happen at all, not to access the struct after it's been moved), which is probably a better approach. However, that will require that the DIP actually be implemented, and AFAIK, there's no ETA on that.

- Jonathan M Davis



November 27, 2018
On Tuesday, 27 November 2018 at 08:00:22 UTC, Sebastiaan Koppe wrote:
> I have a non-copyable struct and I really want a compiler error whenever I access it after it has been moved.
>
> ---
> struct Handle {
>     ...
>     @disable this(this);
>     ...
> }
>
> void main() {
>     import std.algorithm : move;
>     auto handle = getOne();
>     auto second = handle.move;  /// line 14
>     auto third = handle.move;    ///  <- compiler error, variable handle is invalid after line 14
> }
> ---
>
> I believe this would prevent some nasty bugs when dealing with these structs.
>
> What do you think?

void foo(int a)
{
    import std.algorithm : move;
    auto handle = getOne();
    if (a > 0)
        auto second = handle.move;
    auto third = handle.move; // compile error?
}

Not a trivial problem in the general case.
November 27, 2018
On Tuesday, 27 November 2018 at 09:39:13 UTC, Jonathan M Davis wrote:
> Well, the DIP to add opPostMove has been approved, and if opPostMove is then @disabled, then that should disable moving entirely (making it a compile error for it to happen at all, not to access the struct after it's been moved), which is probably a better approach. However, that will require that the DIP actually be implemented, and AFAIK, there's no ETA on that.
>
> - Jonathan M Davis

But I actually want to move it. Just so that the variable moved from is considered invalid afterwards.
November 27, 2018
On Tuesday, 27 November 2018 at 09:50:25 UTC, John Colvin wrote:
> void foo(int a)
> {
>     import std.algorithm : move;
>     auto handle = getOne();
>     if (a > 0)
>         auto second = handle.move;
>     auto third = handle.move; // compile error?
> }
>
> Not a trivial problem in the general case.

Not a trivial problem indeed. But I really believe we need these things (or similar) for safety's sake.

One other issue I can see is if the handle is passed by ref into opaque function. At this point we can't reason about whether the struct is still valid after the call to bar:

---
void bar(ref Handle);

void main() {
  auto handle = getOne();
  bar(handle);
  auto second = handle.move(); // compile error? don't know
}
---

Possible solution is to always disallow move after passing as ref.

And maybe move could be disallowed on a scope ref param, then we could safely pass Handle to functions that take by ref while provably keeping the caller's Handle valid.
November 27, 2018
On Tuesday, 27 November 2018 at 10:53:21 UTC, Sebastiaan Koppe wrote:
>
> Not a trivial problem indeed. But I really believe we need these things (or similar) for safety's sake.
>
> One other issue I can see is if the handle is passed by ref into opaque function. At this point we can't reason about whether the struct is still valid after the call to bar:
>
> ---
> void bar(ref Handle);
>
> void main() {
>   auto handle = getOne();
>   bar(handle);
>   auto second = handle.move(); // compile error? don't know
> }
> ---
>
> Possible solution is to always disallow move after passing as ref.
>
> And maybe move could be disallowed on a scope ref param, then we could safely pass Handle to functions that take by ref while provably keeping the caller's Handle valid.

There exist

auto val = Handle.init;

1. How do you treat this?
2. Why do you don't want to treat the handle after movement the same way?
November 27, 2018
On Tuesday, 27 November 2018 at 10:59:03 UTC, Alex wrote:
> There exist
>
> auto val = Handle.init;
>
> 1. How do you treat this?
I have no idea. Logically I would say that - in my case - the val is invalid.

> 2. Why do you don't want to treat the handle after movement the same way?
Because the handle refers to an underlying resource, and any access to that resource through that handle is invalid after a move. Much like one doesn't want to call .release() twice on an unique(T) wrapper type.

Sure, I could put in a runtime check, but then I get runtime errors and I rather have compile time errors.
November 27, 2018
On Tuesday, November 27, 2018 3:32:40 AM MST Sebastiaan Koppe via Digitalmars-d wrote:
> On Tuesday, 27 November 2018 at 09:39:13 UTC, Jonathan M Davis
>
> wrote:
> > Well, the DIP to add opPostMove has been approved, and if opPostMove is then @disabled, then that should disable moving entirely (making it a compile error for it to happen at all, not to access the struct after it's been moved), which is probably a better approach. However, that will require that the DIP actually be implemented, and AFAIK, there's no ETA on that.
> >
> > - Jonathan M Davis
>
> But I actually want to move it. Just so that the variable moved from is considered invalid afterwards.

Well, if the compiler were doing the move, then there would be no move if there were any possibility of the variable being used after it was moved. So, basically, you're looking at the case where an @system function that the compiler almost ceratinly does not understand is doing any moves has moved an object, and you want the compiler to then complain if the variable is used after the move. That would require some way for the compiler to be told that a move has occurred, which is likely possible, but I don't know how it could be reasonably done - especially since the move could be happening inside a function that the compiler has no visibility into. We'd probably have to add yet another attribute to indicate that a function is doing a move, and I think that most everyone would consider that to be a terrible idea.

The other issue that comes to mind is that there are cases where you actually _do_ want to use a variable after a move has occurred - usually because you want to move something else into that variable (e.g. with swap). So, even if the compiler could perfectly detect when a move had occurred, it would then have to somehow know when using the variable again was then okay and when it wasn't. And I'm not sure that that's really solvable. If it already could detect all moves, then it could know whether another move was being attempted and consider that case safe, but I doubt that it could be made to understand when using opAssign would be safe. In many cases, it would not be, but for some types it would be (in particular when opAssign doesn't care about the previous value). So, it seems like trying to detect what you're trying to detect could easily require flagging cases which are actually valid as being illegal. All in all, at best, this just sounds like it would be adding a lot of extra complexity for detecting problems with an operation that is @system by its very nature and which most programs aren't going to do.

If you can come up with a simple way for the kind of problem that you want detected to be detected while not having it complain about valid code, then maybe it could be implemented, but thinking about it, I'm pretty sure that a new function attribute would be required, and I don't see that flying at this point. As it is, most folks think that D has too many attributes. So, any new ones have to really be worth it, whereas this just seems like it would be dealing with an uncommon edge case. It would certainly be valuable in that context, but I seriously question that it would be worth the extra language complexity. Also, remember that Walter is likely to shoot down most solutions (to pretty much any problem) that require code flow analysis, and this seems like the kind of problem that would require it (at least if it isn't going to have false positives or miss cases which it should flag).

But if you want to solve this problem, then it looks to me like the first thing that you need to solve is how the compiler knows when a move has even occurred. And simply flagging common functions that do moves isn't going to be enough to catch all cases - especially when all it takes to hide the fact that a move occurred is to wrap the move call in another function that the compiler doesn't have visibility into thanks to separate compilation.

- Jonathan M Davis



« First   ‹ Prev
1 2 3 4 5