Whitespace in code causing later function defs to not work

I have a struct definition and several instances of it. The problem I'm encountering is that whitespace is behaving very weirdly. Even though whitespace should be ignored, it seems that, when I break up the instantiation onto multiple lines, I get errors when compiling.

To illustrate, if I write the following:

struct PatternConfig
{
  uint8_t startingHue;
  uint8_t endingHue;
  bool cycleHues;
  void (*pattern)();
};

PatternConfig p = { 0, 255, true, rainbow }

void rainbow()
{
  // some colorful stuff
}

everything compiles and is happy. However, if I change it to add a little whitespace

// struct is the same

PatternConfig p = {
  0,
  255,
  true,
  rainbow
};

// rainbow function is the same

then I get an error about rainbow being undefined in scope

xmas_light:52:17: error: 'rainbow' was not declared in this scope
   0, 255, true, rainbow};

I've seen this error before when I had some syntax error (e.g. a missing paren), but the only change between the two files is introducing the whitespace. My understanding is that the whitespace shouldn't matter in this context, but obviously there's something funky going on here. Also, the above is a somewhat contrived example; the structs I'm actually using are larger and, thus, cumbersome to read on one line, otherwise I'd just forget it and go for the one-liner.

Any ideas? Thanks in advance!

Does the problem go away if you create a prototype for the function rainbow before you attempt to use it in the definition of p ?

The Arduino IDE automatically adds function prototypes for any function that doesn't already have a prototype in your code. Generally, it does a very good job of this, but in some rare cases it inserts the prototype in the wrong place. That is the case here. rainbow() must be declared before the definition of p but the Arduino IDE is not doing that.

The workaround is to manually add the function prototype where you want it:

void rainbow();

This sort of thing can lead to very confusing errors. Sometimes the error messages don't even match the code in your sketch, since the error line is the "hidden" code inserted by the Arduino IDE. When you encounter a mysterious error like this, it can be very helpful to examine the post-sketch preprocessing output:

  • File > Preferences
  • Check the box next to "Show verbose output during: compilation'
  • Click "OK"
  • Sketch > Verify/Compile
  • After compilation fails, scroll the black console window at the bottom of the Arduino IDE window all the way to the top.
  • Examine the first line of output to find the value of the "-build-path" option.

For example, if you had this:

C:\ArduinoIDE\arduino-nightly\arduino-builder -dump-prefs -logger=machine -hardware C:\ArduinoIDE\arduino-nightly\hardware -hardware C:\Users\per\AppData\Local\Arduino15\packages -hardware E:\electronics\arduino\hardware -tools C:\ArduinoIDE\arduino-nightly\tools-builder -tools C:\ArduinoIDE\arduino-nightly\hardware\tools\avr -tools C:\Users\per\AppData\Local\Arduino15\packages -built-in-libraries C:\ArduinoIDE\arduino-nightly\libraries -libraries E:\electronics\arduino\libraries -fqbn=esp32:esp32:node32s:FlashFreq=80,UploadSpeed=921600 -vid-pid=0X2341_0X0042 -ide-version=10809 -build-path C:\Users\per\AppData\Local\Temp\arduino_build_889992 -warnings=all -build-cache C:\Users\per\AppData\Local\Temp\arduino_cache_764477 -prefs=build.warn_data_percentage=75 -prefs=runtime.tools.esptool.path=C:\Users\per\AppData\Local\Arduino15\packages\esp32\tools\esptool\2.3.1 -prefs=runtime.tools.esptool-2.3.1.path=C:\Users\per\AppData\Local\Arduino15\packages\esp32\tools\esptool\2.3.1 -prefs=runtime.tools.mkspiffs.path=C:\Users\per\AppData\Local\Arduino15\packages\esp32\tools\mkspiffs\0.2.3 -prefs=runtime.tools.mkspiffs-0.2.3.path=C:\Users\per\AppData\Local\Arduino15\packages\esp32\tools\mkspiffs\0.2.3 -prefs=runtime.tools.xtensa-esp32-elf-gcc.path=C:\Users\per\AppData\Local\Arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\1.22.0-80-g6c4433a-5.2.0 -prefs=runtime.tools.xtensa-esp32-elf-gcc-1.22.0-80-g6c4433a-5.2.0.path=C:\Users\per\AppData\Local\Arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\1.22.0-80-g6c4433a-5.2.0 -verbose C:\Users\per\AppData\Local\Temp\arduino_modified_sketch_19197\sketch_dec31a.ino

the temporary build folder is C:\Users\per\AppData\Local\Temp\arduino_build_889992.

Open the .ino.cpp file in the sketch subfolder of the temporary build folder with a text editor.

The temporary build folder will be deleted when you exit the Arduino IDE.

Sometimes you really have to fight with this “preprocessor level” behavior towards .ino files aimed at making it easy for novice users to create functions in simple sketches without having to consider the order of their definition.

Assume that the function rainbow took an argument of type PatternConfig. If the preprocessor went on to create a prototype for rainbow and put it above the definition of PatterConfig, you’d get mysterious message about PatternConfig not being defined and no easy way of solving it.