Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make assignment an expression that returns the payload lvalue #1022

Closed
andrewrk opened this issue May 25, 2018 · 6 comments
Closed

make assignment an expression that returns the payload lvalue #1022

andrewrk opened this issue May 25, 2018 · 6 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@andrewrk
Copy link
Member

andrewrk commented May 25, 2018

#This is not for syntax convenience, this is for solving a very particular problem with nullables, union(enum), and error unions.

When we want to read from a nullable we have lots of good options:

// quick
const value = nullable ?? default;

// the most powerful
if (nullable) |*payload| {
    // payload is a mutable pointer to the payload of the nullable
} else {

}

// just testing for null
const is_null = (nullable == null);

Similarly for union(enum):

// the most powerful
switch (value) {
    Tag1 => |*payload| {
        // payload is a mutable pointer to the payload of the value
    },
    Tag2 => |*payload| {
        // payload is a mutable pointer to the payload of the value
    },
    else => {

    }
}

However we run into a problem when writing to these values:

const Point = struct {
    x: i32,
    y: i32,
};
const Foo = struct {
    point: Point,
};
fn initializePoint(p: &Point) void {
    p.x = 12;
    p.y = 34;
}

var nullable_foo: ?Foo = null;

How do we use initializePoint?

This is the best thing we have:

nullable_foo = Foo(undefined);
const ptr = &??nullable;
initializePoint(&nullable.point);

There are several problems with this:

  • It's not obvious that it's possible to do this
  • It looks unsafe even though it is provably safe
    • Debug builds will have an unnecessary safety check

Once we have written a non null value, we should be able to write
to the payload.

Same thing for union(enum). Once we have chosen an active field,
the payload should be writable.

Here's my proposal to fix this:

  • Make assignment an expression that returns the payload lvalue.
  • It only works for nullables, union(enum), and error unions

That's it. Then you can do this:

const ptr = &(nullable_foo = Foo(undefined)); // @typeOf(ptr) == &Foo
initializePoint(&ptr.point);

Here's how it would look for a union(enum):

const Value = union(enum) {
    Int: i32,
    Float: f32,
};

test "aoeu" {
    var value: Value = undefined;

    const ptr = (value = Value { .Int = undefined }); // @typeOf(ptr) == &i32
}

The workaround for union(error) is not bad since we can address fields:

value = Value { .Int = undefined };
const ptr = &value.Int; // invokes runtime safety check in debug mode

If we had named return values (See #286), which would be useful for copy elision (See #287),
then we would want to use this feature for the result. For example:

const Point = struct {
    x: i32,
    y: i32,
}
fn foo() (result: error!Point) {
    const ptr = (result = Point(undefined));

    // later

    ptr.x = 12;
    ptr.y = 34;
}

The workaround for error unions is particularly ugly:

    result = Point(undefined);
    const ptr = &(result catch unreachable);
@andrewrk andrewrk added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label May 25, 2018
@andrewrk andrewrk added this to the 0.4.0 milestone May 25, 2018
@thejoshwolfe
Copy link
Contributor

How does this interact with the "expression value ignored" error? surely you wouldn't have to:

_ = (maybe = foo);

@andrewrk
Copy link
Member Author

I hadn't thought of that. I suppose that there would be an exception for this expression. I'm open minded to a syntax counter proposal as well.

@bheads
Copy link

bheads commented May 25, 2018

I this struct case:

nullable_foo = Foo(undefined);
const ptr = &??nullable;
initializePoint(&nullable.point);

could we say

initializePoint(&nullable??point); 

nullable??point.x = 9; // a unreachable
nullable?point.x = 9; // a nop? this does scare me a little though

That would unwrap the null or be unreachable.
A single ? could be used to return null or the pointer to point

As for named return types could something like this.ReturnType, fn.ReturnType, or @typeof(this).ReturnType be used as a short cut

@bheads
Copy link

bheads commented May 25, 2018

Also is this going to open up using assignment in conditional expressions or passed as arguments?
It would be nice to avoid hard to spot side effects..

@raulgrell
Copy link
Contributor

raulgrell commented Jun 8, 2018

We could leave it out of the assignment statement and hide it behind a keyword:

var nullable_foo: ?Foo = null;

var ptr = with (nullable_foo) undefined;
initializePoint(&ptr.point);

const ptr = with (nullable_foo) | *foo | {
    initializePoint(&foo.point);
}

var value: Value = undefined;
const ptr = with (value) { .Int = undefined };

const ptr = with (value) | *v | {
     v = Value { .Int = undefined };
}

Note: with is probably a bad word for this - It would probably be better if we were to use it for some sort of resource cleanup or something like python's context management

@andrewrk
Copy link
Member Author

I think copy elision (#287) mostly solves this, and we can get by without it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

4 participants