Skip to content

Proposal: rename %return to tryreturn #340

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

Closed
andrewrk opened this issue Apr 23, 2017 · 10 comments
Closed

Proposal: rename %return to tryreturn #340

andrewrk opened this issue Apr 23, 2017 · 10 comments
Labels
enhancement Solving this issue will likely involve adding new logic or components to the codebase. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@andrewrk
Copy link
Member

Reasoning: people hate sigils

So the control flow with errors is then:

if statement style

try (foo) |payload| {

} else |err| {

}

provide a default

foo %% default

assert that it will never be an error

%%foo

give the payload value or return the error

tryreturn foo

I'm happy with the if statement style and tryreturn in the sense that they're useful and successfully avoid sigils. Both of the %% cases mirror ?? for nullables, so it would require some thought to figure out how to not have sigils there.

We also kinda want unwrap asserting to be easy because you have to do it on every printf:

%%io.stdout.printf("foo\n");

Also in situations where you do want to crash easily, such as writing an application or script sort of thing, you'll use %% all over the place. Making error unwrapping easy is a necessary component of making the lazy, easy, default way to code, the one that handles errors correctly.

Point here being, I'm not sure how we could avoid sigils and keep error unwrapping this easy.

@andrewrk andrewrk added the enhancement Solving this issue will likely involve adding new logic or components to the codebase. label Apr 23, 2017
@andrewrk andrewrk added this to the 0.1.0 milestone Apr 23, 2017
@thejoshwolfe
Copy link
Contributor

I like it. Below is other ideas that I don't like:

tryorreturn is slightly more correct, since you return in the failure case, not the success case. But tryorreturn is a little long, and those letters look terrible together.

returnonerror is another idea, and again a little long.

We could move it to a suffix operator:

foo() %% |err| return err;
foo() returnerr;
foo() orreturn;

But I don't like any of those ideas. I like the tryreturn suggested by the OP.

@andrewrk
Copy link
Member Author

andrewrk commented Apr 25, 2017

How about %defer? This is the defer that runs when you exit the current scope by returning an error.

  • trydefer - seems wrong
  • deferonerror - seems awkward
  • errordefer - seems awkward

D has:

  • scope(exit) - equivalent to status quo defer
  • scope(error) - equivalent to status quo %defer

If we don't have a plan to change %defer then we should probably leave %return as is.

andrewrk added a commit that referenced this issue Apr 25, 2017
@raulgrell
Copy link
Contributor

I actually like the %return and %defer syntax, so I wouldn't mind keeping them. But if you really wanna get rid of sigils my only qualm is that tryreturn is not the ideal name for it.

As @thejoshwolfe mentioned the 'longhand' for %return, foo() %% |err| return err; is already there, it's readable, makes control flow obvious, ie you only actually return if there's an error.

Another option is simply a try with no parameters:

fn bar() -> %void {
   x = try foo();
}

As for the defer, the scope() construct is actually quite a neat idea, though we could still call it defer

fn foo() -> %T {
     // Leaving function, equivalent to defer, block or statement
    defer(this) { bar() };
    defer(this) bar();

    // using return value
    defer(this) |val| { bar(val) };

    {
        // Leaving current block
        defer(this) { bar() };
        // Leaving function
       defer(foo) bar(); 
    }

     // Leaving from any error
    defer(error) {}
    defer(error) | err | { io.stdout.printf(@errorName(err) }

    // Leaving from specific/multiple errors
    defer(error.NoMem) {}
    defer(error.Overflow, error.Underflow) | err | { io.stdout.printf(@errorName(err) }

    // What would this mean?
    defer(error.Ok) { }
}    

@andrewrk
Copy link
Member Author

Try with no args looks nice, but we have a grammar problem to solve with that:

try (x) {}

Does this try have args? What if x is a struct? One way to resolve this is if there is a ( after try then it is the full try syntax, otherwise it is the return-on-error syntax. That would make this example be the full try syntax. I think this is reasonable, but let's see what @thejoshwolfe says.

...the 'longhand' for %return, foo() %% |err| return err; is already there, it's readable, makes control flow obvious, ie you only actually return if there's an error.

I was considering removing %return in favor of foo () %% |err| return err for the same reasons you just said. However as I started writing Zig code in practice, it became clear to me that this construct is so common that a shortcut is needed for the promise of "the easiest way to write error handling code is the correct way" to be true.

As for the defer, the scope() construct is actually quite a neat idea, though we could still call it defer

I'm not sure we can pass a scope as an argument to defer. Take for example this code:

fn foo(slice: []const u8) {
    var y: u8 = 0;
    for (slice) |x| {
        defer(foo) y += x;
    }
}

This is equivalent to creating a closure, a big topic and something that requires heap allocation. First of all it captures the value of x, and secondly slice.len is a runtime known value, and that is the size of the list of defers we would have to do at the end of the function.

I believe this does work in Go, and that is because it does automatic heap allocation with garbage collection.

@raulgrell
Copy link
Contributor

Good point with passing the scope.

If you're ok with the return-on-error syntax for try, the same could be done with defer:

defer bar();           // same as status quo
defer(error) bar(); // same as %defer

@andrewrk
Copy link
Member Author

I don't think bare try can work. Parens are necessary for expressions, we can't use them to disambiguate in this case.

@raulgrell
Copy link
Contributor

raulgrell commented Apr 25, 2017

One more consideration along these lines: instead of looking for the absence of a ( after the try, how about the presence of a { right after, such that:

fn bar() -> %void {
   x = try { foo() };
}

ie, you have to do the try against the return expression of the block. The same would apply to the defer.

defer { bar() };       // same as defer bar();
defer(error) { bar() }; // same as %defer bar();

@thejoshwolfe
Copy link
Contributor

defer(error) { bar() }; doesn't work with Zig's current syntax. Here's a proposal that would make it work:

  • Normal defer becomes: defer: { ... }, defer: foo();, etc.
  • %defer becomes: defer(error): { ... }, defer(error): foo();, etc.

(The syntactic similarity to labels makes a little more sense with the labeled blocks proposal in #346, because control jumps in and out of the labeled thing. ... or maybe defer still a pretty special idea and #346 doesn't affect this much.)

Unfortunately, this doesn't help the %return situation, since that's an expression, and : in an expression doesn't work in any existing Zig syntax.

The "bare try" idea can work if you change the idea from try foo to try (foo) and the difference is there's no body block on the try. However, I don't like that this idea involves adding characters to both sides of an expression. I like that %return or tryreturn is strictly a prefix operator.

@andrewrk
Copy link
Member Author

andrewrk commented May 1, 2017

Thanks for the discussion, all. Feel free to bring up any more points, but as it stands I think status quo is better than the proposals.

@andrewrk andrewrk closed this as completed May 1, 2017
@malthe
Copy link

malthe commented May 10, 2017

is.readNoEof(([]u8)(result[0...])) %% @return;

It passes any error to the (currently non-existing) compiler built-in @return.

@tiehuis tiehuis added proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. rejected labels Sep 15, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Solving this issue will likely involve adding new logic or components to the codebase. 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

5 participants