GoTo statements are useful when you wish to Go To an area within a local sub routine without reinventing the go to. For example, the break, exit, and return statements are forms of Go To. Exit is just a poor form of go to since you cannot report any errors while exiting. By using code such as GoTo Error1, GoTo Error2, etc., this is really just an advanced exit statement and makes the code clearer than alternatives. Embedding all the error reporting inline can bloat the code and make it less readable than the goto alternative. Structured use of the goto can keep the code short and to the point and very descriptive of what is happening. I can point you to some quotes from experts (Linus, Knuth, and others) who use Go To for these situations, but you'll just have to take my word for it - because I don't need to prove myself. HaHaOnlySerious. If, however, the language supported some sort of advanced exit system that let you exit toward errors, this could replace this special use of Go To - just as Break, Exit, Continue and friends replaced goto for those special needs of the limited Go To use. And no, I am not speaking of exceptions - but a way to exit the procedure cleanly from within a loop and provide debugging info to some log file, and then return a function result.
Keeping ugly debug info out of the algorithm and patterning a clean exit strategy from the function is very handy. It allows one to have less fear of logging early exits since all that logging can be done in a nice error label hidden from the algorithm (just like encapsulation and data hiding). A lot of people don't log their code because they are afraid the logging will bloat up the algorithm with line noise - the goto label rids this fear, and allows a nice space for logging and other stuff we do on exit. If used in a limited, local scope, disciplined way, the goto can be useful!
Consider the below pseudo language where we are writing some algorithm with loops:
fun test(s: astr): bool; b if s = '' then goto error1; for i = foo to bar go // algorithm stays clean, easy to read if s[i] == #0 then goto error2; process(s); //...etc. e; exit; // default // keep below bloat out of our algorithm and loops error1: debugln('Error 1: Big problem, parameter contains missing data'); result = false; exit; e; error2: debugln('Error 2: Problem, this code contains bad data'); result = false; exit; e; e;If the above errors and debugln calls were kept in the algorithm, instead of as error labels, it takes away from readability of the algorithm. The error labels give us a nice patterned and structured (ironic?) way to cleanly exit the function (and roll up/clean any memory) The alternative is to simply embed all that stuff into the algorithm itself, which is ugly and causes developers to not do logging and other important stuff that needs to be done in critical software on early exits (because early exits can mean big problems or things that have gone wrong, and ignoring these early exists is not so swell.. if they are errors they need to be reported!). Exceptions try and solve some of this issue but this pattern isn't just a reinvention of exceptions.. because we are not halting the code like an exception does. It is simply exiting a local function with more flexibility and reporting ability than an Exit() call, that is all.
This can be considered structured goto usage.. or clean use of goto. Gotos aren't bad when used intelligently and with discipline. However, if we had built in ways to exit with errors without using goto, I'd say we could avoid the goto in this case. Languages, so far, don't have abilities to emulate above behaviour (again, note, that this isn't equivalent to exceptions).
Local scope nested procedures could also help for error reporting and clean exits. However, the problem with a local nested procedures instead of an error label, is that in nested procedures, one cannot generally exit the main parent procedure (except in some version of Apple or Mac Pascal, I think, which had this feature for this very reason: clean error exiting and reporting!).
Usage: Linux device drivers, standard pattern for nested resource cleanup.