Skip to content

gotocase: a case for useful and not harmful goto #5950

Closed

Description

@y0shir

While it's obvious that goto is considered harmful, sometimes it is still a usefull tool.
Because zig dosen't support switch falltrough, i propose an alternative: gotocase.
Let's consider this esample from https://wiki.c2.com/?GoTo in C

Mask *pMask(NULL); switch (type) {
case POINT:
	Mask tmpMask;
	vector<Location> &v=getVectorOfPoints();
	for (iterator it=v.begin();it!=v.end();++it)
	tmpMask.set(*it);
	pMask = &tmpMask;
	goto drawMask;
case MASK:
	pMask=getBitMask();

drawMask:
	// here's the code to draw the mask overlay
	break;

}

This obviously saves time and looks more elegant than combination of if statements and functions.
Perhaps with gotocase it could look like this:

switch (type) {
 POINT =>
	//dopointstuff
	gotocase else;
MASK =>
	pMask=getBitMask();
        gotocase else;
else =>
	// here's the code to draw the mask overlay
}

To enforce good code practices one could restrict gotocase to apear only at the end of the case and only cases that are further down.

Activity

  1. added
    proposalThis issue suggests language modifications. If it also has the "accepted" label then it is planned.
    on Jul 29, 2020
  2. added this to the 0.7.0 milestone on Jul 29, 2020
  3. joachimschmidt557 commented on Jul 29, 2020

    @joachimschmidt557
    Contributor

    I think the more idiomatic zig way for handling that example would be to define pmask with a switch:

    const pmask = switch (xyz) {
        .Point => {
            // more complex code
        }
        .Mask => getBitMask(),
    };
    
    // code for drawing
    
  4. joachimschmidt557 commented on Jul 30, 2020

    @joachimschmidt557
    Contributor

    gotocase, as far as I can see, is there for removing code duplication in situations where multiple cases of a switch contain very similar (almost exact) statements.

    switch (abc) {
        .Car => {
            foo("car");
            bar(42);
            baz(1337);
        },
        .Train => {
            foo("train");
            bar(42);
            baz(1337);
        },
    }

    If they were exactly the same, we could just combine them.

    switch (abc) {
        .Car, .Train => {
            foo("transportation thing idk kthxbai");
            bar(42);
            baz(1337);
        },
    }

    But we can solve this problem without needing gotocase.

    fn doThing(name: []const u8) void {
        foo(name);
        bar(42);
        baz(1337);
    }
    
    switch (abc) {
        .Car => doThing("car"),
        .Train => doThing("train"),
    }

    Furthermore, a prominent example of the linked website on the first issue comment displays the usage of goto in FSM-based lexers. I actually don't think lexers using that are better. This makes understanding them a more tedious task. If there is not only the obvious way I can jump into a switch case, but also non-obvious ways (jumped from other cases), where did I come from? What does this case really do? The best counterexamples for the necessity of lexers with gotocase are the lexers for zig and C in the standard library.

  5. pixelherodev commented on Jul 30, 2020

    @pixelherodev
    Contributor
  6. ekipan commented on Aug 4, 2020

    @ekipan

    a restart keyword

    So, continue! Switches already break, after all.

    switch (val) {
      .case1 => continue .case3,
      .case2 => do_case2(),
      .case3 => do_case1_and_3(),
    }
  7. joachimschmidt557 commented on Aug 4, 2020

    @joachimschmidt557
    Contributor

    @ekipan, as I mentioned in my comment earlier, what you wrote can be achieved without continue in the current zig syntax:

    switch (val) {
      .case2 => do_case2(),
      .case1, .case3 => do_case1_and_3(),
    }
  8. ekipan commented on Aug 4, 2020

    @ekipan

    @joachimschmidt557 Sure, I was just talking syntax though. I've no opinion on the feature itself. Multicase prongs can't do this:

    switch (val) {
      .case1 => do_case1(); continue .case3,
      .case2 => do_case2(),
      .case3 => do_case1_and_3(),
    }

    But again you could just use functions:

    switch (val) {
      .case1 => do_case1(); do_case1_and_3(),
      .case2 => do_case2(),
      .case3 => do_case1_and_3(),
    }
  9. raulgrell commented on Aug 4, 2020

    @raulgrell
    Contributor

    The use case I thought this was going to address is implementing something like computed gotos, described here: https://eli.thegreenplace.net/2012/07/12/computed-goto-for-efficient-dispatch-tables

    // In C
    
    int interp_goto(unsigned char* code, int initval) {
        /* The indices of labels in the dispatch_table are the relevant opcodes*/
        static void* dispatch_table[] = { &&do_halt, &&do_inc, &&do_dec};
        
        #define DISPATCH() goto *dispatch_table[code[pc++]]
    
        int pc = 0;
        int val = initval;
    
        DISPATCH();
        while (1) {
            do_halt:
                return val;
            do_inc:
                val++;
                DISPATCH();
            do_dec:
                val--;
                DISPATCH();
        }
    }
    

    Where it would allow us to express the above with something like

    // In Zig
    
    fn dispatch(code: []const u8, pc: *u32) OpCode {
        defer pc.* += 1;
        return @intToEnum(OpCode, code[pc]);
    }
    
    fn interp_goto(code: []const u8, initval: u32) u32 {
        var pc: u32 = 0;
        var val: u32 = initval;
    
        switch (dispatch(code, &pc)) {
            .Halt => return val,
            .Inc => {
                val += 1;
                gotocase dispatch(code, &pc);
            },
            .Dec => {
                val -= 1;
                gotocase dispatch(code, &pc);
            }
        }
    }
    

    On one hand we lose the cue from "while" that there is effectively a loop.

  10. modified the milestones: 0.7.0, 0.8.0 on Oct 26, 2020
  11. modified the milestones: 0.8.0, 0.9.0 on May 19, 2021
  12. andrewrk commented on Aug 23, 2021

    @andrewrk
    Member

    The use case in this proposal is sufficiently addressed by the comments in this issue as well as #8220.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    proposalThis issue suggests language modifications. If it also has the "accepted" label then it is planned.

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions