Does it make sense to return a const char* from a function?

Says it all in the title. My thought was that a const anything can't change. So is returning one a thing?

Sure.

const char *fnc(int i) {
   static const char *words[] = {
      "The",
      "quick",
      "brown",
      "fox",
      "jumps",
      "over",
      "the",
      "lazy",
      "dog.",
      NULL
   };
   return words[i];
};

void setup() {
   int i = 0;
   Serial.begin(115200);
   const char *p = fnc(i);
   while( p ) {
      Serial.print(p);
      Serial.print(' ');
      ++i;
      p = fnc(i);
   }
   Serial.println();
}

void loop() {}
The quick brown fox jumps over the lazy dog.

Another way to think about it: when a function returns a number (or bool), you can't change that return value either. For example, if you get 14, you can't change that to be something else. You can discard it when assigning another number to the same variable, but that's not the same thing.

If you had a pointer to not-const char, and at that pointer was the C-string "dog"; if changed to "cat", everything with that same pointer would be affected. If the text there is actually "hotdog", and the compiler was optimizing space used to point at that fourth letter, then anything with the pointer to the first letter would now have "hotcat". There might be some case where you'd want to organize program text this way, but definitely not the typical case.

The reason the const is there is to promise (or at least pinky-swear) what you won't do with a pointer: that you won't change what's there. Going back to 14, you can't change a non-pointer value in the same way. In C, const char * is commonplace because that's how text is represented; not with a simple value. You could also have a pointer to a block of int or anything else, and those could be const if it's hard-coded data.

It should be noted that const char* is a pointer to a constant char. The char cannot be changed. The pointer can (as shown in the example above).
Also take care here: the text inside the function is declared as static const char. This is needed to prevent losing the text when the function goes out of scope.

As other said it’s all fine.

There is a possibly interesting angle to understand. In @van_der_decken's code he used static. This static is not what makes it work, it just helps not reconstruct the array at every call, but the code should still work without it.

The reason is that the function returns only a copy of one pointer, words[i], and that pointer refers to a string literal, not to the array that holds it.

As string literals have static storage duration, they exist for the whole run of the program. On a small AVR they are copied into RAM at boot (so you can actually modify them at run time but that’s breaking a promise ), and they are never freed. Thus the local array of pointers can disappear when the function returns without affecting the text it pointed to.

What would not work is returning a pointer to something that really is local, such as a char array that holds its own copy of the text, or the address of the pointer array itself:

const char *bad1() {
  char buf[] = "dog";   // local array on the stack, holds a copy of the text
  return buf;           // dangling pointer, buf is gone after return
}

const char **bad2() {
  const char *words[] = {"The", "quick", "fox"};
  return words;         // dangling, the array itself is gone after return
}

Here the returned pointer would point into stack memory that is reused by the next function call, so the text would be garbage or corrupted.

That’s where making buf or words static would fix both cases. The const does not change any of this, since it only promises that the text will not be modified through that pointer, not that the memory stays valid.

returning `static const char *` is ok. Returning a pointer to a local variable -> software crash (on a PC) or undefined behavior (on MCU)

You can return a pointer to any thing you want from a function. However, if you intend to dereference that pointer, you better be sure it will be pointing to a valid object after the function returns.

Yeah, realize that.

Thanks for the replies guys.