21

If I have a function that never returns (it contains an infinite loop) how do I declare/communicate that it will never return to the user of the function? The user does not see the source code, only the function signature and the documentation.

So the user will know to call my function in a new thread else his program will be blocked forever and the code below my function will never get executed.

Example function:

public void Foo()
{
    while (true)
    {
        _player.Play("scary_sound.wav");
        Thread.Sleep(TimeSpan.FromMinutes(5));
    }
}

Should I declare that as part of the documentation?

/// <summary>Plays scary sounds!</summary>
/// <remarks>This function never returns. Call it on a new thread.</remarks>
public void Foo()

Or name it with a "Run" prefix and "Loop" suffix or something?

public void RunFooLoop()

What ways are there to declare/communicate this? Any common practices? Any examples?

Fred
  • 449
  • 1
  • 4
  • 12
  • 17
    C has had a `_Noreturn` keyword since C11, but it's one of the few languages that do. – Kilian Foth Aug 19 '20 at 13:13
  • 1
    @KilianFoth: One of the reason many languages don't have a special keyword for this is that in an expressive type system, you can just express this as a type. E.g. Scala has the `Nothing` type and Haskell has one, too, although I can't remember the name at the moment, it doesn't *need* a special keyword. – Jörg W Mittag Aug 19 '20 at 13:33
  • 8
    Kotlin also has the `Nothing` type, which can be used for exactly this. – Andy Aug 19 '20 at 13:37
  • 1
    While the `Nothing` type declares that the function will not return anything, it doesn't communicate that a function with an infinite loop will not yield control back to the point where it was called. – Fred Aug 19 '20 at 14:05
  • 6
    At least with Kotlin's nothing and its most popular IDE, there's a static analysis immediately warning you when you invoke some code after calling a method with the `Nothing` return type, telling you that the code will never be reached. Unfortunately, this is not checked at compile time. – Andy Aug 19 '20 at 14:14
  • 4
    `RunLoop` or `EnterLoop` are perfectly acceptable formulations for any method which is designed to occupy the thread indefinitely. I wouldn't get too hung up about needing to warn external callers that there is no return - the usage of the method should be pretty obvious, and if it needs to run on its own thread, then it might be an idea to expose only a method which starts the new thread then returns, whilst the no-return method is called only internally by the new thread. – Steve Aug 19 '20 at 14:24
  • 1
    @Fred: The `Nothing` does *not* declare that the function doesn't return anything. That's `Unit` you are thinking of. `Nothing` means "doesn't return", `Unit` means "returns no useful value" (i.e. the equivalent to `void` in a C-like language). – Jörg W Mittag Aug 19 '20 at 15:07
  • `Nothing` is a terrible name for a bottom type. – 8bittree Aug 19 '20 at 15:48
  • 5
    `Nothing` (in Kotlin) is actually much better then you guys say! `Nothing` represents a value that has 0 instances. Therefore, if a function returns Nothing, it must never return. There are two main standard library functions that return Nothing: `exitProcess` and `throw` (though, throw is not a function but a language intrinsic). This means that you can use `throw` in a function and the compiler can use it to do more complex flow analysis. For example, doing `if(x == null) throw new Exc("x is null")` can be replaced by `if(x==null) error("x is null"` assuming `error` returns `Nothing`. – Carson Graham Aug 19 '20 at 21:34
  • 1
    I've marked up some code on gist to demonstrate this. The Kotlin compiler *does* use the Nothing type a lot. https://gist.github.com/Mee42/ed3bd5af8c90831153cc38ce1cec3192 – Carson Graham Aug 19 '20 at 21:36
  • 1
    @8bittree: It makes sense in the context of collections. E.g. the type of an empty `List[T]` is `List[Nothing]`, the type of an `Option[T]` that contains no value is `Option[Nothing]`. If you look at it as the type of an expression, it depends on how you interpret "evaluates to nothing", I guess. – Jörg W Mittag Aug 20 '20 at 09:07
  • @KilianFoth Apparently, it's also present in C++ in form of `[[noreturn]]` attribute. Note that such function still can exit by throwing an exception (or doing `longjmp`, but please don't). – val - disappointed in SE Aug 20 '20 at 12:29
  • 6
    Rust also has a [Never type](https://doc.rust-lang.org/reference/types/never.html), denoted as `!` for some obscure reason. – Thomas Aug 20 '20 at 12:44
  • Note that there are two different variants of this: Functions which never terminate, and functions which never return normally (always throw an exception). Rust's `!` generally denotes the latter. – CodesInChaos Aug 21 '20 at 10:43
  • we have a straight "no comments" doctrine in our company. If you can't tell what a class / function does by looking at it, it is probably written badly and needs refactoring. – clockw0rk Aug 21 '20 at 11:37
  • may I ask why you tagged this question with `api` and `api-design`? the way you put it is just a programming thing, otherwise, if its an api you are trying to build, this falls into a very long process that has to be initiated and monitored through an API, check this, as an [example](https://stackoverflow.com/q/33009721/436085) – Edoardo Aug 21 '20 at 14:33
  • @Edoardo You seem to use "API" in a narrow sense, like "REST API". But API in general means the "surface area", or interface, of *any* modular unit of code that is meant to be called by other code. – JohannesD Aug 21 '20 at 23:35
  • @KilianFoth `static void __attribute__((__noreturn__)) run_program(char **cmd_argv) { execvp(cmd_argv[0], cmd_argv); warn(_("failed to execute %s"), cmd_argv[0]); _exit((errno == ENOMEM) ? EX_OSERR : EX_UNAVAILABLE); }` – tchrist Aug 22 '20 at 02:35
  • 1
    @Thomas The [bottom type](https://en.wikipedia.org/wiki/Bottom_type) from type theory is denoted with an uptack (⊥). That might be why exclamation mark is used -- "!" looks similar to "⊥". – Mateen Ulhaq Aug 22 '20 at 04:08
  • @JohannesD indeed! That's why API ("libraries" in the specific case, I may add), come with entire books of documentations, explaining situations like this, that would render the whole question unnecessary. – Edoardo Aug 22 '20 at 06:53
  • @clockw0rk There are legitimate reasons for comments. For example: a method or function must be refactored to improve its performance, and the resulting code is anything but obvious. A better policy would be "*Whenever possible,* write your code so that it no longer needs comments to understand." – Robert Harvey Aug 22 '20 at 16:20
  • @RobertHarvey: Another important issue is that the most important things to ensure future people to know about design decisions are not the reasons that various things were done, but rather the reasons that various things were considered but *not* done. If people looking at a piece of code to do some task would naturally be inclined to think of a seemingly-more-efficient way of doing it, but that approach would be fundamentally incapable of handling a critical corner case, a comment saying why the seemingly-obvious approach won't work may be more important than one saying what the code does. – supercat Aug 22 '20 at 18:44
  • @RobertHarvey Sure, and sometimes the underlying language even encourages you to, see php 7.4.3 's typeisation comments. (Alhtough I hope this will be gone in the future, it's terrible imho) – clockw0rk Aug 24 '20 at 10:16

9 Answers9

63

This is one of those cases where infinite loops are so predominantly a bad idea that there's no real support/convention around using them.

Disclaimer
My experience is with C# (as is your code example, seems to be), but I would think that this is language-agnostic, unless there are languages which automatically wrap method calls in separate threads.

Based on the code you posted, what you've created here is a honeytrap. You can call the method any time you like, but you can never leave (I couldn't resist)

What you have here is the equivalent of asking your local government what signs you need to put up in your garden to tell people that you buried mines in your garden. The answer is that you shouldn't be burying mines in your garden.

The only practical use case for an infinite loop is for an application whose runtime process will be killed by the user (e.g. ping 8.8.8.8 -t is such an example). This is most commonly encountered in always-on services that are expected to run indefinitely.

However, playing a sound isn't something that you want your application to hang on. It's something you want to have happen concurrently. Therefore, you should design your code to work with it.

In essence, you should make a class that behaves like a media player. Maybe for you it's enough if this media player only has a start button with no way of ever turning it off again. I'll leave that up to you to decide.

But the play button click event is not an infinite loop. The sound should keep playing, but the click event should return.

This function never returns. Call it on a new thread.

Instead of pushing that responsibility to your consumer, just develop a class that, when called creates this thread and starts playing the sound.

This isn't a matter of "how do I tell my consumer that he should do this". It's significantly easier to do it for them. It promotes code reuse, lowers the change of bugs and unexpected behavior, and the documentation becomes easier to write as well.


Also, as an aside, you may want to look into using async/tasks instead of threads. It helps keep down the amount of active threads. But that's a different discussion.

Flater
  • 44,596
  • 8
  • 88
  • 122
  • 1
    I used C# in this example but code in other languages too and I am curious about this issue in other languages too, so the question is indeed language agnostic. Perhaps the play sound example was a poor example, a better one might be a TCP server that listens on a port for incoming connections. You do want to listen for incoming connections forever. Even with async/await, you still have the issue that the consumer doesn't know if he should await it on the same thread or run it on a new thread. – Fred Aug 19 '20 at 14:17
  • 13
    @Fred: If you presume that the infinite loop is warranted here (which I'm willing to believe you on), it's better to prevent people getting stuck on it (by writing your own thread-spawning logic around it and having your consumers use that) instead of trying to decide on the phrasing used on the warning signs (= wondering how to clearly document it). Your question is built on the idea that you can just push the effort of safely handling your class onto the consumer, but I disagree and feel that you should make things safe yourself. You expect your car to come with seat belts and airbags, no? – Flater Aug 19 '20 at 14:22
  • 9
    On the flip side, there are so many different threading mechanisms in some languages, it might not be appropriate to "force" the choice of a particular one. I see value in having both: have a convenience method that spawns the thread/queue/channel or whatever else is appropriate in your language, have it call another method does the loop and never returns, but make that method also public in case people have other uses for it. Alternatively, make your method only do one "iteration", and have the use control it themselves. See also: https://developer.apple.com/documentation/foundation/runloop – Alexander Aug 20 '20 at 01:27
  • 5
    What strikes me the most as bad here is that he is making decisions for the application programmer. No doubt with the intent to be helpful but this isn't helpful. If the API user wants to play the sound repeatedly or do whatever repeatedly, he can do so in whatever way he sees fit. I would be cursing an API as described. "What was this idiot thinking? This is utterly useless". – Martin Maat Aug 20 '20 at 05:15
  • 11
    -1 from me. Your comment about concurrency is absolutely valid, but it's quite orthogonal to the question. Indeed, an explicit never-return type is quite a good way of _indicating_ to somebody using such a library function that it may be a good idea to wrap this call in a thread fork, without _forcing_ them to use concurrency (which might be counterproductive overkill for something really running indefinitely on an embedded device with scarce resources). – leftaroundabout Aug 20 '20 at 12:11
  • 2
    @leftaroundabout: The only valid use case for a thread-blocking infinite loop is one where it defines the application's lifespan (i.e. always-on services), or self-contained concurrent threads (= which thus can be wrapped, as per my answer). In the former case, there is no code consumer of your class, you are defining your application at the top. OP is asking about people _using his code_ and how to convey its intention, which proves that this code is only one part of a larger codebase, at which point thread-blocking infinite loops are runtime killers, not valid end states for an application. – Flater Aug 20 '20 at 12:17
  • 2
    @Flater not at all true. You may e.g. write a library (or _framework_) whose purpose it is to allow the consumer to create simple webservers. Most users would use this just as “launch the server now, keep it running until manual terminate or system reboot”, so forking by default would be unnecessary and indeed might lead to confusion “my server always terminates right after I launch it” (precisely _because_ main thread happily keeps running and terminating the program). – leftaroundabout Aug 20 '20 at 12:27
  • It _is_ encompassed by that, my “not at all true” refered to the premise that this means nobody else would use the code. Anyways, there are also cases where it would _not_ define the application's lifespan. Never-return functions might still raise exceptions or hand on control flow to user-given callbacks. – leftaroundabout Aug 20 '20 at 12:36
  • 6
    There are some situations, such as in systems programming, where you absolutely do need an infinite loop or a function that otherwise never returns, and absolutely do need to call it without spinning off a thread. This includes, for instance, the Rust [panic handler](https://doc.rust-lang.org/nomicon/panic-handler.html), which is defined when there isn't a standard library, called when the app panics, and is expected to take over the process. Calls like [exit()](https://doc.rust-lang.org/std/process/fn.exit.html) are declared as never returning for a similar reason. – TheHans255 Aug 20 '20 at 14:15
  • @TheHansinator: You're not accounting for OP's own documentation: _"This function never returns. Call it on a new thread."_ That explicitly argues that this code is intended to run concurrently in a separate thread. Your comment may be valid in some cases but the posted question simply does not confirm to any of the exceptions you're thinking of. – Flater Aug 20 '20 at 14:19
  • 1
    And also provide a way to Cancel / Exit the loop. Might be useful. (see `CancellationToken`) – JB. Aug 20 '20 at 14:19
  • 1
    @JB.WithMonica. Yes, passing a cancellation token would be very interesting to me, however that means the function actually can return (which might not be so bad after all). I am just uncertain in how to declare that the function is going to run forever unless cancelled. – Fred Aug 20 '20 at 14:40
  • 1
    @TheHansinator: There are also situations where a function that loops infinitely when unable to compute correct data may be tolerably useless, while returning incorrect data would be intolerably worse than useless. IMHO, instead of giving compilers carte blanche to assume loops will terminate, the Standard should have said that if a piece of code is statically reachable from within a loop, and no *individual action* within the loop would be sequenced before that piece of code, the loop as a whole isn't sequenced before it either. That would have accommodated most useful optimizations... – supercat Aug 20 '20 at 16:36
  • ...without inviting compilers to pretend that computations had magically yielded results which, though incorrect, would allow loops to terminate. – supercat Aug 20 '20 at 16:41
  • 2
    This answer is helpful but doesn't answer the question...and is highly-voted...which is quite common unfortunately. The problem with that is if I google a specific problem and come to a SE answer like this one and it does a frame challenge, and despite the best intentions of the frame-challenger I *actually do need to do it that way*, I am not helped. And that happens fairly often in my experience. In theory theoretical best practices are best practices, but in practice they often aren't. Real projects are beholden to the golden triangle and other immutable constraints. – bob Aug 21 '20 at 15:54
  • 1
    @bob: Conversely, only posting strict answers on how to do [usually bad practice] without allowing for frame challenges will lead to unintentionally implying that [usually bad practice] is what should be done. There is a difference between answering a general question and answering OP's situation. Often they align, and OPs are incentivized to keep questions generally applicable and reusable for others, but when they don't align, I prioritize the actual OP who posted a question instead of some assumed future reader. – Flater Aug 21 '20 at 20:43
  • Right, but we don't know for sure that the actual OP doesn't also *need to do it that way*. It's hard for us to judge what constraints might apply in OP's situation to make a given best practice more or less suitable, or even off the table. So it seems safer to assume OP knows what they're doing except in extreme circumstances (and of course if they post asking how to commit a crime or do something dangerous, it's definitely worth doing a frame challenge). – bob Aug 21 '20 at 20:54
  • So basically I guess I'm arguing that if we're erring on the side of helping OP, we should answer the question they ask assuming in good faith that if they are breaking best practice they either know what they're doing or they have no other choice. Otherwise I think we wind up acting as an educational site (here's how you should do that instead) rather than a QA site (here's how to do what you asked). – bob Aug 21 '20 at 20:58
  • Per the question in question I do want to suggest though: in addition to the frame challenge, it would be helpful to also answer the question. Something like "best practice is really to do X (with explanation) but if you really need to do Y here's how...". Modified that way, this answer would both establish best practice AND help OP if they actually need to do it the way they stated (and any future devs who likewise have the same problem). – bob Aug 21 '20 at 21:07
44

This depends on the type system and the language.

Many languages have a type for this, for example never in TypeScript, Nothing in Scala and Kotlin, or Void in Haskell.

C has a _Noreturn function specifier keyword.

In languages that support annotations (e.g. Java, C#), you could create a custom annotation that communicates your intent. Some IDE vendors or static analyzers might already have invented one for you.

Otherwise, documentation is the best thing you can do.

A type such as this is often a bottom type, meaning a type that is a subtype of every other type, and it is a type that can never have any instance. (This is called an "uninhabited" type.) Since it cannot have any instances, declaring the return type of a function as this type clearly communicates that the function never returns, since there is nothing it could return.

Bergi
  • 996
  • 7
  • 15
Jörg W Mittag
  • 101,921
  • 24
  • 218
  • 318
  • 2
    Yes, for most languages, documentation, convention and consistency should be the way how to "solve" this (e.g. by using a specific method suffix). For other languages actually supporting a mechanism to represent a method which never terminates, such mechanism should be used. – Andy Aug 19 '20 at 13:42
  • How is `never` and `Nothing` different from `void`? Can these `never` and `Nothing` return types indicate that the function runs indefinitely in an infinite loop and never yields control back to where it was called from? – Fred Aug 19 '20 at 14:08
  • 22
    @Fred `void` at least in C, C++, and many similar languages just means the function doesn't return a *value*, but provides no indication on whether or not the function actually returns. A *bottom* type is a type that cannot be instantiated. A function that returns a *bottom* type cannot return without providing an instance of the bottom type, but since bottom cannot be instantiated, it must diverge, i.e. loop forever, throw an exception, power off the machine, etc. – 8bittree Aug 19 '20 at 14:21
  • 11
    @Fred: `void` signifies that a function *does* return, but doesn't return a useful value. `never` signifies that a function *doesn't return*. The equivalent to `void` in a more expressive type system would be a *unit type*, i.e. a type which has only one instance and no useful operations. – Jörg W Mittag Aug 19 '20 at 15:05
  • @JörgWMittag, I only wish there could be standardisation of these terms. *Void* is pretty widely understood - it means the method returns without any value. But is that the same concept as Null? And practically speaking, a "never" type is probably better called the *jump* type or something similar - you're either going to jump up the stack in an unstructured fashion (for example with an exception), or jump out of the program completely (for example with an exit statement that terminates the process). It all gets very confusing. – Steve Aug 19 '20 at 16:03
  • 4
    Haskell's is called `Void`. A fairly standard name is "empty type", which doesn't imply subtyping like "bottom type". C (and friends)'s `void` can be seen as having a single value (therefore no information; if you know something is `void` you know its value), that you just can't name. The *identity* of that single value is not important. In Java/Scala, the `Null` type (which is unnamed in Java) contains only the value `null`, but it's different from Scala's `Unit`, which has a different singleton value. "Jump type" seems... too specific? Maybe "unreachable". – HTNW Aug 19 '20 at 22:39
  • I don't think `Void` is ever used to mark a function that doesn't return in Haskell. The normal way to do it is `forall a. a`, which is also what will be inferred for the return type if you don't give a type signature. (Since a pure function that doesn't return is pointless, it'd be more likely to be `forall a. IO a`.) – benrg Aug 20 '20 at 00:26
  • 2
    @Steve: there *is* standardization, mostly. Both the type and the value for a function that returns no useful value is almost universally called *unit*. It is only a small number of languages that deviate from that and call it *void*. Although note that the concept of *void* in e.g. C is very different, because it is not actually a type, it is more of a marker. In particular, in languages like Scala, there are no statements, there are only expressions. What would be a statement in C or Java is still an expression in Scala, just one whose value is *unit*. – Jörg W Mittag Aug 20 '20 at 00:39
  • 1
    One relevant attribute for c# can be found in https://stackoverflow.com/a/59582675/3102935 – WorldSEnder Aug 20 '20 at 04:35
  • @Fred A big difference is that in languages like Haskell, not using a return value is the exception; you must do it explicitly, or you get compiler warnings/errors. In languages like C or C#, you ignore return values quite often and implicitly (though C# is certainly getting more functional with every new release). Of course, ignoring the return value is very common source of programming errors, which led to both the introduction of exceptions (instead of returning error values) C-like languages and the requirement to handle all return values in ML-like languages. – Luaan Aug 20 '20 at 08:48
  • 1
    @Steve Yes, Void is the same concept of null and unit. It's a type with only one valid value (the empty tuple `()`, `unit`, `null`, etc.). For languages with nullable-by-default (e.g. Java) references, each such reference can point to either an object of `T`, or `null` (which could be thought of as the only possible instance of an imaginary `Null` type. You can imagine that each `T` actually means `T | Null`. A consequence of this is that a truly-non instantiatiable `Never` isn't possible, since `null` is allowed everywhere. See https://docs.oracle.com/javase/7/docs/api/java/lang/Void.html – Alexander Aug 20 '20 at 18:29
  • @HTNW, it was only an offhand thought, but can you think of a situation not covered by "jump"? These "jump types" (i.e. "bottom", "never" or "noreturn" types) are a way of expressing what is essentially the functional equivalent of the `GOTO` - an unstructured deviation from the normal linear call-and-return pattern. "Jump" also seems to be a long-accepted and well-understood terminology for the concept. – Steve Aug 21 '20 at 10:25
  • @AlexanderReinstateMonica, what I'm more particularly curious about is whether `()` (arity 0) is equivalent to `(NULL)` (arity 1), where the type of the single value of the latter is the Null type. Or is that considered to be a degenerate case where both are equivalent? My own understanding is not complete in this area, but straight away I can see that there's room for trouble - there would appear to be a structural distinction, even if there is no distinction of values carried. – Steve Aug 21 '20 at 10:34
  • 1
    I miss the old way of doing it in C. You wrote `volatile void` and the compiler understood the nonsensical type to be uninhabitable and therefore the function never returned. – Joshua Aug 21 '20 at 18:14
  • @HTNW - *Java/Scala, the Null type (which is unnamed in Java) contains only the value null* ... in Java the appropriate type with this property is `java.lang.Void`. – occipita Aug 25 '20 at 04:08
  • @occipita Java also has a built-in null type, it's just not nameable. [See the JLS](https://docs.oracle.com/javase/specs/jls/se14/html/jls-4.html#jls-4.1). Void is an ordinary class that can't be constructed, so it has no objects and the reference type is thus a singleton (containing only null). – HTNW Aug 25 '20 at 11:35
12

I will answer from the point of view of C++, which has not been addressed in other answer at the time of writing.

Since C++11, there is an attribute, noreturn, that you can use to signal that a function will never actually give back control to the caller.

Before C++11, each compiler could decide to have such an extension (for example, GCC on this page you can find the description of the attribute used for this)

In embedded systems, I have seen such functions used as traps when an error or fault is detected, so the the processor can keep running and the developer can debug the problem. It might be useful in production too, but I've not seen such a use yet (and I guess it depends on the application.)

bracco23
  • 399
  • 1
  • 8
6

Naming

Or name it with a "Run" prefix and "Loop" suffix or something?

Instead of "Run" or "Loop", I'd prefer "Forever" here. As in, ForeverFoo() or FooForever().

There's a readability cost to long identifiers, but for rarely used ones this cost is negligible. I suspect the clarity outweighs the loss of brevity in this case. Do explain it in the documentation though, just to be explicit.

Factor out the pattern

If you have multiple of these infinite functions, consider factoring out the forever pattern to its own function/template/macro. Depending on your language, this might be tricky if Foo() requires arguments, but it would definitely make things cleaner and more natural.

In Python, for example, I might do something like this:

import time

def forever(func, *args, sleep_time=0, **kwargs):
    while True:
        func(*args, **kwargs)
        time.sleep(sleep_time)

forever(print, 'hi', end='#\n', sleep_time=1)

Then you might end up with a call like Forever(Foo), which is both readable and DRY.

This approach also lends itself well to other wrappers, for example Repeat() to play your scary sound a fixed number of times.

marcelm
  • 161
  • 3
4

Provide low-level access and an expected-use wrapper

This is a frustrating bug waiting to happen. However, some users will want to control their own threading model. You can get the best of both worlds by creating a low-level function:

/** 
 * Play scary sounds in an infinite loop which never returns. 
 *  
 * See `PlayScarySounds` for a function which doesn't hijack the
 * current thread.
 */ 
public void PlayScarySoundsForever() {
    while (True) {
        _player.Play("scary_sound.wav");
        Thread.Sleep(TimeSpan.FromMinutes(5));
    }
}

And a wrapper function with your expected "spawn new thread" use case:

/** 
 * Spawn a new thread which plays scary sounds in an infinite loop.
 *
 * Wrapper around `PlayScarySoundForever`. 
 */
public void PlayScarySounds() {
    Thread.Spawn(() -> PlayScarySoundsForever());
}

Users might skim past the documentation, but they're less likely to skim over the list of methods and take note of a pattern there. The important part is that you give the "less scary" function the "less scary" name, since some users will still look at the method names and pick the one that looks most fitting.

Of course, there's also the question of "do you really not want to have some way of terminating this?" But that's, I think, a different question.

Phoenix
  • 758
  • 7
  • 14
  • Yes, it would be nice to be able to terminate this. In .NET this is possible by passing in a `CancellationToken` as a argument. I like how the comments of both methods reference each other. – Fred Aug 22 '20 at 17:55
1

Swift has types "Void" and "Never". "Void" has only one value (and needs 0 bits to store it). "Never" has zero values. Therefore a function cannot return a value of type "Never". Therefore a function declared to return "Never" cannot return.

gnasher729
  • 42,090
  • 4
  • 59
  • 119
1

tl;dr Use asynchronous coding if you want asynchronous operation. Don't write sequential code unless:

  1. you actually want things to happen in the specific order; or/and

  2. you're confident that the earlier steps will return quickly enough for it to work out.

If you follow this, then it doesn't matter if a method returns because there's no practical distinction between non-returning methods and other types of methods that don't reliably return quickly (e.g., long-running methods and conditionally-returning methods).


Don't worry about if a method returns.

Programmers shouldn't be worried about stuff like if a method will ever return.

When you write procedural code, you're specifying a sequence of things that should happen. You shouldn't worry about if a method will ever return because it shouldn't matter; you don't want things after the method to happen before that method's done.

For example, if you write

PlaySounds();
SendEmail("Hey, I heard all the sounds!");

, it should be understood that the email won't send until the sounds are done playing.

By contrast,

BeginPlayingSounds();
SendEmail("Hey, I've begun listening to the sounds!");

will allow the email to be sent once sounds have started to play (which can mean different things), even if the sounds go on forever.


Discussion: False synchronicity as a performance hack.

Say you want to calculate both x*x and y*y.

  • Conceptually, unless you want these things to happen in a specific order, you shouldn't specify them as happening in a specific order.

  • Practically, these are very fast operations, so it's not usually worth the verbosity or computational overhead to write them as asynchronous. Modern compilers and CPU's may try to reorder such operations anyway.

In other words, while it's conceptually wrong to write non-synchronous logic as synchronous, it's often very practical to do so.

This disconnect between intent and practical coding is a deeper problem in modern programming practice. We're hesitant to do something like adopt conventions for non-returning methods because those wouldn't really fix the underlying problem.

For working programmers today, I'd think:

  1. By default, use asynchronous coding whenever you don't actually require things to happen sequentially. Use sequential only if you actually want things to happen sequentially.

  2. As a performance/brevity hack, reduce asynchronous code to falsely synchronous code according to your best judgement.


Switch focus to qualifying if methods will return quickly.

As discussed above, there're two reasons to write synchronous code:

  1. We want sequential operation.

  2. As a hack to dodge the verbosity/overhead of asynchronous code/execution.

So if you're going to call a method, the big thing you want to know is if it'll return quickly. Because if it does, then you might use falsely synchronous code.

Non-returning methods are just a subset of methods that don't necessarily return quickly. As such, they're not particularly interesting to programming practice.

Nat
  • 1,063
  • 1
  • 8
  • 11
  • What about if it is a listener loop that listens for incoming connections on a port. The function could be declared as asynchronous but it would run forever so you cant do other things unless you run it on a different thread. – Fred Aug 20 '20 at 09:28
  • @Fred: Are you imagining a listener that loops over polling for new messages and sleeping until the next poll? Because that'd seem to be a sequential operation. (I mixed "_sequential_" and "_synchronous_" a bit above in an attempt to keep it short.) – Nat Aug 20 '20 at 09:31
  • no it doesn't need to sleep it can await reading new messages or using a blocking receive call. But since it listens for new connections for as long as the application is running, you probably want to do that on a different thread than the main thread. – Fred Aug 20 '20 at 09:35
  • @Fred: If it blocks, then it ought to be called `Listen()`, blocking until it's done listening (because blocking-until-completion is what sequential logic is all about). If it doesn't block the calling thread, then it ought to be called `BeginListening()`, blocking until it's done beginning the listening process. – Nat Aug 20 '20 at 09:38
1

Don't make a function that never returns. Don't ask the caller to put that function on its own thread.

Put the logic on its own thread within the function. Then name it appropriately for whatever it does.

For example, in C#:

public void PlaySounds()
{
    new Thread(() =>
    {
        while (true)
        {
            _player.Play("scary_sound.wav");
            Thread.Sleep(TimeSpan.FromMinutes(5));
        }
    }).Start();
}

However, as stated in other answers, an infinite loop is not the ideal way to go. Especially in this case, since the thread will never be killed. So maybe something like this would be more appropriate:

private static bool playSounds;
public void PlaySounds()
{
    playSounds = true;
    new Thread(() =>
    {
        while (playSounds)
        {
            _player.Play("scary_sound.wav");
            Thread.Sleep(TimeSpan.FromMinutes(5));
        }
    }).Start();
}

public void StopSounds()
{
    playSounds = false;
}
Evorlor
  • 1,440
  • 2
  • 16
  • 22
0

In Python, you can use typing.NoReturn to indicate that a function never returns. e.g.

from typing import NoReturn

def stop() -> NoReturn:
    raise RuntimeError('no way')
laike9m
  • 135
  • 7