Skip to content

[revived] Make std.typecons.Rebindable work with structs#6136

Closed
wilzbach wants to merge 8 commits intodlang:masterfrom
wilzbach:pr-4363
Closed

[revived] Make std.typecons.Rebindable work with structs#6136
wilzbach wants to merge 8 commits intodlang:masterfrom
wilzbach:pr-4363

Conversation

@wilzbach
Copy link
Copy Markdown
Contributor

@wilzbach wilzbach commented Feb 7, 2018

Revival of #4363

CC @ntrel

I think only the opAssign problem needs to be resolved, apart from that it looks good.

@dlang-bot
Copy link
Copy Markdown
Contributor

dlang-bot commented Feb 7, 2018

Thanks for your pull request, @wilzbach!

Bugzilla references

Your PR doesn't reference any Bugzilla issue.

If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog.

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub fetch digger
dub run digger -- build "master + phobos#6136"

@wilzbach wilzbach changed the title Make std.typecons.Rebindable work with structs [revived] Make std.typecons.Rebindable work with structs Feb 7, 2018
@n8sh
Copy link
Copy Markdown
Member

n8sh commented Feb 8, 2018

    static if (!is(S == immutable))
    private ref S Rebindable_getRef() @property
    {
        // payload exposed as const ref when S is const
        return trustedPayload;
    }

    static if (is(S == immutable))
    private S Rebindable_get() @property
    {
        // we return a copy for immutable S
        return trustedPayload;
    }

Could you explain why this is necessary?

@wilzbach
Copy link
Copy Markdown
Contributor Author

wilzbach commented Feb 8, 2018

Note that I just revived this PR from the dead because I think it's useful - I'm not the original author (@ntrel is).

However, from what I understand, the problem without the getRef trick, the compiler will complain:

Rebindable!(const S) rs;
rs.ptr = null; // Error: cannot modify const expression rs.Rebindable_get().ptr
s = rs; // Error: cannot implicitly convert expression rs of type Rebindable!(const(S)) to S

However, if the getRef code is used for immutable, no temporary variable would be created and thus resulting in i.e. the postblit not being called:

unittest
{
    int del;
    int post;
    struct S
    {
        int* ptr;
        int level;
        this(this) {
            post++;
            level++;
        }
        ~this() {
            del++;
        }
    }

    // test const
    {
        Rebindable!(const S) rc = S(new int);
        assert(post == 1);
        assert(rc.level == 1);
    }
    assert(post == 1);
    assert(del == 2);
    del = 0, post = 0;

    import std.conv;

    // immutable
    {
        // on every field access a temporary immutable copy gets created
        Rebindable!(immutable S) ri = S(new int);
        assert(post == 1);
        assert(ri.level == 2); // <- would be 1
    }
}

Maybe @ntrel can explain this better?


/// ditto
Rebindable!S rebindable(S)(S s)
if (is(S == struct) && !isInstanceOf!(Rebindable, S))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If S is already Rebindable!struct this can just return s.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's already the existing overload Rebindable!T rebindable(T)(Rebindable!T obj) - though they should probably be unified/combined into overload, but that's something that will increase the diff and thus I would prefer to do at the end or in a quick follow-up.

* temporary copy of `S` in order to enforce _true immutability.
*/
template Rebindable(S)
if (is(S == struct))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By that same token, what if S is already a Rebindable?

void emplace(ref S s)
{
import std.conv : emplace;
static if (__traits(compiles, () @safe {S tmp = s;}))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not check S's constructor directly for @safe/@trusted?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MetaLang What would that look like?

Copy link
Copy Markdown
Member

@MetaLang MetaLang Feb 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import std.traits;

struct Test
{
    this(Test other) @safe {}
}

void main()
{
    pragma(msg, isSafe!(__traits(getMember, Test, "__ctor")));
}

https://run.dlang.io/is/hgusNS

Note that isSafe returns true if an @safe or @trusted constructor exists, so even if the type in question has an @system constructor, this will work. It will only return false in the case that you can't safely construct an S from another S.

std/typecons.d Outdated
// mutPayload's pointers must be treated as tail const
void[S.sizeof] mutPayload;

void emplace(ref S s)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about renaming this emplacePayload, or something similar?

@wilzbach wilzbach added the Review:WIP Work In Progress - not ready for review or pulling label Feb 12, 2018
std/typecons.d Outdated

static if (!is(S == immutable))
ref S Rebindable_getRef() @property
private ref S Rebindable_getRef() @property
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't use private for an alias this symbol, because it won't be accessible outside this module. Ditto below.

std/typecons.d Outdated
static if (is(S == immutable))
private S Rebindable_get() @property
{
// we return a copy for immutable S
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wilzbach re: #6136 (comment)

However, if the getRef code is used for immutable, no temporary variable would be created and thus resulting in i.e. the postblit not being called

I phrased my question unclearly. Why is this necessary when S == immutable if it's not necessary when S == const?

@jmdavis
Copy link
Copy Markdown
Member

jmdavis commented Mar 26, 2018

Honestly, my reaction is that this really doesn't make sense. The whole point of Rebindable is to be able to do the equivalent of const(T)* or immutable(T)* with classes. If you have a struct, just put it on the heap, and then you can do const(T)* or immutable(T)* explicitly. And if it's not on the heap, then it fundamentally doesn't make sense for it be used with Rebindable. This just seems like a lot of code for duplicating what the type system can already trivially do for structs by using pointers.

@ntrel
Copy link
Copy Markdown
Contributor

ntrel commented Mar 29, 2018

@jmdavis Here's the motivation for this pull:

T maxElement(R, T = typeof(R.init.front))(R r)
{
    Rebindable!T max = r.front;
    foreach (e; r.dropOne)
        if (e > max)
            max = e;
    return max;
}

Suppose typeof(r.front) is a constant struct with a reference field. You can't declare max as Unqual!T, because a constant reference field is not convertible to a mutable reference. But that is what std.algorithm.searching.maxElement does (the code is in extremum), so that implementation is broken for this kind of struct.

The idea is that with this pull as a prerequisite, we can make Rebindable accept value types for use in generic code as in the above example. This makes for clean and correct generic code. Given this struct use case, there is no reason to artificially restrict Rebindable only to reference types. It would also be unnecessarily restrictive to not allow algorithms to operate on ranges of such structs.

@ntrel
Copy link
Copy Markdown
Contributor

ntrel commented Apr 2, 2018

@wilzbach I made a pull to improve the doc wording & fix private alias this getters: wilzbach#14

@wilzbach
Copy link
Copy Markdown
Contributor Author

wilzbach commented Apr 2, 2018

Suppose typeof(r.front) is a constant struct with a reference field. You can't declare max as Unqual!T, because a constant reference field is not convertible to a mutable reference. But that is what std.algorithm.searching.maxElement does (the code is in extremum), so that implementation is broken for this kind of struct.
The idea is that with this pull as a prerequisite, we can make Rebindable accept value types for use in generic code as in the above example. This makes for clean and correct generic code. [...]

Yep, funnily enough I even have a PR to fix maxElement for const in the pipe that use the RebindableOrUnqual workaround to hack around the problem (#6157).

@ntrel
Copy link
Copy Markdown
Contributor

ntrel commented Apr 12, 2018

@n8sh Escaping a const ref T still allows the T struct data itself to change (shallowly), just not through that ref. Escaping immutable ref T does not, it requires the T data itself to be fully immutable. As the Rebindable data is always head mutable, we must return an immutable T copy. (If we had multiple alias this, we could have both when S == immutable).

@ntrel
Copy link
Copy Markdown
Contributor

ntrel commented Apr 12, 2018

I've discovered an immutable violation:

struct S
{
	immutable int i;
}
Rebindable!(const S) r = const S(1);
immutable int* p = &r.i;
r = S(5);
assert(*p == 1); //fails

I have a fix ready, just need to finish the tests and docs. For structs with head immutable fields, we require alias this to make a copy, like we do for immutable structs.

@ntrel
Copy link
Copy Markdown
Contributor

ntrel commented Apr 24, 2018

I've discovered an immutable violation:

See wilzbach#15

ntrel and others added 8 commits May 9, 2019 16:57
Otherwise alias this would not work outside std.typecons.
Wording was backward: `const S = S` is fine, `S = const S` disallowed.
Example of previous failing code:

struct S
{
	immutable int i;
}
Rebindable!(const S) r = const S(1);
immutable int* p = &r.i;
r = S(5);
assert(*p == 1); //fails
@atilaneves
Copy link
Copy Markdown
Contributor

I came here to review it and hopefully move this along, but given that the CI was red at the time maybe a new PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Merge:stalled Review:WIP Work In Progress - not ready for review or pulling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants