May 27, 2021

On Thursday, 27 May 2021 at 00:05:24 UTC, Paul Backus wrote:

>

On Wednesday, 26 May 2021 at 22:06:27 UTC, tsbockman wrote:

>

On Wednesday, 26 May 2021 at 21:48:40 UTC, Paul Backus wrote:

>

On Wednesday, 26 May 2021 at 18:53:21 UTC, vitoroak wrote:

In theory, these examples are fine, since they result in a null dereference,

No. That's what I thought at first, too, but if you walk through the code more carefully you will see that x1 never gets set to null, and still points to the old target of u1. So, he is correct.

I've opened issue #21981 requesting a fix.

Thanks, I see the problem now.

I guess the conclusion we're forced to come to is that, given current language rules, it is incorrect to mark the destructor as @trusted.

Yeah, my point is that if there's any way to make this @safe. The same happens for a Vector implementation where you can call push (that can reallocate) while having a reference to an element.

I also don't know a simple way to solve this problem but I think it's important if we want to sell D to have @nogc data structures.

May 27, 2021

On Thursday, 27 May 2021 at 00:20:44 UTC, vitoroak wrote:

>

The same happens for a Vector implementation where you can call push (that can reallocate) while having a reference to an element.

Although it's not shown in my earlier code example, I did recognize the problem of reassignment/reallocation potentially invalidating extant borrowed references in my full system. (But, I missed the problem of manual destructor calls.)

My solution for reassignment/reallocation was simply to make opAssign, unique move operations, etc. @system. Of course this is a significant limitation, but I think there is still a lot of value in a reference counting system if the rest of its operations can be @trusted.

But, if the destructor has to be @system too, then it is no longer possible to use the reference counting system for any practical purpose in @safe code. For myself, I will just cheat and leave the destructor @trusted anyway, because why would I manually call it in @safe code to begin with? But, we'll need a better answer than that for the standard library.

May 27, 2021

On Wednesday, 26 May 2021 at 22:06:27 UTC, tsbockman wrote:

>

On Wednesday, 26 May 2021 at 21:48:40 UTC, Paul Backus wrote:

>

On Wednesday, 26 May 2021 at 18:53:21 UTC, vitoroak wrote:

>

Every time I tried to do something similar in D I stumbled across the same problems and as far as I know it's not possible to implement it completely @safe today. I think one of the problems is that you can manually destroy/move any struct while there are still references/pointers to it or its internals like in the example below (I used your borrow mixin template).

In theory, these examples are fine, since they result in a null dereference,

No. That's what I thought at first, too, but if you walk through the code more carefully you will see that x1 never gets set to null, and still points to the old target of u1. So, he is correct.

I've opened issue #21981 requesting a fix.

I saw you mentioning breaking things in @safe code. This example let you access an invalid pointer without no @trusted code and heap allocation, only @safe code.

struct IntRef {
	int* ptr = void;

    this(return scope int* p) @safe {
    	ptr = p;
    }

    int* borrow() return scope @safe {
		return ptr;
    }
}

void main() @safe {
    import std.stdio: writeln;

    auto x = 1;
    auto r = IntRef(&x);

    writeln(*r.borrow);

	destroy!true(r);

    writeln(*r.borrow);
}
May 27, 2021

On Thursday, 27 May 2021 at 20:47:44 UTC, vitoroak wrote:

>

I saw you mentioning breaking things in @safe code. This example let you access an invalid pointer without no @trusted code and heap allocation, only @safe code.

struct IntRef {
	int* ptr = void;

void initializing IntRef.ptr does not create a gap in IntRef.init. IntRef.init.ptr is effectively still null.

>
    this(return scope int* p) @safe {
    	ptr = p;
    }

    int* borrow() return scope @safe {
		return ptr;
    }
}

void main() @safe {
    import std.stdio: writeln;

    auto x = 1;
    auto r = IntRef(&x);

    writeln(*r.borrow);

	destroy!true(r);

destroy!true sets r to IntRef.init, which sets r.ptr to null.

>
    writeln(*r.borrow);
}

As Paul Backus said earlier, dereferencing a null pointer is formally considered to be memory-safe in D. This is because it will (with some rare exceptions) crash the program immediately, rather than corrupting memory and continuing execution with undefined behavior.

May 27, 2021

On Thursday, 27 May 2021 at 22:13:30 UTC, tsbockman wrote:

>

As Paul Backus said earlier, dereferencing a null pointer is formally considered to be memory-safe in D. This is because it will (with some rare exceptions) crash the program immediately, rather than corrupting memory and continuing execution with undefined behavior.

That's "memory-safe" in any language in that case because that's a function of the operating system rather than the language. However, there are exception like if you are dereferencing a null pointer + offset and the offset is large, then you can corrupt memory. This is more rare though.

A program crash is the best bug you can have. A core dump can be saved and be investigated.

May 28, 2021

On Thursday, 27 May 2021 at 22:34:53 UTC, IGotD- wrote:

>

On Thursday, 27 May 2021 at 22:13:30 UTC, tsbockman wrote:

>

As Paul Backus said earlier, dereferencing a null pointer is formally considered to be memory-safe in D. This is because it will (with some rare exceptions) crash the program immediately, rather than corrupting memory and continuing execution with undefined behavior.

That's "memory-safe" in any language in that case because that's a function of the operating system rather than the language. However, there are exception like if you are dereferencing a null pointer + offset and the offset is large, then you can corrupt memory. This is more rare though.

True. I'm neither defending nor criticizing D's definition of "memory safe" here.

My goal is to achieve a similar level of safety and convenience with RC and borrowing to what D currently considers @safe with GC. @safe doesn't try to prevent null dereferences in GC code, so it shouldn't be a requirement for RC code, either.

November 20, 2021

On Friday, 14 May 2021 at 00:45:09 UTC, tsbockman wrote:

>
class D { }
a: ACCEPTS INVALID: static D = return D

I reduced this once and filed an issue for it, since it is the worst of the bunch:

"Template breaks return annotation for class reference returned by struct method"
https://issues.dlang.org/show_bug.cgi?id=22528

November 20, 2021

On Saturday, 20 November 2021 at 08:08:44 UTC, tsbockman wrote:

>

On Friday, 14 May 2021 at 00:45:09 UTC, tsbockman wrote:

>
class D { }
a: ACCEPTS INVALID: static D = return D

I reduced this once and filed an issue for it, since it is the worst of the bunch:

"Template breaks return annotation for class reference returned by struct method"
https://issues.dlang.org/show_bug.cgi?id=22528

Nice, this one should not be that hard to fix.

Does someone have time to look at it?

November 26, 2021

On Saturday, 20 November 2021 at 08:58:15 UTC, Imperatorn wrote:

>

On Saturday, 20 November 2021 at 08:08:44 UTC, tsbockman wrote:

>

"Template breaks return annotation for class reference returned by struct method"
https://issues.dlang.org/show_bug.cgi?id=22528

Nice, this one should not be that hard to fix.

Does someone have time to look at it?

Dennis partially explained the cause of the problem, and suggested a workaround. After some experimentation, I found a more general form of the workaround that eliminates all "ACCEPTS INVALID" errors in my earlier test code.

Including this line at the beginning of each offending return member function will fix them:

typeof(&this) pseudoEscape = &this ; // DMD Issue #22528 workaround.

With this change to Unique, all of the tests from my first post in this thread now pass except for e: REJECTS VALID: scope int** = &(return ref int*). As a result, the borrow mixin template is no longer needed at all. Progress!

The hackish, possibly fragile nature of the workaround, and the destructor issue still make my solution unsuitable for the standard library for now, though.

November 26, 2021

On Friday, 26 November 2021 at 04:37:16 UTC, tsbockman wrote:

>

The hackish, possibly fragile nature of the workaround, and the destructor issue still make my solution unsuitable for the standard library for now, though.

I have a runtime solution for borrow check, share it here.


struct Obj {
     size_t version;
     size_t counter;
}

struct Ptr {
        Obj* obj;
        size_t ver;
        alias this self;
        @property ref self() return scope  {
           // check ver first
           assert(ver is obj.ver);
           return *obj;
        }
}

To make this work the obj memory can not released before all Pointer released, but it can be reused for new instance, so a memory pool is used to manage every struct|class.

You need update version when object destroyed by counter.

You can make sure there is only one mut borrow for a object like RUST.

You can make WeakRef that don`t change counter, but become Option when the object released.

You can make Unique ownership with this like RUST, but with WeakRef immutable pointer.

Almost every things I want for RUST can be done from runtime, the price is managed memory pool for every object you want this kind behavior.

I use it for 3 years with my private betterC framework, It also can work cross thread with Atomic counter and version.

I give up on class and exception, use c library if there available. I use mecca swichInto Fiber with betterC, but can be graceful exit(mecca fiber never exit).

With libuv I build cross platform app for Andoird/IOS/Windows/Mac/Linux.

The binary is very small compare to C++.

The price is manage memory pool for your self for each diff kind object. This object pool can be reused without fully destroyed. (a methed call reset before return to pool)

1 2
Next ›   Last »